What happened
On 19 August 2026 Bitcoin Core merged
pull request 35980, a two-commit change to
contrib/verify-commits/verify-commits.py. That script walks the repository's commit
history and checks that every commit back to a configured trusted root was signed by a key
on a trusted list. Two paths through it reached the same successful exit without
establishing anything. The pull request describes the problem in its own words: "a commit
that is an ancestor of a configured root is intentionally accepted without checking earlier
history. The script also takes this success path after Git errors or for divergent commits,
even though neither establishes that relationship." It adds that the issue "was also found
and disclosed responsibly by the Red Team".
What it changes
The script answers one question before a developer builds anything: is the history sitting in this clone the history the maintainers signed? Per-commit signatures answer it commit by commit, and an ancestry test decides where to stop, because commits older than the trusted root are deliberately out of scope.
The stopping rule was the hole. The old code asked git whether the trusted root was an ancestor of the commit under test, and read any non-zero exit as "this commit predates the root, stop here", printing a message and exiting successfully. Git returns 1 for "not an ancestor", which covers a commit that genuinely predates the root and equally covers a commit on a history that shares no ancestry with the root at all. Other exit codes mean git could not answer: a missing object, a broken repository. Those took the same branch.
The change routes both questions through two small helpers in the
current script.
is_ancestor now treats any git exit code other than 0 or 1 as fatal and stops, naming the
two commits it could not compare. predates asks the question in both directions and
returns true only when the commit is provably an ancestor of the root, as its docstring
says: "Return whether commit is provably older than root, rejecting divergent history."
When neither direction holds, it reports that the commit "diverges from the trusted Git
root" and exits with an error. A verification tool that exits successfully when it could
not perform the verification is worse than no tool, because it hands a person a result to
rely on. Both cases ship with reproducers.
What it does not change
This is a check on a clone, run by whoever chooses to run it. The tooling's own README is specific about the order: fetch, verify with a trusted copy of the script, then check out, because "you can't use an untrusted script to verify itself". None of that changed, and a person who never runs the script gains nothing from a better one. It is also not what someone downloading a release checks. That is a different artefact with its own signature.
Nothing here says the flaw was used. The pull request supplies reproducers, which demonstrate the script accepting what it should have rejected. Showing that a lock can be picked is not evidence that a house was entered.
And a passing run still means only what it always meant: these commits carry signatures from keys on this repository's trusted list. Whether those keys belong to the people you think they belong to is the supply chain question underneath, and it sits outside the script, as it always did.
Context
Core has spent years making its own failure modes legible rather than quiet. It began publishing fixed vulnerabilities in July 2024, on the reasoning that nobody can be forced to upgrade, so silence just leaves node operators guessing. The BIPs repository added a route for reporting a flaw in a specification earlier this month. This fix points the same instinct at the tooling: what it removes is not a wrong answer but a confident one.
The Red Team credit in the description is the only trace of how the report arrived. The pull request does not say who they are or what else they looked at, and the repository carries no further record of it.
