Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/scripts/check-no-ai-attribution.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
# actions@github.com emails
# - names containing claude/copilot/devin/aider/codex/gemini
#
# One carve-out, and only from the identity rules: a named dependency bot.
# The policy exists so that a human is not displaced as the author of record,
# and a dependency bump has no human author to displace — Dependabot is not an
# assistant that helped somebody write something, it is the whole author. The
# message rules still apply to its commits in full, so a bot cannot carry an
# AI co-author trailer or a "Generated with" watermark in past this.
#
# It is a hygiene guard, not a security boundary: anyone can set an author
# email locally. What stops a forged one is that it still has to survive
# review and a merge, which is where attribution is actually judged.
#
# Usage:
# .github/scripts/check-no-ai-attribution.sh <base>..<head>
#
Expand All @@ -24,6 +35,12 @@
# git commit --allow-empty -s -m "test: bad commit" \
# -m "Co-Authored-By: Example Bot <example[bot]@users.noreply.github.com>"
# .github/scripts/check-no-ai-attribution.sh main..HEAD # must fail once
# # and the dependency-bot carve-out, which must pass on identity but still
# # fail on a watermark in the message:
# git -c user.name='dependabot[bot]' \
# -c user.email='49699333+dependabot[bot]@users.noreply.github.com' \
# commit --allow-empty -m "build(deps): bump x" -m "Signed-off-by: dependabot[bot] <support@github.com>"
# .github/scripts/check-no-ai-attribution.sh main..HEAD # still just the one failure
# git checkout - && git branch -D scratch/attribution
set -euo pipefail

Expand All @@ -42,6 +59,13 @@ msg_patterns=(
email_pattern='(\[bot\]@users\.noreply\.github\.com$|@noreply\.anthropic\.com$|^actions@github\.com$)'
name_pattern='\b(claude|copilot|devin|aider|codex|gemini)\b'

# Automation identities exempt from the *identity* rules above (never from the
# message rules). Anchored at both ends and matched against the whole
# lowercased address: a substring rule here would be a hole a crafted local
# part could walk through. Matched by name rather than by GitHub's numeric user
# id, so the exemption survives an id change; add another bot by naming it.
trusted_bot_emails='^([0-9]+\+)?(dependabot|dependabot-preview)\[bot\]@users\.noreply\.github\.com$'

fail=0
count=0
while IFS= read -r sha; do
Expand All @@ -67,6 +91,11 @@ while IFS= read -r sha; do
name="$(git log -1 --format='%cn' "$sha")"
email="$(git log -1 --format='%ce' "$sha")"
fi
# Skip the identity rules for a named dependency bot in this role. The
# message rules ran above and applied to this commit like any other.
if printf '%s\n' "$email" | tr '[:upper:]' '[:lower:]' | grep -Eq "$trusted_bot_emails"; then
continue
fi
if printf '%s\n' "$email" | tr '[:upper:]' '[:lower:]' | grep -Eq "$email_pattern"; then
echo "::error::Commit ${sha} ${role} email '${email}' is a bot/vendor identity."
fail=1
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ and says so in its CONTRIBUTING. Use whatever helps.

- no `Co-Authored-By:` trailer naming an assistant, a model, or a vendor,
- no "Generated with …" footer, no robot emoji,
- no bot account as author or committer.
- no bot account as author or committer — save a named dependency bot, which
is exempt from the identity rule only and still checked on its message.

The human who opens the pull request is the author of record and takes
responsibility for the change under the DCO. That is what the sign-off
Expand Down
16 changes: 13 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ agents on the layout, the commands, and the house style.

**AI attribution is not welcome.** No `Co-Authored-By` trailer naming an
assistant, model or vendor; no "Generated with …" footer; no robot emoji; no
bot identity as author or committer. Whoever opens the pull request is the
author of record, takes responsibility under the DCO, and the history should
say so — a tool cannot certify the DCO, which is the whole point of it.
bot identity as author or committer, save the one carve-out below. Whoever
opens the pull request is the author of record, takes responsibility under the
DCO, and the history should say so — a tool cannot certify the DCO, which is
the whole point of it.

This is enforced, not requested: `commit-policy.yml` runs
[`check-no-ai-attribution.sh`](.github/scripts/check-no-ai-attribution.sh) and
Expand All @@ -124,6 +125,15 @@ git push --force-with-lease
repository settings. That is a courtesy; the check in CI is the boundary.
Contributions authored *by* an autonomous account are not accepted.

**One carve-out: a named dependency bot.** Dependabot is exempt from the
*identity* half of the check and from nothing else. The rule exists so that a
human is not displaced as the author of record, and a version bump has no
human to displace — it is not somebody's work with the credit misassigned. The
message rules still apply in full, so a bot cannot carry an AI co-author
trailer, a "Generated with" footer or a robot emoji past the check either.
Adding another bot means naming it in `check-no-ai-attribution.sh`: the
allowlist is a list on purpose, so that widening it is a visible decision.

## 7. PR flow

- Branch from `main`; name branches `feat/…`, `fix/…`, `docs/…`, `ci/…`.
Expand Down
Loading