Agent 安全網設計:從 Sandbox、Hooks 到人工核准
這是「Agentic Engineering 實戰手冊」系列的第十二篇。上一篇:Token 經濟學進階
Agent 自動 Push 了 47 次到 Staging
有一次我設定了 agent 自動部署到 staging 的 workflow,然後去吃午餐。回來的時候,staging 環境已經被部署了 47 次。
原因是 agent 卡在一個 deploy → test → fix → redeploy 的 loop 裡。每次 fix 都產生新的 issue,然後又觸發 redeploy。我去吃午餐的一個小時裡,它忠實地執行了 47 個 cycle。
staging 環境沒壞,但同事們的 test data 全被覆蓋了。那個下午我花了兩小時幫大家恢復環境,以及更久的時間恢復他們對我搞的 automation 的信任。
這件事教了我一件事:agent 最危險的特質不是它會做錯事,而是它會不知疲倦地一直做錯事。人類犯錯會停下來思考,agent 犯錯只會繼續 loop。
Agent 的權限困境
Agent 的有用程度跟它的權限成正比:
- 只能讀 code → 能幫你查東西,但不能幫你做事
- 能讀寫 code → 能幫你寫 code、修 bug
- 能執行指令 → 能跑 test、build、lint
- 能操作 Git → 能 commit、create branch、push
- 能操作外部系統 → 能 deploy、query database、send notification
- 能操作瀏覽器 → 能做 visual testing、填表單、抓資料
每一層權限的增加,都讓 agent 更有用,也更危險。
目標不是「限制 agent」(那樣它就沒用了),而是設計可控的自由度,在每個權限等級上放對的護欄。
安全網的三層架構
我的 agent 安全設計有三層,從自動到人工:
Layer 1: Sandbox(自動隔離)
↓ 擋住大部分的「無意間搞破壞」
Layer 2: Hooks & Guardrails(自動檢查)
↓ 擋住「不該做的操作」
Layer 3: Human-in-the-Loop(人工審批)
↓ 擋住「需要判斷的決策」
Layer 1: Sandbox 環境設計
Git Branch 是回滾點,不是 Sandbox
Feature branch 可以隔離 commit 歷史,卻不能限制 agent 讀寫 home directory、呼叫網路或操作外部服務。它是很有用的版本控制護欄,但不要把它當安全邊界。
# Agent 開始工作前
git checkout -b feat/agent-task-xxx
# Agent 工作完成後
# → 你 review → merge → 或 discard
萬一變更只存在這個 branch、所有檔案都已追蹤,而且沒有碰 database、雲端或其他外部狀態,刪掉 branch 就能回到原本的 commit。未追蹤檔案、共用 working tree 與外部副作用不會因此復原。
Git Worktree
進階做法:用 git worktree 讓 agent 在獨立目錄裡工作。它能避免直接踩到你目前 working tree 的未提交內容,但仍共享同一個 repository,也不限制 repo 外的檔案或網路。
Claude Code 有內建的 worktree 支援——你可以讓 sub-agent 在 worktree 裡跑,確保主 context 的檔案狀態不被干擾。
Network Isolation
Codex CLI 使用作業系統層級的 sandbox 與 approval policy;macOS、Linux、Windows 的實作不同,本機 Codex 不等於一律跑在 Linux container。Cloud tasks 則使用隔離的雲端環境。
Claude Code 現在也提供 sandboxed Bash,用 OS 級機制限制 filesystem 與 network;它和一般 permission rules 各管不同層。兩邊都要實際檢查 allowlist、可寫路徑、network 與 credentials,不能只看到「sandbox」三個字就放心。
Layer 2: Hooks 作為 Guardrails
Claude Code 的 hooks 系統讓你在 agent 的操作前後自動執行檢查:
| Hook 類型 | 觸發時機 | 用途 |
|---|---|---|
| PreToolUse | Agent 要使用工具之前 | 攔截危險操作 |
| PostToolUse | Agent 使用工具之後 | 記錄 / 檢查結果 |
| Notification | 需要通知時 | Alert / log |
我的 Hook 設定:
1. 敏感檔案保護
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-sensitive-files.sh"
}
]
}
]
}
}
Hook command 會從 stdin 收到 JSON;檔案路徑在 .tool_input.file_path,不是不存在的 $FILE_PATH 環境變數。腳本可以用 jq 取值,命中 .env、credentials 或其他敏感路徑時把原因寫到 stderr,並回傳 exit code 2 阻擋操作。完整事件格式與 exit code 行為以 Claude Code Hooks 官方文件為準。
2. Git Push 確認
Agent 可以在隔離 branch commit,但 push 預設需要人工確認。Push 本身可以用後續 commit 或刪 branch 修正;真正不可逆的是 secret、個資或未公告內容一旦送到 remote,可能已被其他系統或人讀取。
3. 破壞性操作攔截
rm -rf、DROP TABLE、git reset --hard——這些操作在被 agent 執行之前,一律需要你的明確批准。
Layer 3: Human-in-the-Loop 斷路器
有些操作,不管有多少自動化檢查,最終都需要人來決定。
斷路器設計原則:Reversibility × Blast Radius,再加資料敏感度與外部副作用
| 低影響 | 高影響 | |
|---|---|---|
| 可逆 | 自動放行 | Hook 檢查 |
| 不可逆 | Hook 檢查 | 人工審批 |
對應到實際操作:
自動放行(可逆 + 低影響):
- 讀取檔案
- 跑 test / lint
- 在隔離 worktree/feature branch 上 commit
- 格式化 code
- 在本地執行 build
Hook 檢查(可逆 + 高影響 or 不可逆 + 低影響):
- 修改 config files
- 安裝 npm packages
- 修改 database migration files
人工審批(高影響、對外或接觸敏感資料):
- Git push to remote(尤其包含未公開內容或觸發自動部署時)
- Deploy to staging / production
- 刪除檔案或 branch
- 修改 CI/CD pipeline
- 任何涉及 credentials 的操作
Near-Miss Stories:差點出事的那幾次
案例 1:47 次 Staging Deploy
(開場說的那個。)
根因:agent 有自動 deploy 的權限,且沒有 rate limit。
加了什麼防護:
- Deploy 操作加了 cooldown:每 10 分鐘最多 deploy 一次
- 連續失敗 3 次自動停止(回扣 CLAUDE.md 的 “max 3 attempts” rule)
- 在 workflow 尚未證明穩定前,deploy 前需要人工 confirm
案例 2:差點 Push Credentials
Agent 在修一個環境設定問題時,為了「方便測試」,把一個 API key 硬寫在 code 裡。然後它準備 commit + push。
幸好 pre-commit hook 攔住了——我的 hook 裡有 credential 掃描(用 gitleaks),偵測到 API key pattern 就 block 了 commit。
根因:Agent 不理解 secrets management。它的 goal 是「讓 code 能跑」,hardcode API key 就是達到這個 goal 最快的方式。
加了什麼防護:
- Pre-commit 的 credential scanning(原本就有,救了一命)
- 在 CLAUDE.md 裡加了明確規則:「永遠不要 hardcode secrets,使用環境變數」
.env檔案加入 hook 的保護清單
案例 3:Production Migration 驚魂
Agent 寫了一個 database migration script,在 staging 跑得很順利。然後它「貼心地」準備了 production 的 migration command——包含 production database 的連線字串。
如果我沒注意到那個 command 裡的 hostname 是 production 而不是 staging,然後不小心讓 agent 執行了…
根因:Agent 不區分環境。它看到 staging 成功了,就按照同樣的模式準備 production 的指令。它不知道 production migration 需要完全不同的審批流程。
加了什麼防護:
- Production 連線字串不在任何 agent 可以讀取的檔案裡
- 任何包含
production或prod關鍵字的指令需要人工 confirm - Production migration 預設由人核准;若未來要自動化,必須另有 backup、相容性檢查、dry run、觀測與 rollback/roll-forward 設計
權限的漸進式開放
不建議一次開放所有權限。建議的漸進路徑:
Week 1-2:Read Only + Write Code
✅ 讀檔案
✅ 寫 code(在 feature branch)
✅ 跑 test / lint
❌ Git commit(你手動做)
❌ 安裝 packages
❌ 執行任意 shell 命令
熟悉 agent 的行為模式。看它會做什麼決策、會犯什麼錯。
Week 3-4:加 Git 操作
✅ 以上全部
✅ Git commit(到 feature branch)
✅ 安裝 npm packages(需確認)
❌ Git push
❌ 操作外部系統
Month 2+:加外部操作
✅ 以上全部
✅ Git push(需確認)
✅ MCP 操作(指定的 server)
✅ Deploy to staging(需確認)
❌ Deploy to production
❌ Database 操作
預設保留人工審批的事
以下是我目前預設保留人工審批的操作:
- Production deploy
- Database migration on production
- 刪除 production 資料
- 修改 IAM / permissions
- Push to main/master
這不是宇宙通則。有成熟 progressive delivery、policy-as-code 與自動 rollback 的團隊,部分步驟可以安全自動化;但在護欄還沒被證明前,先保留核准比較合理。
建立你的安全網 Checklist
## Agent Safety Checklist
### Sandbox
- [ ] Agent 在隔離 worktree/feature branch 上工作,不碰使用者未提交變更
- [ ] 變更前有 checkpoint,且已演練 discard branch、revert 或 roll-forward
- [ ] Production credentials 不在 agent 可讀的範圍
### Automated Guardrails
- [ ] Pre-commit hooks:credential scanning、lint、type check
- [ ] Sensitive file protection(.env、config with secrets)
- [ ] Rate limiting on destructive operations
- [ ] Max retry limit(3 次失敗自動停止)
### Human-in-the-Loop
- [ ] Git push 需要確認
- [ ] Deploy 需要確認
- [ ] 刪除操作需要確認
- [ ] Production-related 操作需要確認
### Monitoring
- [ ] Agent 的操作有 log(至少 git history)
- [ ] 異常行為有通知(連續失敗、大量操作)
- [ ] 定期 review agent 的操作歷史
Takeaway
-
Agent 安全是設計可控的自由度:OS 級 sandbox、hooks/policy 與人工核准各擋不同風險,不能互相取代。
-
Git 只保護版本化的檔案狀態:branch/worktree 很適合 review 與回滾 code,但保護不了 secrets 外洩、database 變更與外部 API 副作用;這些要靠真正的 sandbox、權限與環境隔離。
-
每次 near-miss 都是加強安全網的機會:不要等到真的出事。47 次 staging deploy 教我加 rate limit,差點 push credentials 教我加 secret scanning。把每次驚險經歷都轉化成一條新的 guardrail。