跳至內容

額外內容 — 治理掛鉤

📎 額外實作課程 — 不屬於 Copilot SDK。 此內容涵蓋由以下項目驅動的 Shell 掛鉤: .github/hooks/、一個 Copilot CLI 功能。SDK 有自己的處理程序內對等功能 — 請參閱以下內容的權限章節: 實作課程 03 — 工具。此內容為選修,且與編號的 SDK 學習路徑彼此獨立。

目標:preToolUse 安全性防護閘門確實會封鎖對機密檔案的存取、觀察稽核記錄器記錄活動,並瞭解設定錯誤的掛鉤為何比完全沒有掛鉤更危險。

時間: 約 20 分鐘

必要條件: 額外內容 — 自訂代理程式 完成。 jq 已安裝 — 掛鉤指令碼仰賴它。

步驟 1 — 查看掛鉤的接線方式

cat .github/hooks/retail-governance.json
ls -l .github/hooks/scripts/

四個生命週期事件,各自對應一個指令碼:

事件 指令碼 用途
sessionStart session-init.sh 宣告原則並開始稽核軌跡
preToolUse security-gate.sh 允許或拒絕 工具呼叫執行前
postToolUse audit-logger.sh 記錄實際發生的情況
sessionEnd session-end.sh 結束並摘要工作階段

每個項目都會宣告 typebash 要執行的指令碼、工作目錄,以及 timeoutSec.

只有 preToolUse 可以 停止 任何操作。其他事件只負責觀察。

步驟 2 — 一個警示案例

此存放庫曾有一段時間包含故障的防護閘門。設定指向:

./.github/hooks/scripts/security-gate-notworking.sh

…但磁碟上的檔案是 security-gate.sh。參照的指令碼 並不存在.

防護閘門沒有發生錯誤,也沒有發出警告。它只是完全沒有執行 — 每個工具呼叫都未經檢查直接通過,但存放庫看起來卻受到完整治理。舊版的 .env 甚至包含註解,聲稱「preToolUse 掛鉤應封鎖對此檔案的存取」。實際上並不會。

這項問題已修正,也是本實作課程中最重要的教訓:

⚠️ 設定錯誤卻毫無警示的控制措施,比完全沒有控制措施更糟,因為這會製造虛假的信心。請務必證明防護閘門確實有觸發。

確認參照現在正確無誤:

grep -o '"bash": "[^"]*"' .github/hooks/retail-governance.json

列出的每個路徑都必須存在於 .github/hooks/scripts/.

步驟 3 — 閱讀防護閘門

cat .github/hooks/scripts/security-gate.sh

合約很簡單 — stdin 輸入 JSON,stdout 輸出決策:

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TOOL_ARGS=$(echo "$INPUT" | jq -r '.toolArgs')

它會在三種情況下拒絕:

  1. 具破壞性的 Bash 操作rm -rf /, rm -rf ., DROP TABLE, DROP DATABASE, format, mkfs.、Fork bomb 模式
  2. 機密資訊存取 — 提及以下內容的命令: .env, credentials, secrets, .pem, .keypassword
  3. 越界寫入edit/create 之外 src/, tests/, docs/.github/

並發出下列其中一個結果:

{"permissionDecision":"allow"}
{"permissionDecision":"deny","permissionDecisionReason":"..."}

⚠️ 請注意,它會以以下狀態結束: 0 ,即使拒絕時也是如此。 決策 會隨 JSON 承載資料傳送,而不是透過結束代碼 — 非零結束代碼看起來會像掛鉤損壞,而非刻意拒絕。

步驟 4 — 證明防護閘門會封鎖機密資訊

確認指令碼具備執行權限,且記錄目錄已存在:

