Files
docker_practice/.github/workflows/identity-guard.yaml
T
yeasy 1e56295251 fix(ci): close two silent false negatives in Identity Guard
An audit found the guard could miss commits it exists to catch.

1. SIGPIPE. Three inner checks used `printf ... | grep -q` under
   `set -euo pipefail`. When grep -q matches early it exits, printf dies of
   SIGPIPE, pipefail turns the pipeline non-zero, the `if` goes false, and the
   offending commit is silently not reported — the guard then prints its
   success line. This file already documents the hazard in a comment and had
   fixed it for the two outer pre-filters; the three inner ones were left as
   pipes. Now here-strings, like the rest.

   Demonstrated on a synthetic revert-style commit (an AI trailer quoted near
   the top, then a 200KB body): the pipe form MISSES it, the here-string form
   detects it. A trailer in the conventional last position happens to survive
   the pipe form, because grep must read to the end before matching — which is
   why this never showed up in practice.

2. `chatgpt` was in BAD_NAME and BAD_ANY but not BAD_TRAILER, so a
   Co-authored-by naming ChatGPT with an email outside anthropic.com/openai.com
   was not caught. Confirmed by test, then added.

Verified before pushing this time: the amended script flags an AI-authored
commit, a large-body early trailer, and a ChatGPT trailer, and scans
claude_guide's full 345-commit history with zero false positives — that repo
has 46 commits with Claude or Anthropic in the subject, none of which are
identity or trailer hits.
2026-07-23 13:13:39 -07:00

138 lines
6.4 KiB
Go
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.
name: Identity Guard
# 拒绝把 AI 助手写成提交作者/提交者或写进 Co-authored-by 尾注
# 本地 .git/hooks 不随仓库分发云端会话推上来的提交不受其保护
# 因此这道检查必须放在服务端
on:
push:
# 'branches: [**]' 覆盖所有分支且天然排除 tag
# 注意不能写成只有 tags-ignoreGitHub 的规则是"只给了 tag 过滤器就只跑 tag"
# 于是 tags-ignore: ['**'] 等于"只跑 tag,但忽略所有 tag" —— 工作流永不触发
branches: ['**']
pull_request:
workflow_dispatch:
permissions: {}
jobs:
check-commit-identity:
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 10
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_requestbase..head只看本 PR 新增的提交
# 2. push before 指向一个真实存在的提交before..head
# 3. 其余新建分支 before 0force-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|chatgpt)'
# 粗筛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整条管道返回非 0if 就会跳过检查——
# 而且只在输入大到写不进管道缓冲区时才发生是典型的隐性漏判
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 grep -qE "$BAD_EMAIL" <<<"$lmail" \
|| grep -qE "$BAD_NAME" <<<"$lname"; 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 grep -qiE "$BAD_TRAILER" <<<"$body"; 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)."