Files
ISEP/hooks/credential-only-guard.sh
Leo c2638668e3 ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
              「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
                你自己可以 dogfooding」

搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。

51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。

尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:41:46 +08:00

117 lines
5.8 KiB
Bash
Executable File
Raw Permalink 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
# PreToolUse hook — 金鑰只能走 credential 中心,禁止寫在外面(L3 硬攔截)
#
# 【leo 2026-07-29 立】原話:
# 「那就很簡單,禁止把 secrets 寫在外面,不給 AI 寫入欄位,凡是要 secrets 就要去統一中心拿」
#
# 為什麼存在(同日事故實錄):
# 同一批 workflow 裡**兩種金鑰寫法並存**——
# {{credential.gemini_api_key}} 8 處(正解,走 credential 中心)
# __KBDB_TOKEN__ 17 處 / __GITEA_TOKEN__ 6 處 / __GEMINI_API_KEY__ 1 處(佔位符替換,違規)
# 連 Gemini 同一把金鑰都同時有兩種寫法。兩套並存必然漂移:
# t145 就是漂移的結果——kbdb 身上舊 token、workflow 帶新 token
# 萃取切出的 5 個三元組全部 401 寫不進去,且「指紋一致→永遠跳過」自我保護、重裝幾次都修不好。
#
# 病根不是「這次寫錯」,是「AI 天然偏向新增一種做法,而非找出既有做法」。
# 靠紀律會漂移,靠機械閘不會 ⇒ 讓「繞開 credential」在寫入那一刻就失敗。
#
# 掛在 settings.json 的 PreToolUsematcher: Write|Edit|MultiEdit)。
# stdin 收到 JSON{ tool_name, tool_input: { file_path, content?, new_string? } }
#
# 誠實限制(抄 wiki-secret-scan 的自我要求):regex 偵測有偽陰/偽陽。
# 擋的是「明顯的金鑰佔位符被寫進 workflow/設定」,擋不了刻意改名混淆的繞道。
# 價值是「讓正解成為阻力最小的路 + 留痕可審」,不是技術防偽。絕不聲稱不可能繞過。
set -euo pipefail
INPUT=$(cat)
# ── 解析 file_path 與要寫入的內容(優先 jq,無 jq 退回 grep)─────────────
if command -v jq >/dev/null 2>&1; then
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
CONTENT=$(printf '%s' "$INPUT" | jq -r '[.tool_input.content, .tool_input.new_string] | map(select(. != null)) | join("\n")')
else
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
CONTENT=$(printf '%s' "$INPUT")
fi
[ -z "${FILE_PATH:-}" ] && exit 0
[ -z "${CONTENT:-}" ] && exit 0
# ── 只管「會被執行的產物」:workflow 定義、安裝器、部署設定 ──────────────
# 文件/wiki/落帳談論這些字串是正常的(本檔自己就寫滿了),不能擋。
case "$FILE_PATH" in
*.md|*/wiki/*|*/docs/*|*/mistakes*|*/CRITICAL-PATH*) exit 0 ;;
esac
case "$FILE_PATH" in
*workflow*|*.yaml|*.yml|*workflows.json|*installer*|*worker.js|*wrangler*) ;;
*) exit 0 ;;
esac
# ── 豁免:該行標了 credential-ok 就放行(給真有理由的例外,留痕可審)────
SCAN=$(printf '%s' "$CONTENT" | grep -v 'credential-ok' || true)
[ -z "$SCAN" ] && exit 0
# ── 違規特徵:金鑰類佔位符(__XXX_TOKEN__ / __XXX_KEY__ / __XXX_SECRET__)──
# 非金鑰的佔位符(__KBDB_BASE__、__CODE_URL__、__NAMESPACE__…)是正常的,不擋。
HITS=$(printf '%s' "$SCAN" \
| grep -oE '__[A-Z0-9_]*(TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL)[A-Z0-9_]*__' \
| sort -u || true)
# ── 違規特徵②:真身被寫進定義(leo:「只能拿 key,要送出時自動去拉 value」)──
# AI 只准碰名字;值在 WASM 執行前由 resolve_credentials 回填(graph-executor.ts:247)。
# 抓「Bearer <一長串>」「api_key: <一長串>」這類把值直接寫死的形態。
# {{credential.X}} 與 ${...} 這類引用不算值,先剔除再掃。
LITERAL=$(printf '%s' "$SCAN" \
| grep -vE '\{\{ *credential\.[a-z0-9_]+ *\}\}' \
| grep -oiE '(bearer|api[-_]?key|token|secret|password)"?[:= ]+"?[A-Za-z0-9_\-]{24,}' \
| sort -u || true)
if [ -n "$LITERAL" ]; then
{
echo "🔒 credential 鐵律攔截②:金鑰真身被寫進定義(leo 2026-07-29 立)"
echo ""
echo " 偵測到疑似金鑰值(已遮蔽尾段):"
printf '%s\n' "$LITERAL" | sed -E 's/(.{20}).*/ \1…(已遮蔽)/'
echo " 檔案:$FILE_PATH"
echo ""
echo "【鐵律】「只能拿 key,要送出時自動去拉 value」"
echo " ⇒ 定義裡只准出現**名字**,真身永遠不落在 workflow/設定上。"
echo ""
echo "【正解】{{credential.<名字>}}——執行前由 resolve_credentials 回填"
echo " graph-executor.ts:247WASM 執行前才解析)。"
echo ""
echo "【為什麼】值不落地,AI 讀 workflow 也讀不到真身;"
echo " 換金鑰只改中心一處,不必回頭改每個引用點(副本歸零=不可能漂移)。"
echo ""
echo "【誤判】若這串不是金鑰(如 hash/id),該行尾加 credential-ok 豁免。"
} >&2
exit 2
fi
if [ -n "$HITS" ]; then
{
echo "🔒 credential 鐵律攔截(leo 2026-07-29 立)"
echo ""
echo " 偵測到金鑰被寫在 credential 中心之外:"
printf ' %s\n' $HITS
echo " 檔案:$FILE_PATH"
echo ""
echo "【鐵律】「禁止把 secrets 寫在外面,不給 AI 寫入欄位,"
echo " 凡是要 secrets 就要去統一中心拿」"
echo ""
echo "【正解】用 credential 引用,執行時才解析真身:"
echo " Authorization: \"Bearer {{credential.kbdb_internal_token}}\""
echo " 同批 workflow 已有正解範例可抄:{{credential.gemini_api_key}}"
echo ""
echo "【為什麼】兩套寫法並存必然漂移。t145 實錄:kbdb 身上舊 token、"
echo " workflow 帶新 token ⇒ 萃取切出的 5 個三元組全 401 寫不進去,"
echo " 且指紋一致→永遠跳過→重裝幾次都修不好(自我保護的錯誤狀態)。"
echo ""
echo "【真有例外】該行尾加 credential-ok 豁免(會留痕,請在 commit 說明理由)。"
} >&2
exit 2
fi
exit 0