chmod +x .github/hooks/scripts/*.sh
mkdir -p logs

現在,請透過管線傳入真正工具呼叫會送出的 JSON,直接進行測試:

echo '{"toolName":"bash","toolArgs":{"command":"cat .env"}}' \
  | ./.github/hooks/scripts/security-gate.sh

預期結果:

{"permissionDecision":"deny","permissionDecisionReason":"Access to credential/secret files blocked by security policy"}

這表示防護閘門確實生效 — 也就是先前從未執行的同一項檢查。

步驟 5 — 測試其他路徑

具破壞性的命令:

echo '{"toolName":"bash","toolArgs":{"command":"rm -rf /"}}' \
  | ./.github/hooks/scripts/security-gate.sh

"Destructive command blocked by retail governance policy"

寫入允許目錄之外的位置:

echo '{"toolName":"create","toolArgs":{"path":"/etc/hosts"}}' \
  | ./.github/hooks/scripts/security-gate.sh

"File edits restricted to src/, tests/, docs/, .github/, and top-level project docs"

位於最上層的專案檔案,必須通過:

echo '{"toolName": "create", "toolArgs": {"path": "README.md"}}' \
  | ./.github/hooks/scripts/security-gate.sh

{"permissionDecision":"allow"}

允許清單會比對 相對於存放庫 路徑,因此即使存放庫外有外觀相似的項目,仍會遭到拒絕:

echo '{"toolName": "create", "toolArgs": {"path": "/etc/README.md"}}' \
  | ./.github/hooks/scripts/security-gate.sh

→ 遭到拒絕。若只比對檔名,原本會讓該操作通過。

以及必須通過的合法呼叫:

echo '{"toolName":"bash","toolArgs":{"command":"dotnet build"}}' \
  | ./.github/hooks/scripts/security-gate.sh

{"permissionDecision":"allow"}

⚠️ 務必也要測試允許案例。 一個拒絕所有操作的防護閘門,雖然能通過所有「是否有封鎖?」測試,卻會讓存放庫完全無法使用。

步驟 6 — 檢視拒絕記錄

cat logs/security-denials.log

每次拒絕都會附上 UTC 時間戳記、工具及原因,並附加至記錄:

2026-08-10T11:04:35Z DENIED tool=bash reason="Access to credential/secret files blocked by security policy"

這正是法規遵循檢閱人員會要求的成品:證明控制措施確實存在 以及 它確實觸發的證據。

步驟 7 — 防護閘門不需要檔案實際存在

此存放庫未認可任何機密檔案 — .env 已由 gitignore 忽略,絕不能認可。這不會削弱防護閘門,因為它會比對 命令文字,而不是磁碟上實際存在的內容。

確認沒有 .env,然後仍嘗試讀取它:

ls .env 2>/dev/null || echo "no .env present"
echo '{"toolName":"bash","toolArgs":{"command":"cat .env"}}' \
  | ./.github/hooks/scripts/security-gate.sh

仍遭到拒絕。其他機密資訊模式也會得到相同結果:

echo '{"toolName":"bash","toolArgs":{"command":"cat ~/.ssh/id_rsa.key"}}' \
  | ./.github/hooks/scripts/security-gate.sh

💡 這是雙面刃。文字比對表示防護閘門不會因檔案尚不存在而遭規避 — 但避免使用觸發詞的命令也可能繞過它(cat .en''v,或透過指令碼讀取檔案)。請將它視為防止意外的防護措施,而非抵禦蓄意攻擊者的防線。

步驟 8 — 在掛鉤啟用時執行工作階段

copilot -p "List the files in the src directory" --allow-all-tools

接著檢查稽核記錄器擷取的內容:

ls -la logs/
tail -20 logs/*.jsonl 2>/dev/null || tail -20 logs/*.log 2>/dev/null

💡 掛鉤由 CLI 針對每個工作階段設定。若未顯示任何內容,請確認 CLI 已為此存放庫載入 .github/hooks/retail-governance.json 於此存放庫。

步驟 9 — 停用掛鉤

開發掛鉤時,您會希望暫時停用它。您可以重新命名設定:

mv .github/hooks/retail-governance.json .github/hooks/retail-governance.json.off
# restore with the reverse

…或者從陣列中移除個別事件的項目,以註解該事件。

⚠️ 絕不要在共用分支中停用防護閘門後忘記還原 — 這會重現步驟 2 的無聲失敗情況。請考慮新增 CI 檢查,判斷設定中的每個 bash 設定中的路徑確實存在於磁碟上。

✅ 檢查點

  • [x] 您能說出四個掛鉤事件,以及哪一個可以拒絕操作
  • [x] 您已證明防護閘門會封鎖機密資訊、具破壞性的命令及不當位置的寫入
  • [x] 您已確認合法呼叫仍會通過
  • [x] 您已找到拒絕記錄中的證據
  • [x] 您能說明故障的參照為何危險

💡 延伸練習

新增規則至 security-gate.sh 以封鎖 git push --forcemain。請測試它會拒絕強制推送,同時確認一般的 git push 仍會通過。