chore: fail CI when a pull request carries agent attribution - #73
Merged
Merged
Conversation
A pull request opened from another machine or tool never runs the vendored commit-msg hook, so it could still carry a Co-Authored-By trailer naming a coding agent, a generated-with banner, or a link back to an agent session. scripts/check-pr-title.ts already reads the title and the commit range for the Conventional Commits check, so the same pass now scans the pull request body and every commit message in the range for those three shapes and names the offending line, the way the title check already names its own failures. A human co-author still passes: the marker list is brand names and known bot accounts, never a bare first name, so Cody, Devin and Jules keep working as people. Adds REQ-012 to the behaviour ledger and raises the coverage ratchet to what the suite now reaches (statements 97.88, branches 90.27, functions 100, lines 97.69); nothing moved down. This does not land as a distinct gate: it is one more step in the existing PR Title Validation workflow, which docs/architecture.md's Gates table already excludes, so that table is unchanged.
CodeQL flagged the session-link regex on both sides. The host list was joined straight into `https?://\S*(?:host1|host2)\S*`, so a host substring could match anywhere in the URL rather than at the actual host: a banned host sitting in another host's path (https://evil.com/claude.ai/x) matched as a spoof, and an unescaped literal dot let a host match one character off (chatXopenai.com for chat.openai.com), a spoof the other way. The pattern now walks a real host boundary: scheme, optional generic subdomain labels, the escaped host, then a lookahead for /, :, whitespace or end of string. That boundary also rejects a host used as a prefix of someone else's domain (claude.ai.evil.com), which the old pattern would have caught by accident and the new one catches on purpose. The ad hoc dot-only escaping is replaced with the standard escape-string-regexp character class, which also escapes a literal backslash. Coverage held at exactly its current values (statements 97.88, branches 90.27, functions 100, lines 97.69); no threshold or README change needed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #66 (parent #21)
What this branch does
CI fails a pull request whose body, or any commit in it, carries agent attribution. The repository refuses such commits on the machine that writes them, but a pull request opened from another machine or by another tool never met that hook.
Three patterns, all beside the title validator in
scripts/check-pr-title.ts, each with its own test:The check runs as one more step in the existing pull request title workflow, which already reads the title and walks the commit range, so it costs one step rather than a new workflow. It reads the whole commit message now (
git log --format=%B) rather than the subject line, since a trailer lives in the body. The body arrives through an environment variable, the same way the title already does. Failures name what was found, where, and what to remove.A human co-author still passes. The check keys on the agent named in the line, never on the presence of a trailer.
The names that are also names
Cody, Devin and Jules are real first names. Matching them as bare words would reject a human contributor, so those three are matched only on the distinctive bot account strings their commits actually carry.
codexstays a bare marker, which is a judgement call worth a second opinion.Ledger and ratchet
REQ-012indocs/requirements.md, count to 12.docs/architecture.mdis unchanged: its Gates table has never included title validation, whose workflow gates nothing and is not among the jobsPipeline Completeneeds, and this lands in that same workflow.Ratchet: statements 97.71 to 97.88, branches 89.78 to 90.27, functions 100, lines 97.51 to 97.69. README's quoted numbers follow, which
lint:docsandREQ-011now require.Two
?? ""capture-group fallbacks became casts. A.+group cannot be empty once matched, and the first line of a message cannot be undefined once the list is filtered non-empty, so neither branch could fire.Verification
Each acceptance criterion run as a real CLI invocation against temporary git repositories:
npm run verifyclean, 234 tests.actionlint1.7.7, the version CI pins, clean.Estate
The ticket asks whether this belongs in
JorisJonkers-dev/github-workflows. It is close, with four things to settle first: the marker list is a judgement made for one repository and deserves estate-level agreement, the step assumes a full-depth checkout so the range is walkable, an estate version wants the markers and session hosts overridable per repository, and this has met exactly one repository's commit history so far.