mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-08-10 08:27:25 +00:00
两处都是本地实测发现的,不是推测。 1) 漏判(正确性):尾注预筛写成 `git log --format=%B "$RANGE" | grep -qiE`, 在 `set -o pipefail` 下 grep -q 命中即退出,左侧 git log 收到 SIGPIPE, 整条管道返回非 0,if 判定为"没命中"从而跳过整个尾注检查。 实测:沙箱里 8 条应报错误只报出 6 条,两个带 AI 尾注的提交被静默放过。 改为 here-string(不再有管道),身份预筛同样改掉以杜绝这一类问题。 2) 性能:全量审计路径(新建分支推送、workflow_dispatch)原先为每个提交 各起一个 git 子进程,docker_practice 1,592 个提交实测 50 秒。 现在先用一次 git log 流式预筛,只有命中时才逐个提交定位; 同一仓库实测降到 1 秒,且不改变任何判定结果。 回归验证:被删分支的真实 4 个提交在 push / pull_request / 新建分支三种 形态下仍全部拦下;专门构造的 2,502 提交仓库(身份流 258KB,远超管道缓冲区) 中埋在末尾的 AI 提交能被检出;14 个仓库完整历史仍然零误报; 范围解析失败仍 fail closed,删除分支仍正确放行。
134 lines
6.2 KiB
Go
134 lines
6.2 KiB
Go
name: Identity Guard
|
||
|
||
# 拒绝把 AI 助手写成提交作者/提交者,或写进 Co-authored-by 尾注。
|
||
# 本地 .git/hooks 不随仓库分发,云端会话推上来的提交不受其保护,
|
||
# 因此这道检查必须放在服务端。
|
||
|
||
on:
|
||
push:
|
||
tags-ignore: ['**']
|
||
pull_request:
|
||
workflow_dispatch:
|
||
|
||
permissions: {}
|
||
|
||
jobs:
|
||
check-commit-identity:
|
||
permissions:
|
||
contents: read
|
||
runs-on: ubuntu-24.04
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
fetch-depth: 0
|
||
persist-credentials: false
|
||
- name: Reject AI assistant identities and co-author trailers
|
||
env:
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
BEFORE_SHA: ${{ github.event.before }}
|
||
HEAD_SHA: ${{ github.sha }}
|
||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
ZERO=0000000000000000000000000000000000000000
|
||
US=$'\037' # 用不可能出现在姓名/邮箱里的 unit separator 作分隔符
|
||
|
||
# 确定扫描范围。三种情况:
|
||
# 1. pull_request:base..head,只看本 PR 新增的提交
|
||
# 2. push 且 before 指向一个真实存在的提交:before..head
|
||
# 3. 其余(新建分支 before 全 0、force-push 后 before 不可达、
|
||
# workflow_dispatch):退化为全量审计,扫描 HEAD 的完整历史
|
||
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||
RANGE="${PR_BASE_SHA}..${PR_HEAD_SHA}"
|
||
elif [ "$EVENT_NAME" = "push" ] \
|
||
&& [ -n "${BEFORE_SHA:-}" ] \
|
||
&& [ "$BEFORE_SHA" != "$ZERO" ] \
|
||
&& git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then
|
||
RANGE="${BEFORE_SHA}..${HEAD_SHA}"
|
||
else
|
||
RANGE="$HEAD_SHA"
|
||
fi
|
||
echo "Scanning range: ${RANGE}"
|
||
|
||
# 只有"确实没有 head 提交"(例如删除分支的 push)才允许放行;
|
||
# 其余任何解析不出范围的情况一律 fail closed,不能静默通过。
|
||
if [ -z "${HEAD_SHA:-}" ] || [ "$HEAD_SHA" = "$ZERO" ]; then
|
||
echo "No head commit (branch deletion?); nothing to check."
|
||
exit 0
|
||
fi
|
||
if ! COMMITS="$(git rev-list "$RANGE" 2>&1)"; then
|
||
echo "::error::cannot resolve commit range ${RANGE}: ${COMMITS}"
|
||
exit 1
|
||
fi
|
||
COUNT="$(printf '%s' "$COMMITS" | grep -c . || true)"
|
||
echo "Commits in range: ${COUNT}"
|
||
if [ "$COUNT" -eq 0 ]; then
|
||
echo "No commits to check."
|
||
exit 0
|
||
fi
|
||
|
||
# 身份判定刻意收窄,避免误伤:
|
||
# - 邮箱在 AI 厂商域名下(含子域)
|
||
# - 姓名恰好等于助手名,因此真人 "Claude Dubois" 不受影响
|
||
# 正文里出现 Claude/Anthropic 是完全合法的(本仓库群里有专讲 Claude
|
||
# 的书),所以只检查身份字段与 Co-authored-by 尾注,绝不扫描自由文本。
|
||
BAD_EMAIL='@([a-z0-9.-]+\.)?(anthropic|openai)\.com$'
|
||
BAD_NAME='^(claude([ ._-]?code)?|codex|chatgpt|copilot|anthropic|openai)(\[bot\])?$'
|
||
BAD_TRAILER='^[[:space:]]*co-authored-by:.*(claude|anthropic|codex|copilot|openai)'
|
||
# 粗筛:BAD_EMAIL 与 BAD_NAME 命中的行必然含下列词之一,
|
||
# 因此它是二者的超集,用来跳过绝大多数干净提交,不会漏判。
|
||
BAD_ANY='(claude|anthropic|codex|chatgpt|copilot|openai)'
|
||
|
||
failed=0
|
||
|
||
# 身份检查。命令替换先落地,set -e 才能捕获 git 失败;
|
||
# while 循环用 here-string 喂数据,避免管道子 shell 吞掉 failed 赋值。
|
||
IDENTS="$(git log --format="%H${US}%an${US}%ae${US}%cn${US}%ce" "$RANGE")"
|
||
# 用 here-string 而不是管道:set -o pipefail 下 grep -q 命中即退出会让
|
||
# 左侧进程收到 SIGPIPE,整条管道返回非 0,if 就会跳过检查——
|
||
# 而且只在输入大到写不进管道缓冲区时才发生,是典型的隐性漏判。
|
||
if grep -qiE "$BAD_ANY" <<< "$IDENTS"; then
|
||
while IFS="$US" read -r sha an ae cn ce; do
|
||
[ -n "$sha" ] || continue
|
||
for role in author committer; do
|
||
if [ "$role" = author ]; then name="$an"; mail="$ae"; else name="$cn"; mail="$ce"; fi
|
||
lname="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')"
|
||
lmail="$(printf '%s' "$mail" | tr '[:upper:]' '[:lower:]')"
|
||
if printf '%s' "$lmail" | grep -qE "$BAD_EMAIL" \
|
||
|| printf '%s' "$lname" | grep -qE "$BAD_NAME"; then
|
||
echo "::error::${sha} ${role} identity is an AI assistant: ${name} <${mail}>"
|
||
failed=1
|
||
fi
|
||
done
|
||
done <<< "$IDENTS"
|
||
fi
|
||
|
||
# Co-authored-by 尾注检查(行首锚定并要求冒号,避免匹配正文叙述)。
|
||
# 先用一次 git log 流式扫全部正文;只有确实命中时才逐个提交定位,
|
||
# 否则全量审计要为每个提交起一个 git 子进程(1500+ 提交约 50 秒)。
|
||
BODIES="$(git log --format='%B' "$RANGE")"
|
||
if grep -qiE "$BAD_TRAILER" <<< "$BODIES"; then
|
||
while read -r sha; do
|
||
[ -n "$sha" ] || continue
|
||
body="$(git log -1 --format='%B' "$sha")"
|
||
if printf '%s' "$body" | grep -qiE "$BAD_TRAILER"; then
|
||
echo "::error::${sha} has a Co-authored-by trailer referencing an AI assistant"
|
||
printf '%s' "$body" | grep -iE "$BAD_TRAILER" | sed 's/^/ /'
|
||
failed=1
|
||
fi
|
||
done <<< "$COMMITS"
|
||
fi
|
||
|
||
if [ "$failed" -ne 0 ]; then
|
||
echo ""
|
||
echo "AI assistant attribution found in the commits above."
|
||
echo "Rewrite them before pushing, e.g.:"
|
||
echo " git rebase -i --exec 'git commit --amend --no-edit --reset-author' <base>"
|
||
exit 1
|
||
fi
|
||
|
||
echo "OK: no AI assistant identity or Co-authored-by trailer in ${COUNT} commit(s)."
|