Claude Code 要讀取敏感檔案前:用 PreToolUse hook 多加一道阻擋規則
當 coding agent 開始幫忙找設定、追 log 或修 CI,最容易被忽略的不是它能不能修改檔案,而是它為了理解問題會讀到什麼。.env、私鑰、部署憑證和匯出的設定檔,往往都在同一個 repository 裡;即使沒有寫入風險,也不代表應該把內容放進工作上下文。
這篇的目標很窄:用 Claude Code 的 PreToolUse hook,在工具實際執行前攔下明確不該碰的檔案與指令。它不是秘密管理系統,也不是取代 code review;它只是把「這類操作不該由 agent 做」變成可執行的規則。
先把敏感資訊和一般設定檔分開
不要用一條模糊規則,例如「不要讀機密」。先列出目前專案不應交給 agent 的實體目標,再決定是否需要搬離 repository。
| 類型 | 例子 | 較合適的處理 |
|---|---|---|
| 長期憑證 | .env、私鑰、cloud token | 移出 repo、改用 secret store;hook 做最後一道防線 |
| 可公開範例 | .env.example、設定 schema | 可讀,但不可含真實值 |
| 部署輸出 | 備份、下載的設定、log dump | 放在受限位置,不要和原始碼混放 |
| 一般專案設定 | package.json、tsconfig.json | 仍需 review,但通常可讀 |
最重要的判斷是:hook 只能降低誤觸機率,不能把已經提交到 Git 的 secret 變回安全資料。 若真實憑證已經進入版本控制,先撤銷與輪替它,再處理歷史與存放位置。
PreToolUse hook 能做什麼
Claude Code 官方文件說明,PreToolUse 會在工具呼叫前觸發;hook 可讀取送到 stdin 的 JSON,並回傳 permissionDecision: "deny" 來拒絕該呼叫。這適合用來處理明確、可比對的禁止條件。
先在專案建立 .claude/hooks/block-sensitive-access.sh:
#!/usr/bin/env bashset -euo pipefail
input="$(cat)"tool_name="$(printf '%s' "$input" | jq -r '.tool_name // ""')"
# Read、Glob、Grep 的路徑欄位不同;把可取得值集中後再比對。target="$(printf '%s' "$input" | jq -r ' .tool_input.file_path // .tool_input.path // .tool_input.pattern // .tool_input.command // ""')"
if [[ "$target" =~ (^|/)(\.env|\.env\..+|id_rsa|id_ed25519|.*\.pem|.*\.key)($|/) ]] \ || [[ "$target" =~ (cat|sed|awk|grep)[[:space:]].*(\.env|id_rsa|id_ed25519|\.pem|\.key) ]]; then jq -n --arg reason "Sensitive file access is blocked by this project hook" '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: $reason } }' exit 0fi
# 沒有決策時,Claude Code 會走既有的 permission 流程。exit 0這個範例刻意不把 .env.example 納入阻擋,也不嘗試猜測所有 secret 格式。先用小而可檢查的名單,避免規則過度寬鬆而讓日常工作完全無法進行。
在專案設定中只匹配需要攔的工具
接著將 hook 放進 .claude/settings.json。官方文件支援在這個檔案定義 project scope 的 hook,因此團隊可以和程式碼一起 review 規則。
{ "hooks": { "PreToolUse": [ { "matcher": "Read|Glob|Grep|Bash", "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-sensitive-access.sh" } ] } ] }}matcher 的作用是決定什麼工具會觸發 handler;它不是萬用的存取控制清單。對 Bash 而言,請把 hook 視為額外檢查,並用 Claude Code 的權限規則限制可執行命令,不要把所有安全假設都押在一段正規表示式上。
先用安全的測試案例驗證它真的會拒絕
設定完成後,請在測試 repository 或不含真實 secret 的檔案上檢查。可先建立假資料:
mkdir -p /tmp/claude-hook-checkprintf 'DEMO_TOKEN=not-a-real-secret\n' > /tmp/claude-hook-check/.env在該目錄啟動 Claude Code,要求它讀取 .env;預期結果是拒絕原因顯示在工作階段中。再讓它讀取 README.md 或 .env.example,確認正常工具呼叫沒有被誤攔。
如果規則沒有生效,先檢查三件事:hook 檔案能否執行、.claude/settings.json 是否為有效 JSON、Claude Code 是否在該 project root 啟動。不要先把 matcher 擴成 .*;先知道是哪一層沒有載入,才能避免新增大量無關攔截。
hook、權限與 secret store 各自解決不同問題
| 控制 | 解決的問題 | 不解決的問題 |
|---|---|---|
| PreToolUse hook | 擋下可辨識的危險工具呼叫 | 已外洩的憑證、未知檔名的機密 |
| Claude Code permissions | 限制工具與指令可否執行 | 不會自動理解哪個值是 secret |
| Secret manager/CI secrets | 讓長期憑證不落在工作目錄 | agent 的其他危險操作 |
| Code review 與最小權限 | 發現規則漏洞、限制影響範圍 | 即時阻止每次誤觸 |
實務上我會先把憑證移到正確的存放位置,再在 project settings 放 hook,最後用最小權限執行 agent。順序不能倒過來:一個漂亮的 hook,補不了散落在 Git 歷史和工作目錄裡的 token。
這次要記住的事
Claude Code 的 hook 最適合把少數明確的禁區自動化,而不是假裝它能辨識所有機密。先列出禁止讀取的檔案與命令、在測試資料上驗證拒絕行為,再配合 secret store 與權限設定,才會形成真正可維護的防線。
常見問題
Q: PreToolUse hook 可以取代 Claude Code 的 permission 設定嗎?
A: 不可以。hook 是工具呼叫前的自訂檢查;權限設定仍負責哪些工具和命令可以執行。官方文件也指出,hook 的 Bash 篩選屬於 best-effort,硬性的 allow/deny 應交給 permission system。
Q: 我應該阻擋 .env.example 嗎?
A: 通常不需要。它的用途就是提供可公開的欄位名稱與設定範例。前提是它沒有真實 token、內網網址或其他不該公開的值;把範例檔當成 secret 是另一個需要先修正的設計問題。
Q: 已經把 token 提交到 Git,新增 hook 還有用嗎?
A: hook 仍能避免後續工作階段再讀取該檔案,但不能解除既有外洩。應立即撤銷或輪替 token,確認 CI 與部署設定使用新值,並依團隊流程處理 Git 歷史與存取紀錄。
參考資料:
回報錯字、失效連結,或告訴我你想看的延伸主題。