d19bf88503
09-07 三種寫入在雲端全被 auto mode 分類器擋:npx wrangler secret put、 stage-deploy-artifacts.py --confirm、curl POST 到 arcrun-yuga3bse—— 白名單是逐條比前綴,複合指令與任意 curl 永遠對不上;放整類又是紅線。 - scripts/stage secret put|list|delete:直接打 CF API(不經 npx);先 GET /accounts 看這把 token 打得到誰,stage 的 ID 不在裡面就拒絕並印出它打得到誰;URL 永遠只帶 1129efd7…;值只從 --from-env 讀,--value 拒收。 - scripts/stage api <METHOD> <worker>/<path>:curl 打實例,主機只會是 *.arcrun-yuga3bse.workers.dev,別的主機拒絕;標頭與 body 走 stdin 設定檔+管線, 不走 argv;--bearer-env/--header-env/--data-env 從環境變數讀。 - docs/permissions-allow.json:stage 四種形狀+Arcrun 自己那支 stage-deploy-artifacts.py 的相對形狀(它已寫死只認 youlin 帳號 ID、不收 --account/--token,不重造)。 - scripts/test-stage.sh 45 條(假 CF 對 PUT 回 201——第一次實跑 CF 回 201 被講成失敗, 就是這格抓的);test-settings-allow-sync 仍 19/19。 - 真跑:探針種進 youlin arcrun-cypher-executor、CF API 獨立列到、刪掉恢復原狀; api GET health 200、無 Bearer POST 401、leo21c 主機拒絕零請求。 「帶 Bearer 真寫 KBDB」缺 KBDB_INTERNAL_TOKEN 沒跑(TESTING A36 標明)。 盤點在本樹實數:hooks 61、註冊 85(都沒動)、scripts 56(+stage、+test-stage.sh)。 版本待總管定版。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DTZ9QtvjY7MNxjfQbexAm7
98 lines
6.3 KiB
Bash
Executable File
98 lines
6.3 KiB
Bash
Executable File
#!/bin/bash
|
||
# settings-allow-sync 的測試(inkstone/ISEP#130)
|
||
#
|
||
# 它守什麼: 「雲端 session 開工時就帶著跟本機同一份權限白名單」這件事的機器那一半。
|
||
# leo 09-07 親手加的四條只在本機 InkStoneCo/.claude/settings.json;雲端沒有 ⇒ 同樣動作被分類器擋
|
||
# (09-04 run log:permission_denials=6)。清單改住 ISEP 一份,本支把它寫進那台機器的 ~/.claude/settings.json。
|
||
#
|
||
# 🔴 全程在 TMP 底下的假 settings.json 上跑,不碰真的 ~/.claude/settings.json。
|
||
cd "$(dirname "$0")/.." || exit 1
|
||
S=scripts/settings-allow-sync
|
||
LIST=docs/permissions-allow.json
|
||
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
|
||
PASS=0; FAIL=0
|
||
ok(){ PASS=$((PASS+1)); printf ' ✅ %s\n' "$1"; }
|
||
bad(){ FAIL=$((FAIL+1)); printf ' ❌ %s\n' "$1"; }
|
||
N=$(python3 -c "import json;print(len(json.load(open('$LIST'))['allow']))")
|
||
|
||
echo "── A. 清單本身的形狀 ──"
|
||
python3 - "$LIST" <<'PY' && ok "A1 每一條都是 Bash(…) 形狀,而且六個正門工具都在場(ticket/mainline/gate-ok/gitea-pr-merge/stage/Arcrun 的 stage-deploy-artifacts.py)" || bad "A1 清單形狀不對或少了正門工具"
|
||
import json, sys
|
||
rules = json.load(open(sys.argv[1]))["allow"]
|
||
assert rules and all(r.startswith("Bash(") and r.endswith(")") for r in rules), rules
|
||
for tool in ("scripts/ticket", "scripts/mainline", "scripts/gate-ok", "scripts/gitea-pr-merge.sh", "scripts/stage ", "scripts/stage-deploy-artifacts.py"):
|
||
assert any(tool in r for r in rules), "缺 " + tool
|
||
assert not any(r.strip() in ("Bash(python3 *)", "Bash(*)", "Bash(bash *)") for r in rules), "整類放行"
|
||
PY
|
||
|
||
echo "── B. 寫進去(假的 settings.json)──"
|
||
T="$TMP/a/settings.json"
|
||
OUT=$(python3 "$S" --target "$T" --home /root 2>&1); RC=$?
|
||
[ "$RC" = 0 ] && [ -f "$T" ] && ok "B1 目標不存在 ⇒ 建出來(離開碼 0)" || bad "B1 rc=$RC:$OUT"
|
||
python3 -c "import json;d=json.load(open('$T'));assert len(d['permissions']['allow'])==$N" && ok "B2 寫進 $N 條,JSON 合法" || bad "B2 條數不對或不是 JSON"
|
||
grep -q 'Bash(python3 /root/.claude/plugins/cache/inkstone/isep/\*/scripts/ticket \*)' "$T" \
|
||
&& ok "B3 開頭的 ~ 換成了 --home 給的家目錄(雲端=/root)" || bad "B3 ~ 沒換成家目錄"
|
||
grep -q '"~/' "$T" && bad "B4 檔裡還留著 ~" || ok "B4 檔裡沒有殘留的 ~"
|
||
grep -q 'CLAUDE_PLUGIN_ROOT' "$T" && ok "B5 \$CLAUDE_PLUGIN_ROOT 那幾條原樣保留(閘印出來的逃生門就是這個形狀)" || bad "B5 少了 \$CLAUDE_PLUGIN_ROOT 形狀"
|
||
tail -c1 "$T" | od -An -c | grep -q '\\n' && ok "B6 收尾有換行" || bad "B6 沒換行"
|
||
|
||
SUM1=$(cksum < "$T"); OUT=$(python3 "$S" --target "$T" --home /root 2>&1); SUM2=$(cksum < "$T")
|
||
[ "$SUM1" = "$SUM2" ] && printf '%s' "$OUT" | grep -q '已齊' && ok "B7 跑第二次:檔案一個位元組都沒變、說「已齊」(冪等)" || bad "B7 不冪等:$OUT"
|
||
|
||
echo "── C. 只加不減:別人手加的東西一個字不動 ──"
|
||
T2="$TMP/b/settings.json"; mkdir -p "$TMP/b"
|
||
cat > "$T2" <<'JSON'
|
||
{
|
||
"extraKnownMarketplaces": {"inkstone": {"source": {"source": "git", "url": "https://git.uncle6.me/inkstone/ISEP.git"}}},
|
||
"enabledPlugins": {"isep@inkstone": true},
|
||
"permissions": {
|
||
"allow": ["Bash(npx wrangler secret:*)", "Bash(python3 scripts/ticket *)"],
|
||
"deny": ["Edit(//Users/x/wrangler.toml)"]
|
||
},
|
||
"hooks": {"PreToolUse": []}
|
||
}
|
||
JSON
|
||
OUT=$(python3 "$S" --target "$T2" --home /Users/youlinhsieh 2>&1)
|
||
python3 - "$T2" "$N" <<'PY' && ok "C1 原有的 allow/deny/marketplace/hooks 全在,原本那條沒重複,只補了缺的" || bad "C1 動到了別人的東西:$(cat "$T2")"
|
||
import json, sys
|
||
d = json.load(open(sys.argv[1])); n = int(sys.argv[2])
|
||
a = d["permissions"]["allow"]
|
||
assert a[0] == "Bash(npx wrangler secret:*)" and a[1] == "Bash(python3 scripts/ticket *)", a[:2]
|
||
assert a.count("Bash(python3 scripts/ticket *)") == 1
|
||
assert len(a) == 2 + n - 1, (len(a), n)
|
||
assert d["permissions"]["deny"] == ["Edit(//Users/x/wrangler.toml)"]
|
||
assert d["enabledPlugins"] == {"isep@inkstone": True}
|
||
assert d["hooks"] == {"PreToolUse": []}
|
||
assert any("/Users/youlinhsieh/.claude/plugins/cache/inkstone/isep/*/scripts/mainline" in x for x in a)
|
||
PY
|
||
printf '%s' "$OUT" | grep -q "+$((N-1)) 條" && ok "C2 印出來的是「+$((N-1)) 條」(缺幾條補幾條)" || bad "C2 計數不對:$OUT"
|
||
|
||
echo "── D. --check/--dry-run ──"
|
||
python3 "$S" --target "$T" --home /root --check >/dev/null 2>&1 && ok "D1 --check:全齊 ⇒ 離開碼 0" || bad "D1 --check 全齊卻非 0"
|
||
T3="$TMP/c/settings.json"; mkdir -p "$TMP/c"; echo '{}' > "$T3"
|
||
python3 "$S" --target "$T3" --home /root --check >/dev/null 2>&1; RC=$?
|
||
[ "$RC" = 1 ] && ok "D2 --check:缺 ⇒ 離開碼 1(驗收用)" || bad "D2 --check 缺卻回 $RC"
|
||
[ "$(cat "$T3")" = "{}" ] && ok "D3 --check 不寫檔" || bad "D3 --check 寫了檔"
|
||
python3 "$S" --target "$T3" --home /root --dry-run >/dev/null 2>&1
|
||
[ "$(cat "$T3")" = "{}" ] && ok "D4 --dry-run 不寫檔" || bad "D4 --dry-run 寫了檔"
|
||
|
||
echo "── E. 壞掉的目標:不碰、不炸(這支掛在 SessionStart)──"
|
||
T4="$TMP/d/settings.json"; mkdir -p "$TMP/d"; printf '{ not json' > "$T4"
|
||
OUT=$(python3 "$S" --target "$T4" --home /root 2>&1); RC=$?
|
||
[ "$RC" = 0 ] && [ "$(cat "$T4")" = "{ not json" ] && printf '%s' "$OUT" | grep -q '沒有動它' \
|
||
&& ok "E1 目標不是 JSON ⇒ 原封不動、講出來、離開碼 0" || bad "E1 rc=$RC 內容=$(cat "$T4") out=$OUT"
|
||
python3 "$S" --target "$T4" --home /root --check >/dev/null 2>&1; [ $? = 1 ] && ok "E2 同一個檔用 --check 問 ⇒ 離開碼 1(不裝沒事)" || bad "E2 --check 對壞檔回 0"
|
||
|
||
echo "── F. 接線:SessionStart 有掛、雲端 setup script 有叫 ──"
|
||
python3 - <<'PY' && ok "F1 hooks.json 的 SessionStart 註冊了 scripts/settings-allow-sync" || bad "F1 hooks.json 沒掛"
|
||
import json
|
||
d = json.load(open("hooks/hooks.json"))
|
||
cmds = [h["command"] for g in d["hooks"]["SessionStart"] for h in g["hooks"]]
|
||
assert any("scripts/settings-allow-sync" in c for c in cmds), cmds
|
||
PY
|
||
grep -q 'settings-allow-sync' docs/cloud-setup-script.sh && ok "F2 docs/cloud-setup-script.sh 裝完 plugin 會跑它" || bad "F2 setup script 沒叫它"
|
||
grep -q 'exit 0$' docs/cloud-setup-script.sh && ok "F3 setup script 仍然 exit 0(非零會讓整個 session 開不起來)" || bad "F3 setup script 結尾不是 exit 0"
|
||
|
||
echo; echo "$PASS/$((PASS+FAIL)) 通過"
|
||
[ "$FAIL" -eq 0 ]
|