安全與沙盒
沙盒、權限等級、鉤子管道,以及 6 階段資金安全棧
本頁介紹交易執行和沙盒機制。面向一般使用者的 Chat 提醒請參閱財務安全提醒。
Minara 在兩個層面實施安全保障:適用於所有工具調用的系統級隔離與權限門控,以及適用於交易路徑的金融專項安全棧。
Minara Agent 的輸出僅供參考,不構成金融或投資建議。在運行軟件、授權交易或使用策略前,請閱讀完整項目免責聲明。在適用法律允許的最大範圍內,Minara.AI 不對因使用本項目而產生或與使用本項目有關的損失負責。
為何資金安全需要 6 個階段? 每個階段針對不同類型的錯誤。LLM 可能幻覺一個代幣名稱、遺忘用戶偏好、遭到提示詞注入,或持有過時的價格數據。將 6 個成本低且相互獨立的檢查疊加在一起,意味著一個錯誤必須突破所有關卡才能真正動用資金。單一的"LLM 判斷"檢查成本更高,且會遺漏一行類型守衛就能攔截的分類錯誤。
本頁涵蓋兩個層面,從基礎開始介紹。
📘 面向操作員的配套說明請參閱 安全 章節。 該章節講解 4 層獨立的安全機制(command-guard、OS jail、fund-moving confirm、script-risk gate)在日常使用中如何呈現、各自防什麼。本頁是 實現參考;那一章是用戶使用層的講解。
實際應用參考:每個涉及資金的功能頁面(交易、投資組合、預測)均鏈接至此。首次交易演示中展示了面向用戶的預覽步驟(6 階段安全棧中的第 3 階段)。
基礎:沙盒、權限等級與鉤子管道
每次工具調用都會經過兩個相互獨立的層:沙盒(文件系統隔離,即工具能夠訪問的範圍);權限等級加鉤子管道(行為門控,即工具當前被允許執行的操作)。
沙盒
所有文件系統工具均以 $dataDir/sandbox/files/(默認:~/.minara/sandbox/files/)為根目錄。每個路徑參數都經過 apps/agent/src/tools/_shared/sandbox.ts 中的 resolveInSandbox() 處理,具體步驟如下:
- 將請求路徑解析至沙盒根目錄。
- 拒絕包含
..且會逃逸根目錄的路徑。 - 解析符號鏈接,拒絕指向沙盒外部的鏈接。
- 返回工具可安全使用的絕對解析路徑。
工具在物理層面無法讀寫 apps/agent/src/、用戶主目錄或沙盒之外的任何位置。此規則適用於 read_file、write_file、patch、search_files 及所有文件相關工具。
Shell 出口也受到鎖定。 terminal 工具會從每個子進程中清除 38 個憑據前綴,shell 包裝器出口(bash -c curl 等)默認禁止。
權限等級
每個 ToolEntry 攜帶一個 permissionTier:
| 等級 | 名稱 | 示例 |
|---|---|---|
| 1 | READ_ONLY | get_price、get_balance、read_file、web_search |
| 2 | CONFIRM_ONCE | analyze_market、deep_research_run、小額閃兌 |
| 3 | ALWAYS_CONFIRM | write_file、patch、buy_token、docx_create |
| 4 | MANUAL_ONLY | 向外部地址 transfer_token、切換緊急停止開關 |
鉤子管道
每次工具調用前,全局 BeforeToolCallHook 會按以下順序執行:
- 緊急停止開關。 若
safetyConfig.killSwitch已激活,所有交易工具(等級 ≥ 2)均被阻止。 - 每日消費上限。 累計涉及資金的交易量會與
safetyConfig.dailySpendCap進行比對。 - 單筆上限。 單筆交易金額與
safetyConfig.perTxMax進行比對。 - 等級門控。 在自主輪次(無用戶參與)中,除非
safetyConfig.autopilotEnabled為true,否則等級 3 及以上工具均被阻止。 - 僅限手動執行。 等級 4 工具始終需要用戶確認的往返同步。
狀態存儲在 SQLite 的 audit_log 表中。每次調用均記錄其推理過程、參數、結果及鉤子決策。不會有任何內容被靜默刪除。
提示詞注入防禦
Agent 通過以下機制防禦來自市場數據、工具輸出和記憶召回的提示詞注入:
- 分隔符隔離。 不可信內容被隨機唯一分隔符包裹,並告知 LLM 不得遵循其中的任何指令。
- 零寬字符清除。 移除 ZWSP、ZWJ、RLO 等字符。
- 內容掃描。 內容進入提示詞前,與經整理的注入載荷數據庫進行正則匹配。
參見 apps/agent/src/core/prompt-builder.ts 和 tests/unit/prompt-injection.test.ts。
金融專項安全棧
權限等級鉤子鏈攔截明顯的錯誤調用。金融專項安全棧攔截隱性的錯誤交易:以 50% 滑點執行的閃兌、單一代幣 5 倍倉位、一有回調即爆倉的 15 倍槓桿永續合約。該模塊位於 apps/agent/src/finance/,將六個可獨立測試的組件組合成完整的交易安全路徑。
安全棧結構
trade intent
│
▼
┌───────────────┐
│ token-safety │ scam detection, canonical address, chain resolution
└───────┬───────┘
▼
┌───────────────┐
│ position-sizing│ fixed_usd / fixed_fraction / half_kelly
└───────┬───────┘
▼
┌───────────────┐
│ exposure-limits│ per-token / per-chain / per-asset-class caps
└───────┬───────┘
▼
┌───────────────┐
│ slippage │ simulate → check price impact → reject if too high
└───────┬───────┘
▼
┌───────────────┐
│ risk-manager │ per-tx max, daily cap, emergency stop (atomic debit)
└───────┬───────┘
▼
SafeTradingClient → Minara backend
│
▼
┌───────────────┐
│ stop-loss │ periodic workflow closes positions on exit trigger
└───────────────┘每個階段都是獨立模塊。頂層 full-risk-manager.ts 負責組合,而非膨脹為單體。這種組合設計是核心所在:任何一個組件都可以替換或擴展,無需改動其他部分。
risk-manager.ts:最低基礎防線
apps/agent/src/finance/risk-manager.ts 是始終運行的第一階段基礎防線,包含三個硬約束:
- 單筆交易上限(默認
$500)。拒絕estimated_value_usd超過上限的交易。 - 每日消費上限(默認
$2,000)。使用BEGIN IMMEDIATE對daily_spendSQLite 表進行原子檢查和扣減,避免併發交易通過競爭超出上限。 - 緊急停止開關。 激活後,所有等級 2 及以上的交易均被拒絕,直至調用
/unkill。
單筆上限和每日上限由 ~/.minara/settings.json 中 safety 區段的字段控制:
{
"maxTransactionAmount": 500,
"dailySpendCap": 2000,
"killSwitchActive": false,
"allowedTokens": [],
"blockedTokens": ["SQUID", "SAFEMARS"]
}白名單默認為空(即所有代幣均可通過)。黑名單始終強制執行。可通過 minara config set safety.maxTransactionAmount 1000 修改。
token-safety.ts:入口檢查
以優先級 50 作為交易鉤子運行,在權限等級鉤子之前執行。負責三項職責:
- 規範地址解析。 當用戶說"在 Arbitrum 買 USDT"時,鉤子從
CANONICAL_ADDRESSES中查找規範地址,確保交易發送至真實的 USDT 合約,而非使用相同 ticker 的仿冒合約。 - 已知欺詐檢測。 維護一組經過整理的代幣 ticker 和地址,無論白名單/黑名單配置如何,均予以硬性攔截。該集合存儲在源代碼中,添加條目需提交 PR。
- 鏈解析。 將模糊的鏈標識符("eth"、"ethereum"、"mainnet")映射為 Minara 後端所需的規範鏈 ID。
若代幣無法解析,交易將以結構化錯誤被拒絕,LLM 可據此說明原因("在 'ethereum' 上無法識別 'SAFEMARS' 為規範代幣,且與欺詐列表匹配")。
position-sizing.ts:倉位大小
三種策略,通過 ~/.minara/settings.json 中的 safety.sizing.strategy 選擇:
fixed_usd
size_usd = min(fixedAmountUsd, max_transaction_usd)確定性策略。"始終交易 $100。"適合定投 (DCA) 工作流。
fixed_fraction
size_usd = min(portfolio_value_usd * fraction, max_transaction_usd)按投資組合比例。"始終使用 2% 的權益。"適合風險平價配置,隨著投資組合縮水,每筆交易金額自動下調。
half_kelly
f_star = (p * b - q) / b // p = win prob, q = 1 - p, b = payoff ratio
size_usd = portfolio_value_usd * f_star * kellyMultiplier // default 0.5基於優勢和方差的最優下注規模。默認使用半 Kelly,因為完整 Kelly 在優勢真實存在時也會產生劇烈回撤。需要 LLM 提供 win_probability 和 payoff_ratio 參數;若任一缺失,則回退至 fixed_fraction。
每種策略的計算結果始終受單筆交易上限約束。SizingDecision.clipped 標誌記錄計算結果是否觸及上限,審計查詢可據此瞭解上限的實際約束情況。
exposure-limits.ts:集中度控制
基於當前投資組合(而非歷史數據)計算三層敞口限制:
interface ExposureLimitsConfig {
maxPerTokenUsd: number; // default $5,000
maxPerChainUsd: number; // default $15,000
maxPerAssetClassFraction: number; // default 0.40 (40% in any one class)
maxTotalExposureUsd: number; // default $50,000
}敞口檢查在交易執行前運行。若新交易會使敞口超過四個限制中的任一項,鉤子將以具體違反的上限為由拒絕執行。
資產類別由 learning/methodology-store.ts → classifyAsset 計算,與技能系統的資產類別分類法一致。因此,在路由器中標記為 crypto_meme 的交易,在敞口檢查中同樣標記為 crypto_meme。統一的詞彙表使得"meme 幣不超過 40%"無需任何膠水代碼即可強制執行。
slippage-protection.ts:價格影響門控
大額交易適用更嚴格的滑點預算:
| 交易規模 | 最大價格影響 |
|---|---|
| 低於 $1,000 | 2.0% |
| $1,000 – $10,000 | 1.0% |
| 超過 $10,000 | 0.5% |
分級結構既能防止小額交易承受 5% 的價格影響(令人惱火但可恢復),也能防止大額交易承受 1%(對於鯨魚級訂單可能造成災難性損失)。上述閾值均為默認值,每個字段均可在 ~/.minara/settings.json 的 safety 區段下配置。
執行閃兌前,鉤子調用 Minara 後端的 /v1/tx/cross-chain/swaps-simulate 接口獲取預估輸出和價格影響。若影響超過該等級上限,交易將被拒絕。此時實際閃兌尚未發生,模擬成本低廉,拒絕操作乾淨利落。
永續合約槓桿上限
maxLeverage: 10硬約束。leverage: 15x 的永續合約訂單將以 leverage_exceeds_cap 被拒絕。上限以單筆交易為單位;目前尚無投資組合級別的槓桿彙總(若需要,可在敞口限制器中添加)。
最低輸出比率
minOutputRatio: 0.95 // expect at least 95% of input USD out針對輸出異常偏低的閃兌報價的最後一道檢查。就算價格影響看起來正常,若模擬顯示"$100 的交易只會得到 $40",該檢查仍會將其攔截。
stop-loss.ts:退出管理
倉位級別的止損規則,共三種類型:
fixed_percent
當價格從入場價下跌 threshold_pct 時退出。
{ type: "fixed_percent", threshold_pct: 0.10 } // 10% drop triggers exittrailing
當價格從入場後最高點下跌 threshold_pct 時退出,而非從入場價計算,可鎖定收益。
{ type: "trailing", threshold_pct: 0.08 } // 8% drawdown from hightime_based
無論盈虧,在固定時長後退出。
{ type: "time_based", max_age_ms: 86400000 } // 24 hours三種類型均支持可選的 take_profit_pct,可在盈利時同樣觸發平倉。
運行機制
止損規則由週期性工作流(workflow/templates/stop-loss-monitor.ts)檢查。該工作流按計劃輪詢開放倉位,判斷各觸發條件是否滿足,並通過常規交易路徑下達平倉訂單。
這一點至關重要:止損模塊本身不會直接調用交易後端,只負責決策。平倉訂單仍會經過完整安全棧(權限等級、每日上限、滑點檢查等所有環節)。止損在交易所故障期間嘗試退出時,同樣遵守所有門控,不享有任何專用旁路。
組合:FullRiskManager
full-risk-manager.ts 將所有組件連接為一個交易鉤子:
new FullRiskManager(db, safetyConfig, {
sizing: DEFAULT_SIZING_CONFIG,
exposure: DEFAULT_EXPOSURE_LIMITS,
slippage: DEFAULT_SLIPPAGE_CONFIG,
});組合方式刻意保持明確。可以替換任意組件(自定義倉位策略、為保守用戶設置更嚴格的敞口限制、在測試中禁用滑點檢查),無需改動其他部分。底層風險管理器始終運行,這是最低基礎防線。
配置入口
安全配置存儲在 ~/.minara/settings.json 的 safety 區段中,可通過 minara config 修改:
minara config list # show every field
minara config get safety.sizing.strategy
minara config set safety.sizing.strategy half_kelly
minara config set safety.exposure.maxPerTokenUsd 2500
minara config set safety.slippage.largeTradeMaxImpact 0.003驗證在讀取時進行:無效值(負數上限、槓桿 > 100、threshold_pct > 1.0)會使安全配置加載器回退至默認值並向結構化日誌輸出警告。不存在以無效配置啟動的情況。
可觀測性
每次交易鉤子決策均記錄在兩張表中:
audit:工具調用、參數、結果。tier_events:觸發的鉤子及原因。
常用排查查詢:
-- Why was my swap rejected yesterday?
SELECT a.tool_name, te.decision, te.reason, a.created_at
FROM audit a
LEFT JOIN tier_events te ON te.trace_id = a.trace_id
WHERE a.tool_name IN ('swap', 'buy', 'sell')
AND a.blocked = 1
AND date(a.created_at) = '2026-04-14';
-- How often are sizing caps binding?
SELECT COUNT(*) FROM audit
WHERE tool_set = 'trade'
AND json_extract(result_json, '$.sizing.clipped') = 1;
-- Which tokens hit exposure limits most often?
SELECT json_extract(args_json, '$.token'), COUNT(*)
FROM audit
WHERE block_reason LIKE '%exposure%'
GROUP BY 1 ORDER BY 2 DESC LIMIT 10;擴展安全棧
- 自定義倉位策略。 在
SizingStrategy中添加新變體,在PositionSizer中為其實現computeSize,並在 env-vars 及本頁記錄新的sizing.*配置字段。 - 新增敞口維度。 在
ExposureLimitsConfig中添加字段,在ExposureLimiter.check中實現檢查,並在交易鉤子的拒絕原因中體現。編寫同時覆蓋通過和失敗兩種情況的單元測試。 - 調整滑點分級。 修改
DEFAULT_SLIPPAGE_CONFIG中的閾值或添加新等級。分級表格刻意保持簡潔,除非有充分理由,否則不要將其改為連續函數。 - 新增止損類型。 在
StopLossType中添加變體,在StopLossEvaluator中實現觸發邏輯,並確保週期性工作流模板能夠識別。
不要添加繞過 FullRiskManager 的交易路徑。單一門控特性是安全模型可辯護的基礎。為"可信"調用方開設快速通道,是那種在審查時看似無害、卻在最需要審計日誌的那天將其破壞的變更。