From 66f983d6805120be48e3a37641357d3878f0d64e Mon Sep 17 00:00:00 2001 From: Lixi20 Date: Tue, 8 Sep 2026 16:28:04 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=87=AD=E8=AF=81=E9=98=B2=E6=B3=84?= =?UTF-8?q?=E6=BC=8F=E6=A3=80=E6=9F=A5=E8=90=BD=E5=9C=B0=E2=80=94=E2=80=94?= =?UTF-8?q?check-secrets=20=E8=84=9A=E6=9C=AC=E5=85=A5=E5=BA=93=20+=20CI?= =?UTF-8?q?=20=E5=85=9C=E5=BA=95=20step=EF=BC=88ADR-021=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 scripts/check-secrets.sh 与 scripts/hooks/pre-commit(三仓同构,规则单一来源) - ci.yml 在 checkout 后新增 Secret scan step(同一脚本 --all),既有 step 未动 - 验收:全仓 234 个已跟踪文件扫描零误报;假凭证自测 9 类命中全拦截 Co-Authored-By: Claude Fable 5 --- .gitea/workflows/ci.yml | 4 ++ scripts/check-secrets.sh | 104 +++++++++++++++++++++++++++++++++++++++ scripts/hooks/pre-commit | 6 +++ 3 files changed, 114 insertions(+) create mode 100755 scripts/check-secrets.sh create mode 100755 scripts/hooks/pre-commit diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index da03c19..5759f74 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -26,6 +26,10 @@ jobs: git remote add origin "$AUTH_URL/${{ github.repository }}.git" git fetch -q --depth 1 origin "+${{ github.ref }}:refs/ci-head" git checkout -q refs/ci-head + # 凭证防泄漏兜底(ADR-021):与本地 pre-commit 同一脚本、同一规则表, + # 扫全部已跟踪文件(覆盖本次 push 变更的超集),纯 shell 零外部依赖。 + - name: Secret scan + run: sh scripts/check-secrets.sh --all - name: Install Flutter (flutter-io.cn mirror, toolcache reuse) run: | FLUTTER_VERSION=3.44.6 diff --git a/scripts/check-secrets.sh b/scripts/check-secrets.sh new file mode 100755 index 0000000..90299d2 --- /dev/null +++ b/scripts/check-secrets.sh @@ -0,0 +1,104 @@ +#!/bin/sh +# check-secrets.sh —— 凭证防泄漏检查(ADR-021,规范见 patbond-doc docs/development/git-workflow.md) +# +# 规则单一来源:本地 pre-commit 与 CI 兜底跑的是同一个脚本、同一张规则表。 +# 三仓(patbond-api / patbond-flutter / patbond-doc)各存一份同构副本,改规则时三仓同步。 +# +# 用法: +# sh scripts/check-secrets.sh --staged # pre-commit:扫暂存区内容(经 scripts/hooks/pre-commit 调用) +# sh scripts/check-secrets.sh --all # CI 兜底 / 手动自查:扫全部已跟踪文件(缺省模式) +# sh scripts/check-secrets.sh <文件...> # 扫指定文件 +# +# 拦下真实凭证时的第一动作:去云控制台轮换/禁用该密钥,然后才是清理提交。 +set -u + +mode="${1:---all}" + +# 允许清单:行内出现任一形态即放行(${} 注入、占位值、明显示例值) +ALLOW='\$\{[^}]*\}|\{\{[^}]*\}\}|changeme|change[-_]me|your[-_][a-zA-Z0-9_-]+|<[a-zA-Z0-9 ,_.-]+>|placeholder|example|sample|dummy|fake|redacted|\*\*\*' + +# 内容扫描跳过:本脚本与 hook 自身(含规则文本,非凭证) +SKIP_PATHS='(^|/)scripts/(check-secrets\.sh|hooks/pre-commit)$' + +# 文件名黑名单:凭证载体文件本体禁止入库(.sample/.example 除外) +DENY_NAME='(^|/)\.env(\.[^/]+)?$|(^|/)credentials[^/]*$|[Aa]ccess[Kk]eys?[^/]*\.csv$|(^|/)rootkey\.csv$' +DENY_NAME_OK='\.(sample|example)$' + +# 规则表:ID大小写旗标(i=忽略大小写,-=敏感)文件范围ERE(-=全部文件)行模式ERE +RULES=$(cat <<'EOF' +AK-AWS - - AKIA[0-9A-Z]{16} +AK-QCLOUD - - AKID[0-9A-Za-z]{16,} +AK-ALIYUN - - LTAI[0-9A-Za-z]{12,} +MINIO-DEFAULT i - minio[-_.]?admin +PRIVATE-KEY - - ^[[:space:]]*-----BEGIN [A-Z ]*PRIVATE KEY-----[[:space:]]*$ +KEY-ASSIGN i - (access[-_]?key(_?id)?|secret[-_]?(access[-_]?)?key)["']?[[:space:]]*[:=][[:space:]]*["']?[A-Za-z0-9+/=_-]{8,} +JWT-SECRET i - (jwt[-_.]?secret|signing[-_]?key|token[-_]?secret|hmac[-_]?(key|secret))["']?[[:space:]]*[:=][[:space:]]*["']?[A-Za-z0-9+/=_-]{8,} +DB-PASSWORD i \.(ya?ml|properties|toml|conf|ini)(\.sample|\.example)?$ (password|passwd|pwd)["']?[[:space:]]*[:=][[:space:]]*["']?[^[:space:]"'$]{6,} +EOF +) + +case "$mode" in +--staged) + files=$(git diff --cached --name-only --diff-filter=ACM) + src=index + ;; +--all) + files=$(git ls-files) + src=worktree + ;; +-*) + echo "用法: $0 [--staged|--all|<文件...>]" >&2 + exit 2 + ;; +*) + files=$(printf '%s\n' "$@") + src=worktree + ;; +esac + +[ -n "$files" ] || exit 0 + +tmp=$(mktemp) || exit 2 +viol=$(mktemp) || exit 2 +trap 'rm -f "$tmp" "$viol"' EXIT + +# 第一道:文件名黑名单 +printf '%s\n' "$files" | grep -E "$DENY_NAME" | grep -vE "$DENY_NAME_OK" | + sed 's/^/[NAME-DENY] /' >>"$viol" || true + +# 第二道:逐文件逐规则内容扫描(二进制文件经 grep -I 自然跳过) +IFS=' +' +for f in $files; do + printf '%s' "$f" | grep -qE "$SKIP_PATHS" && continue + if [ "$src" = index ]; then + git show ":$f" >"$tmp" 2>/dev/null || continue + else + [ -f "$f" ] || continue + cat -- "$f" >"$tmp" + fi + printf '%s\n' "$RULES" | while IFS="$(printf '\t')" read -r id flag scope pat; do + [ -n "$id" ] || continue + if [ "$scope" != "-" ]; then + printf '%s' "$f" | grep -qE "$scope" || continue + fi + ci="" + [ "$flag" = "i" ] && ci="-i" + grep -InE $ci -e "$pat" "$tmp" 2>/dev/null | grep -viE "$ALLOW" | + sed "s|^|[$id] $f:|" >>"$viol" || true + done +done + +if [ -s "$viol" ]; then + echo "凭证防泄漏检查未通过(ADR-021)——以下内容疑似真实凭证:" >&2 + cat "$viol" >&2 + cat >&2 <<'MSG' +处置: + 1. 若是真实凭证:先去云控制台轮换/禁用该密钥,再从提交中移除; + 2. 若是误报:改用 ${} 注入或占位值(changeme / your-xxx / <占位>), + 或与团队确认后调整三仓同构的 scripts/check-secrets.sh 规则表。 +敏感信息只允许存在于被 gitignore 的文件或 .sample 占位中(git-workflow.md)。 +MSG + exit 1 +fi +exit 0 diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100755 index 0000000..dbe22c5 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,6 @@ +#!/bin/sh +# pre-commit —— 凭证防泄漏(ADR-021)。启用(每人每仓一次): +# git config core.hooksPath scripts/hooks +# 注意:core.hooksPath 会整体接管 hooks 目录;本仓无其他自定义 hook。 +repo_root=$(git rev-parse --show-toplevel) || exit 1 +exec sh "$repo_root/scripts/check-secrets.sh" --staged