A signed Git commit's hash is not a unique fingerprint
Carnegie Mellon research shows a signed Git commit can be re-minted with a different hash but the same 'Verified' badge — no signing key required, no code changed.
A paper posted to arXiv on July 2 by Jacob Ginesin, a PhD student at Carnegie Mellon and cryptographic auditor at Cure53, demonstrates that a signed Git commit’s hash is not a unique fingerprint of that commit. Given any signed commit on GitHub, someone without the signing key can produce a second commit — same files, same author, same date, same “Verified” badge — that resolves to a different SHA-1 hash. GitHub still stamps it verified. Everything a reviewer would visually check matches. The identifier the rest of the toolchain treats as canonical does not.
Ginesin reported the finding to GitHub in March. As of the paper’s publication, neither Git nor any forge had shipped a fix. No CVE has been assigned.
What the badge actually promises
The “Verified” label is intuitive. If a commit is signed by a key you trust, the code you’re reading came from the identity you expected. That much still holds. What the label has never promised — but which much of the ecosystem has quietly begun to assume — is that a specific commit hash names one specific commit. It doesn’t.
Git’s object model derives the commit hash from a header that includes the signature bytes verbatim. Change any byte inside the signature and you change the hash. Change any byte inside the signature without invalidating the signature and you have a distinct commit hash pointing at identical bytes, both verified.
Not a collision — a malleability
This is the piece that reads counterintuitively at first, so it’s worth being careful. This isn’t a SHA-1 collision, and it isn’t a preimage attack. It doesn’t require the signing key. It exploits the fact that most signature formats admit multiple equally-valid encodings of the same underlying signature — a property cryptographers call malleability. Ginesin’s git-chain-malleator demonstrates three classes:
- ECDSA signatures have the classic
(r, s)and(r, n − s)symmetry — either form verifies, and only one is canonical. Git and the forges accept both. - RSA and EdDSA signatures embedded in OpenPGP packets carry an “unhashed subpackets” area whose contents are not covered by the signature itself. Anything can go in there without breaking verification.
- S/MIME signatures rely on DER encoding rules that permit multiple length representations for the same value. Non-canonical lengths verify.
In every case the commit files, the author, the date, and the underlying cryptographic proof are unchanged. What changes is the hash.
What breaks downstream
If a hash is not a unique name for a commit, a growing list of infrastructure that treats it as one is doing something subtly wrong.
- Hash blocklists. A supply-chain incident response that pins malicious commit hashes as an indicator of compromise can be sidestepped by re-minting the same code under fresh hashes. The signature still verifies. The blocklist does not fire.
- Mirror provenance. Two mirrors of the same repository, both showing the same “Verified” author on the same files, can carry different hashes for what is otherwise the same commit. Downstream tooling that pins a hash from one mirror will not find it on the other, or will treat a legitimate mirror as suspicious.
- SBOM and reproducibility keys. Anything that expresses “we shipped exactly this commit” by hash is quietly weakened. The hash still identifies the bytes it names; it does not identify the commit those bytes represent.
Ginesin is careful about what is not broken. The paper does not describe a way to slip different code past a verification check. If you pinned a hash and fetched it, you get the code you expected or you get nothing. That is the assumption the badge was designed around and it still holds. It is the load-bearing assumption people added later — that a verified hash uniquely names a verified commit — that quietly does not.
Where the fix lives
The paper is unambiguous that the flaw is not in the signing algorithms themselves. Canonicalizing signatures before hashing them into the commit is a solved problem in other domains — Bitcoin’s low-S rule has been standard for years, and TLS libraries reject non-canonical DER as a matter of course. What Git and the forges have not done is enforce a canonical form before deciding what “Verified” means. That is where the fix belongs and it is a change a forge can make on its own side, without touching the commit-object format or breaking existing repositories.
None of this is unfamiliar. Signature malleability has been the reason a very long list of things went subtly sideways over the last twenty years, from early Bitcoin transaction pinning to old TLS parsing bugs to a good chunk of the JSON Web Token history. The specific target changes; the mistake does not. Verify the signature, hash the message, forget to require a canonical encoding of the signature between those two steps — and you have handed the ecosystem a second name for something it thought had one.
The system reads “Verified” as an identity claim. It always has. The hash next to it turns out to be a coincidence, not a promise.
Sources
- Jacob Ginesin, “Signature Malleability in Git Commit Objects” — arXiv, July 2, 2026.
- git-chain-malleator — public demonstration tool.
- Ravie Lakshmanan, GitHub ‘Verified’ Commits Can Be Rewritten Into New Hashes Without Breaking Signatures — The Hacker News, July 8, 2026.
Found this useful? Share it.


