c2638668e3
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>
117 lines
5.8 KiB
Bash
Executable File
117 lines
5.8 KiB
Bash
Executable File
#!/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 的 PreToolUse(matcher: 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:247,WASM 執行前才解析)。"
|
||
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
|