Skip to content

Add a CI check on PR commit author identity #2891

Closed as not planned
Closed as not planned

Description

@erikdarlingdata

The gap

Nothing in CI checks the author identity of a PR's commits. This repo has one intended commit
identity — the maintainer's GitHub users.noreply.github.com address — and a commit authored under
any other address would land on dev with all six checks green, because no check looks at
authorship.

Commit metadata is effectively permanent: once a commit is on dev, changing its author needs a
history rewrite, which invalidates every existing clone and every open PR's merge base. So this is a
class where prevention is the only affordable control, and the prevention is cheap.

Why the existing guards don't cover it

  • FleetIdentifierScrubTests scans tracked source. Commit metadata is not a file it can see.
  • check-pr-branch.yml already does exactly this genre of work — a cheap check over PR metadata
    rather than content — but only asserts that PRs to main come from dev.
  • The review bot reads the diff, not the authorship.

So the check has an obvious home and no home currently claims it.

Suggested shape

Add a step to the existing check-branches job in .github/workflows/check-pr-branch.yml. It
already runs on pull_request_target for opened/reopened/synchronize/edited, which is the
right trigger set, and pull_request_target deliberately does not check out PR head — so use the
API rather than a checkout:

GET /repos/{owner}/{repo}/pulls/{number}/commits

and fail if any commit's commit.author.email or commit.committer.email is not on an allowlist.
Notes for whoever picks this up:

  • Check both author and committer. A rebase or an amend can leave one correct and the other
    not; the two are independent fields.
  • Allowlist, don't pattern-match loosely. *@users.noreply.github.com is broad enough to admit
    any GitHub account's noreply address, including one belonging to a different identity. Pin the
    specific address(es) intended for this repo.
  • web-flow@github.com is GitHub's own committer for merge commits and web-UI edits. It will
    appear legitimately and needs allowlisting, or the check fails on every merge commit.
  • Paginate. The endpoint caps at 250 commits per PR, far above anything here, but the default
    page size is 30 — without pagination a long PR silently passes on its unread tail. That is the same
    trust-a-truncated-result failure shape this repo has hit elsewhere.
  • Emit ::error:: naming the short SHA and which field was wrong, so the fix is obvious without a
    local bisect.

A local pre-commit hook would catch it earlier, but hooks are per-clone and unenforceable, so the
CI check is the load-bearing one. A hook is a nice-to-have on top, not a substitute.

Activity

  1. changed the title [-]CI does not check PR commit author identity, so a commit authored under the wrong address lands green[/-] [+]Add a CI check on PR commit author identity[/+] on Sep 4, 2026
  2. erikdarlingdata commented on Sep 4, 2026

    @erikdarlingdata
    OwnerAuthor

    Not building this. The check is disproportionate to the rule, and it does not enforce what the issue implied it would.

    Built to completion, reviewed, and green (PR #2904), then closed unmerged. Recording why, because the reasoning is more useful than the code was.

    It does not enforce identity

    commit.author.email and commit.committer.email are self-declared and unauthenticated. Anyone who can push can set user.email to an allowlisted value and pass. So this check cannot enforce who authors a commit — it can only notice a misconfigured clone. That is a real but narrow thing, and the issue above (which I wrote) framed it as an identity control. That framing was wrong.

    The implementation cost turned out to be much larger than "a step in an existing job"

    What it actually required:

    • A ~180-line script, because pull_request_target runs the base branch's copy of the workflow, so the check cannot run on its own PR and needs to be independently testable.
    • Dependabot allowlisted, or the check reds every Monday — .github/dependabot.yml opens grouped PRs against dev weekly and authors those commits itself.
    • A pagination coverage-failure path. The issue asserted the 250-commit cap was "far above anything here." It is not: this repo has PRs at 926, 607, 536, 452, 446, 304, 304 and 250 commits. Without handling it, a long PR silently passes on its unread tail.
    • An independent head-SHA anchor, because declared and the commit list are same-family endpoints that can agree while both describing the previous head. Not theoretical — immediately after a push, GET /pulls/... served the stale headRefOid for a while.
    • Every fork PR red by design. Nothing exempts forks, so any outside contribution fails the check.

    Two factual errors in this issue's own design, found by building it

    Worth recording so they are not repeated:

    1. web-flow@github.com is not this repository's GitHub committer. It appears zero times. noreply@github.com is — 429 occurrences across 1,464 commits, covering every GitHub-created merge and squash commit. An allowlist built to this issue's specification would have failed a completely clean PR on 101 of its commits.
    2. The 250-commit cap is reachable here, as above.

    Where that leaves it

    The convention stands as a convention: commits to this repo use the maintainer's users.noreply.github.com address, and git config user.email is checked before committing. That is a rule to follow, not a rule to police — and the machinery required to police it costs more than the rule does, while still only catching the accidental case.

    Closing as not planned. The work is preserved in the closed PR if the trade ever looks different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions