MINARA

安全與沙盒

沙盒、權限等級、鉤子管道,以及 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_filewrite_filepatchsearch_files 及所有文件相關工具。

Shell 出口也受到鎖定。 terminal 工具會從每個子進程中清除 38 個憑據前綴,shell 包裝器出口(bash -c curl 等)默認禁止。

權限等級

每個 ToolEntry 攜帶一個 permissionTier

等級名稱示例
1READ_ONLYget_priceget_balanceread_fileweb_search
2CONFIRM_ONCEanalyze_marketdeep_research_run、小額閃兌
3ALWAYS_CONFIRMwrite_filepatchbuy_tokendocx_create
4MANUAL_ONLY向外部地址 transfer_token、切換緊急停止開關

鉤子管道

每次工具調用前,全局 BeforeToolCallHook 會按以下順序執行:

  1. 緊急停止開關。safetyConfig.killSwitch 已激活,所有交易工具(等級 ≥ 2)均被阻止。
  2. 每日消費上限。 累計涉及資金的交易量會與 safetyConfig.dailySpendCap 進行比對。
  3. 單筆上限。 單筆交易金額與 safetyConfig.perTxMax 進行比對。
  4. 等級門控。 在自主輪次(無用戶參與)中,除非 safetyConfig.autopilotEnabledtrue,否則等級 3 及以上工具均被阻止。
  5. 僅限手動執行。 等級 4 工具始終需要用戶確認的往返同步。

狀態存儲在 SQLite 的 audit_log 表中。每次調用均記錄其推理過程、參數、結果及鉤子決策。不會有任何內容被靜默刪除。

提示詞注入防禦

Agent 通過以下機制防禦來自市場數據、工具輸出和記憶召回的提示詞注入:

  • 分隔符隔離。 不可信內容被隨機唯一分隔符包裹,並告知 LLM 不得遵循其中的任何指令。
  • 零寬字符清除。 移除 ZWSP、ZWJ、RLO 等字符。
  • 內容掃描。 內容進入提示詞前,與經整理的注入載荷數據庫進行正則匹配。

參見 apps/agent/src/core/prompt-builder.tstests/unit/prompt-injection.test.ts

金融專項安全棧

權限等級鉤子鏈攔截明顯的錯誤調用。金融專項安全棧攔截隱性的錯誤交易:以 50% 滑點執行的閃兌、單一代幣 5 倍倉位、一有回調即爆倉的 15 倍槓桿永續合約。該模塊位於 apps/agent/src/finance/,將六個可獨立測試的組件組合成完整的交易安全路徑。

finance-safety diagram

安全棧結構

          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 是始終運行的第一階段基礎防線,包含三個硬約束:

  1. 單筆交易上限(默認 $500)。拒絕 estimated_value_usd 超過上限的交易。
  2. 每日消費上限(默認 $2,000)。使用 BEGIN IMMEDIATEdaily_spend SQLite 表進行原子檢查和扣減,避免併發交易通過競爭超出上限。
  3. 緊急停止開關。 激活後,所有等級 2 及以上的交易均被拒絕,直至調用 /unkill

單筆上限和每日上限由 ~/.minara/settings.jsonsafety 區段的字段控制:

{
  "maxTransactionAmount": 500,
  "dailySpendCap": 2000,
  "killSwitchActive": false,
  "allowedTokens": [],
  "blockedTokens": ["SQUID", "SAFEMARS"]
}

白名單默認為空(即所有代幣均可通過)。黑名單始終強制執行。可通過 minara config set safety.maxTransactionAmount 1000 修改。

token-safety.ts:入口檢查

以優先級 50 作為交易鉤子運行,在權限等級鉤子之前執行。負責三項職責:

  1. 規範地址解析。 當用戶說"在 Arbitrum 買 USDT"時,鉤子從 CANONICAL_ADDRESSES 中查找規範地址,確保交易發送至真實的 USDT 合約,而非使用相同 ticker 的仿冒合約。
  2. 已知欺詐檢測。 維護一組經過整理的代幣 ticker 和地址,無論白名單/黑名單配置如何,均予以硬性攔截。該集合存儲在源代碼中,添加條目需提交 PR。
  3. 鏈解析。 將模糊的鏈標識符("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_probabilitypayoff_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,0002.0%
$1,000 – $10,0001.0%
超過 $10,0000.5%

分級結構既能防止小額交易承受 5% 的價格影響(令人惱火但可恢復),也能防止大額交易承受 1%(對於鯨魚級訂單可能造成災難性損失)。上述閾值均為默認值,每個字段均可在 ~/.minara/settings.jsonsafety 區段下配置。

執行閃兌前,鉤子調用 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 exit

trailing

當價格從入場後最高點下跌 threshold_pct 時退出,而非從入場價計算,可鎖定收益。

{ type: "trailing", threshold_pct: 0.08 }  // 8% drawdown from high

time_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.jsonsafety 區段中,可通過 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 的交易路徑。單一門控特性是安全模型可辯護的基礎。為"可信"調用方開設快速通道,是那種在審查時看似無害、卻在最需要審計日誌的那天將其破壞的變更。

本頁目錄