18d735cc12
CI / flutter-gates (push) Successful in 3m52s
`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>
120 lines
5.1 KiB
Bash
Executable File
120 lines
5.1 KiB
Bash
Executable File
#!/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
|