Files
ISEP/hooks/tests/leo21c-mcp-guard.test.sh
T
Leo b8060ca263 新增 leo21c-mcp-guard.sh:擋下打向 leo 真庫的 Arcrun/KBDB MCP 呼叫(ISEP#69)
leo 2026-08-27:「我從頭的指令就叫你去打 youlin,stage 環境是它你不知道嗎?
為什麼打算用我的真庫測試?」

實況(總管當天實查):這台機器同時掛兩條 Arcrun MCP 連線,工具名是兩串 UUID——
mcp__00402d88-...__* → logged_in_as: leo21c(真庫),mcp__d0eaf65b-...__* →
logged_in_as: youlin(測試場)。總管整天用第一條,測試資料卻寫在第二條,
造成三次假結論(「wiki 沒送上去」「排在 1691 筆後面」「ingest 沒寫進去」——
全是查錯庫),而總管自己每張派工單都寫著「測試一律用 youlin」,卻一次都沒套用。

判準:任何打向 leo21c 座標的 kbdb_*/arcrun_* MCP 呼叫一律擋,除非明說
(touch /tmp/.leo21c-mcp-ok,單次 15 分鐘、會留痕到 .claude/hooks/
leo21c-mcp-guard.log)。讀寫都擋,但訊息不同:唯讀提示「你可能查錯地方」,
寫入提示「這會汙染真資料」。arcrun_whoami 本身永遠放行(唯一能確認身份的
工具,擋它會造成死結)。

已知限制(查過但查不到穩定識別法,不是猜測):MCP server 的 UUID 是這次連線
分配的,不是穩定識別碼,重裝/換 session 會換掉;~/.claude.json 等本機設定檔
裡找不到這兩條連線的來源設定,判斷是雲端 connector 層動態配的。因此這兩個
UUID 是寫死的(2026-08-27 由 arcrun_whoami 實測得到),且會過期。設計上不是
「表格以外的一律放行」,而是「不在表格裡的 UUID 一樣擋,且訊息明講清單可能
過期」——大聲失敗,不靜默放行到身份不明的目標(fail-closed)。

測試:hooks/tests/leo21c-mcp-guard.test.sh 19/19 通過,涵蓋擋(真庫讀/寫)、
放行(測試場讀/寫)、不擋(whoami 三種座標)、不擋(無關工具)、擋(未知UUID)、
戳記單次放行+留痕。既有測試套件(ask-user-question/dispatch-format/
factory-idle/main-and-prod-push/prod-write/stage-before-prod/sdd-guard)
全部重跑一遍,均維持原本通過狀態,未受本次改動影響。

hooks.json 新增一條 PreToolUse matcher:.*mcp__[0-9a-fA-F-]+__(kbdb_|arcrun_).*
docs/hooks-inventory.md 補上新閘一行,並記錄目前實際磁碟數(49 檔/60 條)
跟這份表既有列表(47/57)之間還有既存落差(isep-presence-beacon.sh/
milestone-due-guard.sh 疑似兩支舊閘未收錄)——不是本票動的閘,未一併查證,
留給總管或後續票核實。

plugin.json 0.5.0 → 0.5.1(改完不升版沒人吃得到;check-version-consistency.sh
在本分支照慣例是紅的,tag 於 merge 時打)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 15:01:29 +08:00

72 lines
3.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# leo21c-mcp-guard.sh 的迴歸測試(ISEP#69)。
HOOK="$1"
rm -f /tmp/.leo21c-mcp-ok 2>/dev/null # 起手清乾淨,避免上一輪殘留戳記污染這輪
mk(){ python3 -c "import json,sys;print(json.dumps({'tool_name':sys.argv[1],'tool_input':{}}))" "$1"; }
PASS=0; FAIL=0
t(){ # t <期望 block|pass> <說明> <tool_name> —— 除了明說放行那幾格,其餘每格開跑前先確保沒有殘留戳記
mk "$3" | "$HOOK" >/dev/null 2>&1; rc=$?
got=$([ $rc -eq 2 ] && echo block || echo pass)
if [ "$got" = "$1" ]; then echo " ✅ $2"; PASS=$((PASS+1))
else echo " ❌ $2 —— 期望 $1,實得 $got"; FAIL=$((FAIL+1)); fi
}
LEO="00402d88-5e2f-4125-b971-8bdac50d7456"
YOULIN="d0eaf65b-583e-464e-aacc-00527ac05c4b"
UNKNOWN="11111111-2222-3333-4444-555555555555"
echo "── 該擋(打向 leo21c 真庫)──"
t block "唯讀查詢打 leo21c" "mcp__${LEO}__kbdb_search"
t block "get_map 打 leo21c" "mcp__${LEO}__kbdb_get_map"
t block "寫入打 leo21c" "mcp__${LEO}__kbdb_create_record"
t block "推工作流打 leo21c" "mcp__${LEO}__arcrun_push_workflow"
echo "── 該放行(打向 youlin 測試場)──"
t pass "唯讀查詢打 youlin" "mcp__${YOULIN}__kbdb_search"
t pass "寫入打 youlin" "mcp__${YOULIN}__kbdb_create_record"
t pass "查 template 打 youlin" "mcp__${YOULIN}__kbdb_list_templates"
t pass "arcrun 讀清單打 youlin" "mcp__${YOULIN}__arcrun_list_workflows"
echo "── 不該擋:arcrun_whoami 本身(不管打哪條線)──"
t pass "whoami 打 leo21c" "mcp__${LEO}__arcrun_whoami"
t pass "whoami 打 youlin" "mcp__${YOULIN}__arcrun_whoami"
t pass "whoami 打未知 UUID" "mcp__${UNKNOWN}__arcrun_whoami"
echo "── 不該擋:非 kbdb/arcrun 的 MCP 工具、非 MCP 工具 ──"
t pass "無關的 MCP 工具" "mcp__${LEO}__some_other_tool"
t pass "一般工具名" "Bash"
t pass "另一個一般工具" "kbdb_search"
echo "── 該擋(清單以外的未知 UUID —— 對照表可能過期,大聲失敗)──"
t block "未知 UUID 的 kbdb_search" "mcp__${UNKNOWN}__kbdb_search"
t block "未知 UUID 的寫入" "mcp__${UNKNOWN}__kbdb_create_record"
echo "── 明說要打真庫:戳記放行(單次)──"
rm -f /tmp/.leo21c-mcp-ok
touch /tmp/.leo21c-mcp-ok
t pass "戳記在,打 leo21c 放行一次" "mcp__${LEO}__kbdb_search"
# 上面 t() 已經在下一次呼叫前 rm -f 戳記,這裡驗證戳記確實是單次
touch /tmp/.leo21c-mcp-ok
mk "mcp__${LEO}__kbdb_search" | "$HOOK" >/dev/null 2>&1
mk "mcp__${LEO}__kbdb_search" | "$HOOK" >/dev/null 2>&1; rc2=$?
if [ $rc2 -eq 2 ]; then echo " ✅ 戳記用完即消失(第二次仍被擋)"; PASS=$((PASS+1))
else echo " ❌ 戳記用完即消失 —— 期望第二次被擋,實得 pass"; FAIL=$((FAIL+1)); fi
rm -f /tmp/.leo21c-mcp-ok 2>/dev/null
echo "── 明說放行要留痕 ──"
LOGDIR="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/hooks"
mkdir -p "$LOGDIR"
rm -f "$LOGDIR/leo21c-mcp-guard.log"
touch /tmp/.leo21c-mcp-ok
mk "mcp__${LEO}__kbdb_search" | "$HOOK" >/dev/null 2>&1
if [ -f "$LOGDIR/leo21c-mcp-guard.log" ] && grep -q "明說放行" "$LOGDIR/leo21c-mcp-guard.log"; then
echo " ✅ 明說放行有寫進 log"; PASS=$((PASS+1))
else
echo " ❌ 明說放行沒有留痕"; FAIL=$((FAIL+1))
fi
rm -f "$LOGDIR/leo21c-mcp-guard.log" /tmp/.leo21c-mcp-ok 2>/dev/null
echo
echo "結果:通過 $PASS 失敗 $FAIL"
[ $FAIL -eq 0 ] || exit 1