From 29e3636bd2c319ee2ae70d38a4a3cce6494caafb Mon Sep 17 00:00:00 2001 From: richblack Date: Sat, 20 Jun 2026 12:45:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20wiki=20=E6=A9=9F=E6=95=8F=E9=98=B2?= =?UTF-8?q?=E8=AD=B7=E4=B8=89=E5=B1=A4=EF=BC=88.wikiignore=20+=20=E8=A1=8C?= =?UTF-8?q?=E5=85=A7=E6=A8=99=E8=A8=98=20+=20secret-scan=20hook=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不想被編入 wiki 的內容(密碼/金鑰/個資)三層防線: - L1 .wikiignore:整個機敏檔不編入(glob,像 .gitignore) - L2 行內標記 :檔案內某段不編入 - L3 wiki-secret-scan.sh hook:機敏值真寫進 .claude/wiki/ → exit 2 擋 L3 偵測密碼賦值/PEM 私鑰/AWS·GitHub·Slack·Google·Stripe 金鑰/JWT/ 連線字串帳密/台灣身分證/信用卡號;wiki-secret-ok 行尾標記可豁免誤判。 wiki-init/wiki-capture/SKILL 寫入 L1+L2 協議。 誠實限制:L1/L2 靠 CC 自律、L3 靠 regex(有偽陽/偽陰),減少意外外洩 非保險箱——真正的密鑰本就不該進版控。 Co-Authored-By: Claude Opus 4.8 (1M context) --- skills/llm-wiki/SKILL.md | 20 ++++ template/.claude/commands/wiki-capture.md | 8 ++ template/.claude/commands/wiki-init.md | 13 ++- template/.claude/hooks/wiki-secret-scan.sh | 113 +++++++++++++++++++++ template/.claude/settings.json | 4 + template/.claude/wiki/.wikiignore | 36 +++++++ 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100755 template/.claude/hooks/wiki-secret-scan.sh create mode 100644 template/.claude/wiki/.wikiignore diff --git a/skills/llm-wiki/SKILL.md b/skills/llm-wiki/SKILL.md index c2e292c..492c5f5 100644 --- a/skills/llm-wiki/SKILL.md +++ b/skills/llm-wiki/SKILL.md @@ -57,6 +57,26 @@ description: >- --- +## 機敏內容防護(三層) + +有些內容不該被編入 wiki——密碼、API 金鑰、私鑰、個資。三層防線: + +| 層 | 機制 | 擋什麼 | 性質 | +|----|------|--------|------| +| **L1** | `.claude/wiki/.wikiignore`(glob,像 .gitignore) | 整個機敏檔不編入 | 協議(CC 遵守) | +| **L2** | 行內標記 `` … `` | 檔案內某段不編入 | 協議(CC 遵守) | +| **L3** | `wiki-secret-scan.sh` hook(PreToolUse) | 機敏值真的寫進 wiki → exit 2 擋 | 硬攔截(機械偵測) | + +**CC 的守則**: +1. init/migrate 掃描時,**先套 `.wikiignore`** 再分類;命中的檔案不讀不編入。 +2. 編入任何內容前,自檢有無密碼/金鑰/個資——有就記「位置」不記「值」。 +3. L3 是兜底底線,不是藉口:別把機敏值帶到寫入那一刻才靠 hook 攔。 + +> 誠實限制:L1/L2 靠 CC 自律,L3 靠 regex 特徵(有偽陽/偽陰)。 +> 這是「減少意外外洩」的機制,不是保險箱。真正的密鑰本就不該進版控。 + +--- + ## 流程 A:新專案 Init 1. 建立目錄結構 diff --git a/template/.claude/commands/wiki-capture.md b/template/.claude/commands/wiki-capture.md index 5b00230..fecb6d7 100644 --- a/template/.claude/commands/wiki-capture.md +++ b/template/.claude/commands/wiki-capture.md @@ -7,6 +7,14 @@ ## 執行流程 +### 第零步:機敏檢查(寫入前一律先過) + +把任何內容寫進 wiki 前,先確認**不含**密碼 / API 金鑰 / 私鑰 / 連線字串帳密 / 個資(身分證、信用卡)。 +- 命中 → 不要記「值」,改記「位置」(例:「DB 密碼放 `.env`,不入 wiki」) +- 來源整檔機敏 → 提醒使用者加進 `.claude/wiki/.wikiignore` +- 真要保留示範格式 → 該行尾加 `wiki-secret-ok` 標記 +> 這是協議層自律。最後一道 `wiki-secret-scan.sh` hook 會在寫入 `.claude/wiki/` 時機械攔截,但別依賴它兜底——當場就不要把機敏值帶進來。 + ### 第一步:辨識對話中的可記錄內容 掃描當前對話,找出: diff --git a/template/.claude/commands/wiki-init.md b/template/.claude/commands/wiki-init.md index 4cb7bfd..8512fc9 100644 --- a/template/.claude/commands/wiki-init.md +++ b/template/.claude/commands/wiki-init.md @@ -20,8 +20,17 @@ ### 第二步:已有專案的掃描(已有專案才執行) 1. 遞迴找出所有 `.md` 檔案 -2. 對每個檔案標注建議位置和信心度 -3. 列出清單給使用者確認,**停下來等確認** +2. **先套用 `.claude/wiki/.wikiignore`**:命中 pattern 的檔案整個排除,不讀不編入。 + - 若 `.wikiignore` 不存在,從範本建立一份(預設排除 `.env`/`*.pem`/`*secret*` 等) + - 被排除的檔案在清單裡標「🚫 .wikiignore 排除」,**不可被覆蓋** +3. 對其餘檔案標注建議位置和信心度 +4. 列出清單給使用者確認,**停下來等確認** + +> 機敏防護(三層): +> - **L1 .wikiignore**:整檔排除(這一步) +> - **L2 行內標記**:檔案要編入但某段不要 → 遇到 `` … `` 之間的內容**略過**,只留「(此處機敏,已略過)」 +> - **L3 hook**:萬一機敏值仍被寫進 wiki,`wiki-secret-scan.sh` 會 exit 2 擋下 +> 編入任何檔案前,先檢查是否含密碼/金鑰/個資——有就改記「位置」而非「值」。 分類規則: ``` diff --git a/template/.claude/hooks/wiki-secret-scan.sh b/template/.claude/hooks/wiki-secret-scan.sh new file mode 100755 index 0000000..d0600f8 --- /dev/null +++ b/template/.claude/hooks/wiki-secret-scan.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# PreToolUse hook — 寫入 wiki 前掃機敏資訊(L3 硬攔截) +# +# 為什麼存在:wiki 的 ignore 規則(.wikiignore + 行內標記)是「協議層」,靠 CC 遵守。 +# 但密碼/金鑰/個資外洩是「不可逆」後果——只靠口頭約束太危險。 +# 這支 hook 是機械式底線:CC 真的把機敏資訊寫進 .claude/wiki/ 的那一刻 → exit 2 擋下。 +# +# 掛在 settings.json 的 PreToolUse(matcher: Write|Edit)。 +# stdin 收到 JSON:{ tool_name, tool_input: { file_path, content?, new_string? } } +# 行為:只在目標路徑是 .claude/wiki/** 時啟動,掃要寫入的內容,命中機敏特徵 → exit 2。 +# +# 誠實限制(抄 sdd-guard):regex 偵測有偽陰/偽陽。 +# 擋的是「明顯特徵的機敏字串被自動抄進 wiki」,擋不了刻意混淆/編碼的繞道。 +# 價值是「意外外洩的機械底線 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。 + +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') + # Write 用 content;Edit 用 new_string。兩個都抓,合起來掃。 + 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/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//') + # 無 jq 時內容解析不可靠(JSON 跳脫),退回掃整包 INPUT,寧可多掃不漏掃 + CONTENT="$INPUT" +fi + +# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺) +[ -z "$FILE_PATH" ] && exit 0 + +# 只管寫進 wiki 的動作。其他路徑放行(這支專責 wiki 洩漏,不是全域 secret scanner) +case "$FILE_PATH" in + *.claude/wiki/*) ;; + *) exit 0 ;; +esac + +[ -z "$CONTENT" ] && exit 0 + +# 行內豁免:若該段內容已被標記為刻意保留(例:範例文件要示範格式),略過該行 +# 標記:行尾加 # wiki-secret-ok (或 ) +# 先把標記過的行抽掉再掃。 +SCAN=$(printf '%s' "$CONTENT" | grep -v -E 'wiki-secret-ok' || true) +[ -z "$SCAN" ] && exit 0 + +# ── 機敏特徵 pattern。一行一類,命中即攔。────────────────────────── +# 設計取捨:偏向高訊號 pattern(有明確結構的金鑰/標記),降低偽陽。 +# 純「password=xxx」這類也納入,因為那正是使用者最擔心的場景。 +HITS="" + +check() { + local label="$1" regex="$2" + # -e 讓以 - 開頭的 pattern(如 PEM 的 -----BEGIN)不被當成選項。 + # grep 無命中回傳 1,在 set -e 下會中止 → 用 if 包住吸收掉。 + if printf '%s' "$SCAN" | grep -qiE -e "$regex"; then + HITS="${HITS} + • ${label}" + fi +} + +# 密碼/密鑰賦值(password = ..., secret: ..., api_key=...) +check "密碼/密鑰賦值 (password/secret/api_key/token = ...)" \ + '(pass(word)?|secret|api[_-]?key|access[_-]?key|auth[_-]?token|priv(ate)?[_-]?key)[[:space:]]*[:=][[:space:]]*[^[:space:]<>"'"'"']{6,}' + +# 私鑰 PEM 區塊 +check "私鑰檔內容 (BEGIN ... PRIVATE KEY)" \ + '-----BEGIN[[:space:]].*PRIVATE KEY-----' + +# 常見雲端/服務金鑰前綴 +check "服務金鑰特徵 (AWS/GitHub/Slack/Google/Stripe 等)" \ + '(AKIA[0-9A-Z]{16}|gh[pousr]_[0-9A-Za-z]{20,}|xox[baprs]-[0-9A-Za-z-]{10,}|AIza[0-9A-Za-z_-]{20,}|sk_(live|test)_[0-9A-Za-z]{16,})' + +# JWT +check "JWT token" \ + 'eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}' + +# 連線字串內嵌帳密 (proto://user:pass@host) +check "連線字串內嵌帳密 (proto://user:pass@host)" \ + '[a-z][a-z0-9+.-]*://[^[:space:]:/@]+:[^[:space:]:/@]+@' + +# 台灣身分證字號(個資)。BSD/GNU grep 都支援 ERE,避免 \b(BSD 不認),改用字元類邊界。 +check "台灣身分證字號 (個資)" \ + '(^|[^A-Za-z0-9])[A-Z][12][0-9]{8}([^0-9]|$)' + +# 信用卡號(個資,粗略 13-16 連續數字,可含空格/連字號分隔)。避免 PCRE,用 ERE 近似。 +check "疑似信用卡號 (個資)" \ + '(^|[^0-9])[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{0,4}([^0-9]|$)' + +# Email 不擋(wiki 常需記聯絡人),手機號也不擋(偽陽太高)——刻意留白。 + +if [ -n "$HITS" ]; then + cat >&2 < +# 這幾行不會被編入 wiki +# +# L3 機械底線:萬一機敏值仍被寫進 .claude/wiki/,wiki-secret-scan.sh 會 exit 2 擋下。 +# +# 三層的分工:L1 整檔排除(你主動列)|L2 局部遮蔽(你標記)|L3 兜底攔截(自動掃)。 +# ──────────────────────────────────────────────────── + +# ── 機敏檔案(預設就該排除)────────────────────── +.env +.env.* +*.pem +*.key +*.p12 +*.pfx +*credentials* +*secret* +**/secrets/** + +# ── 個資 / 客戶資料(依專案調整)──────────────── +# customers/** +# *personal-data* + +# ── 草稿 / 暫存(不值得進記憶)────────────────── +# **/draft/** +# *.tmp.md +# SCRATCH.md