Files
patbond-flutter/scripts/check-secrets.sh
T
lixi 18d735cc12
CI / flutter-gates (push) Successful in 3m52s
fix(security): 凭证扫描放行依赖注入传参 + 四仓口径更正
`api_key=volcengine.api_key` 这类**依赖注入传参**被 KEY-ASSIGN 判为疑似凭证。
这是规则的真缺口而非代码问题:行里没有任何秘密,而做 DI 的代码到处都是这种
写法——绕过一次就得在每个调用点绕过,那等于把规则废掉。故改规则。

新增 ALLOW_REFERENCE,刻意收得很紧以免开后门:
- 值必须是**未加引号**的小写起首标识符引用(真密钥要么带引号,要么不是标识符形状)
- 只允许**恰好一个点且随即行尾**
- 连字符不在字符集内

对抗性夹具验证(三真三假):
- 拦下 `api_key = "sk-T3Blbk…"`(OpenAI 形态)
- 拦下 `secret_key: "AKID…"`(腾讯云形态)
- 拦下 `auth_token=eyJ….eyJ….Sfl…`(**三段式 JWT——两个点,故不落进放行**)
- 放行 `api_key=lowercase.reference` / `api_key = settings.api_key` / `api_token: config.authToken`
原 5 形态泄漏夹具复验仍 8 条命中,一条不少。

另更正口径:脚本头与 git-workflow.md 仍写「三仓同构」,实际已是四仓——
patbond-ai 是唯一持有 AI 服务商凭证的仓,漏同步它风险最大。四仓 md5 已一致
(4945940093b4b8413b7d3eec8bf6a5ec),四仓 --all 全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 12:18:18 +08:00

120 lines
5.1 KiB
Bash
Executable File
Raw 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/sh
# check-secrets.sh —— 凭证防泄漏检查(ADR-021,规范见 patbond-doc docs/development/git-workflow.md)
#
# 规则单一来源:本地 pre-commit 与 CI 兜底跑的是同一个脚本、同一张规则表。
# 四仓(patbond-api / patbond-flutter / patbond-doc / patbond-ai)各存一份同构副本,
# 改规则时四仓同步提交。patbond-ai 是唯一持有 AI 服务商凭证的仓,尤其不可漏。
#
# 用法:
# 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}"
# 允许清单:行内出现任一形态即放行(${} 注入、占位值、明显示例值)
# 注:x{3,}(xxx / xxxx…)是本项目文档描述凭证形态时的既有占位约定,
# 放行它使「讲解规则的文档」不会被规则自身拦下(如 iteration-4/08 §漏检清单)。
ALLOW='\$\{[^}]*\}|\{\{[^}]*\}\}|changeme|change[-_]me|your[-_][a-zA-Z0-9_-]+|<[a-zA-Z0-9 ,_.-]+>|placeholder|example|sample|dummy|fake|redacted|\*\*\*|x{3,}'
# 依赖注入的传参不是字面量密钥:`api_key=settings.api_key`、
# `.param("secretKey", props.getSecretKey())` 这类行里没有任何秘密,而做 DI 的
# 代码到处都是这种写法——不放行就得在每个调用点绕过一次。
# 收得很紧,避免把真凭证放进来:
# * 值必须是**未加引号**的小写起首标识符引用(真密钥要么带引号,要么不是标识符形状)
# * 只允许**恰好一个点**且随即行尾(JWT 是 a.b.c 三段,仍会被拦)
# * 连字符不在字符集内(sk-ant-… 这类前缀仍会被拦)
ALLOW_REFERENCE='[:=][[:space:]]*[a-z][a-zA-Z0-9_]*\.[a-z][a-zA-Z0-9_]*[[:space:]]*[,);]?$'
# 内容扫描跳过:本脚本与 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<TAB>大小写旗标(i=忽略大小写,-=敏感)<TAB>文件范围ERE(-=全部文件)<TAB>行模式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,}
AK-ANTHROPIC - - sk-ant-[A-Za-z0-9_-]{16,}
AK-OPENAI - - sk-(proj-|svcacct-)?[A-Za-z0-9]{20,}
MINIO-DEFAULT i - minio[-_.]?admin
PRIVATE-KEY - - ^[[:space:]]*-----BEGIN [A-Z ]*PRIVATE KEY-----[[:space:]]*$
KEY-ASSIGN i - (access[-_]?key(_?id)?|secret[-_]?(access[-_]?)?key|api[-_]?(key|secret|token)|auth[-_]?token)["']?[[: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" |
grep -vE "$ALLOW_REFERENCE" |
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