Files
Arcrun/cli/harness/hooks/arcrun-guard.sh
T
uncle6me-web 68da769c1b fix(harness): guard hook 區分「執行 acr push」vs「展示指令」(壓測 §9.5)
舊版 grep "acr push" 連「在 echo/heredoc 裡提到 push 字串」都擋,
導致 CC 連「把可貼上的指令印給使用者看」都做不到 → 反而違反
user-cc-harness §0.5「擋下必須印出正路」自身鐵則(連選②都印不出)。

修法(啟發式,誠實標明非完美,mindset §7):
- awk 抽掉 heredoc 主體 → 剩下「實際執行命令列」才看 push 是否在命令位置
  (行首 / ; & | && ||)。真執行擋、純展示放行。
- block 訊息補上選②可貼指令(符合 §0.5)。
- shell 層無法 100% 區分執行/展示,偷渡(bash<<EOF / $(...))由上游安全分類器擋。

驗證:8/8 案例正確(真執行全 BLOCK、純展示全 ALLOW)+ 邊角;bash -n OK。
SDD: .agents/specs/user-cc-harness/tasks.md A6 + C9。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:11:28 +08:00

91 lines
5.7 KiB
Bash
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.
#!/bin/bash
# arcrun-guard.sh — 用戶專案的 arcrun PreToolUse guard(由 acr install-harness 裝進 .claude/hooks/
#
# 對象:在「用 arcrun 開發」的專案裡工作的 CC。擋它走歪(退回自寫 Python / 不用 recipe / 未經同意暴露)。
# 與 arcrun repo 開發版 hook 完全不同(那個擋的是開發 arcrun 本身)。
#
# 鐵則(user-cc-harness design §0.5):每次擋下/提醒都要給「具體怎麼做才對」的正路,不只說「不行」。
#
# 退出碼:0=允許(可附 stderr 提醒);2=硬擋(stderr 回給 CC)。
# 分級(design §4):多數用「提醒不硬擋」(避免誤殺正常 python);硬擋只留給「未經同意暴露資料」。
set -o pipefail
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
remind() {
# 提醒但放行(exit 0)。CC 看到 stderr,自己判斷是否真要繼續。
echo "💡 arcrun 提醒:$1" >&2
echo " 正路:$2" >&2
exit 0
}
block() {
echo "❌ arcrun guard 擋下:$1" >&2
echo " 正路:$2" >&2
exit 2
}
# ── 硬擋:未經人類同意的暴露動作(明確越界,mindset §5)──────────────
# 非互動環境下 CC 自己跑「部署對外 webhook / push recipe」= 替人類決定公開。
#
# 壓測 §9.5 修正(A6):舊版 grep "acr push" 連「在 echo/heredoc 裡**提到** push 字串」
# 都擋 → CC 連「把可貼上的指令印給使用者看」都做不到,反而違反 §0.5「擋下必指正路」鐵則。
# 故先判斷此命令是「展示指令」還是「真的執行 push」,只擋後者。
#
# 誠實限制(mindset §7):shell 命令層**無法 100% 乾淨區分**「執行」與「展示」
# (例:`echo "跑 acr push"` 與 `acr push` 對單純 grep 都命中)。以下是**啟發式**,
# 目的是降低誤殺、讓「印出正路指令」這條合法用途能通,不是完美防護。邊角情況可能漏判,
# 但漏判方向是「偏向放行展示」——而真正的暴露(執行)仍受 TTY/env 把關。
if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then
# 目標(壓測 §9.5):擋「真的執行 push」,放行「把 push 指令印給使用者看」。
#
# 作法:把 heredoc 主體(cat/印出用的多行文字)先抽掉,剩下的才是「實際會被 shell 執行的命令列」,
# 再看 push 是否出現在那裡的「命令位置」(行首 / ; & | && || 之後)。
# 執行 → 命中:`acr push x`、`cd f && acr push x`、`echo done; acr push x`
# 展示 → 不命中:`echo "跑 acr push x"`、`cat <<EOF\n acr push x \nEOF`push 在 heredoc 主體內)
#
# 抽掉 heredoc 主體:刪掉從 `<<EOF`(或任何 <<TOKEN)那行的下一行起、到單獨一行 TOKEN 為止的內容。
# 用 awk 做簡單狀態機(只處理最常見的 `<<TOKEN` / `<<-TOKEN`,不含複雜巢狀——夠用且誠實)。
EXEC_PART=$(printf '%s\n' "$CMD" | awk '
BEGIN{inhd=0}
{
if (inhd) { if ($0 ~ "^[[:space:]]*" term "[[:space:]]*$") { inhd=0 }; next }
line=$0
if (match(line, /<<-?[[:space:]]*"?'"'"'?[A-Za-z_][A-Za-z0-9_]*"?'"'"'?/)) {
t=substr(line, RSTART, RLENGTH); gsub(/^<<-?[[:space:]]*["'"'"']?/, "", t); gsub(/["'"'"']$/, "", t)
term=t; inhd=1
}
print line
}')
# 誠實限制(mindset §7):shell 層無法 100% 乾淨區分「執行」與「展示」。awk heredoc 抽取只覆蓋
# 常見形式(`<<EOF` / `<<-EOF` / 引號 token);`bash <<EOF ... acr push` 這種「heredoc 內容其實
# 會被執行」的偷渡,這裡會誤放——但它已被上游安全分類器擋下(壓測 §9.6 實證)。取捨上偏向
# 「放行展示」以保住 §0.5「擋下也要能印出正路指令」這條合法用途,不假裝此啟發式完美。
if echo "$EXEC_PART" | grep -qE "(^|[;&|][[:space:]]*)acr[[:space:]]+(push|recipe[[:space:]]+push)\b"; then
if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then
block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \
"交人類在終端機執行(真 TTY 會自動放行)。可把指令完整複製給使用者貼上自己跑:\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。"
fi
fi
fi
# ── 提醒(不硬擋):退回自寫 Python/Node 一次性自動化 ──────────────────
# 「我先用 Python 測試」這類退回熟悉工具的傾向。python 不絕對錯(可能跑測試),故提醒不擋。
if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then
# 排除明顯的測試 / 既有工具呼叫(pytest / npm test / jest 等)降低誤判
if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then
remind "偵測到用 python/node 跑腳本。這專案用 arcrun,串服務/自動化不要自刻一次性腳本。" \
"先跑 \`acr parts\` 看有哪些零件,把需求寫成 workflow.yaml 用 \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。"
fi
fi
# ── 提醒(不硬擋):自寫打固定 API 的 script,而非 recipe ──────────────
if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then
remind "偵測到自己打外部 API。arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \
"用 \`acr recipe push\` 把這個 API 包成 recipeworkflow 裡用 component 引用它。見 arcrun-mindset Skill。"
fi
exit 0