Commit trust, with nothing in the repo
A signature says who signed. It cannot say whether they are still allowed to. That answer belongs in one place a community can check and change.
On 4 August, someone took over the GitHub account of the maintainer behind keyv, a widely used JavaScript caching library. They pushed straight to the main branch and cut a release. That same day, the payload reached other packages from the same project, and from there it spread to hundreds more.
The release even passed npm’s provenance check, a signed record of the build that produced it, because that build was genuine.
The code was not.
This week, a large part of the open-source world is in Prague for Linux Plumbers and the Open Source Summit Europe. Most of the people in those rooms maintain something that others depend on, and they make the same judgement many times a day. A commit arrives, and the maintainer has two questions:
- Who wrote this?
- Were they allowed to, right now?
A signature can answer the first. Most projects answer the second with a file in the repository that anyone with write access can edit. A community should be able to see, and withdraw at any moment, who may change its code.
In my last piece, Move the authorisation, not the keys, I wrote about what an AI agent carries when it acts on my behalf. I ended it by saying that an agent’s authority is only as good as the code it runs. More of that code is now written, reviewed or merged with an AI agent somewhere in the loop, so both questions are getting harder to answer.
Getting there meant retiring some of our own work first. I’ll explain that below.
The file every project keeps
Projects that check signatures at all usually keep a list of the keys allowed to sign. It might be an allowed-signers file, a keyring, or a list of maintainers in the README. It lives in the repository, it is edited like any other file, and it is only as current as the last person who remembered to update it.
Say a maintainer steps down from a project spread across 40 repositories. Someone now has to edit 40 files, and one missed file is a door left open. Most of us have run that sweep, and wondered afterwards whether we finished it.
The usual answer is to sign more. Since the XZ Utils backdoor in 2024, this community has once again argued about key signing and webs of trust. A signature proves that a key was used. It cannot say whether the person holding that key is still meant to have it, and git itself will let anyone write any name and email address into a commit.
The platform has its own answer: whoever holds an account with write access. That answers a different question. It tells you who could push, not who should. On 4 August, the person who could push was not the maintainer.
It is also getting harder to tell who is on the other end at all. On 17 September, the Rust project warned that attackers were setting up job- and project-related video calls to get maintainers to install malware on their own machines.
The Linux kernel has already drawn its own line on the first question. Its guidance for AI coding assistants is plain: “AI agents MUST NOT add Signed-off-by tags. Only humans can legally certify the Developer Certificate of Origin (DCO).” On 24 September, Sasha Levin proposed adding an AGENTS.md file, the file most AI coding agents load automatically, to point them to that guidance. In his testing, one AI agent added a Signed-off-by tag for the developer on its own.
Others deserve credit, too. Sigstore’s gitsign has made signing far easier, with no long-lived keys to manage. It ties each signature to the signer’s account identity at the moment of signing. And the crates.io team’s warning on 17 September asked every maintainer to verify that multi-factor authentication is enabled.
The kernel already insists that a human is accountable for every change. Commit trust, which we have built in OpenVTC, answers the second question at the moment the commit is checked: it asks the community’s registry whether the signer is still allowed to make this change.
Who may change this code, and who says so right now?
The badge, not the lock
Most of us already use a better pattern every time we walk into an office. When someone leaves a company, their badge stops working. Each door checks one list, and that list is kept in one place. A repository is a building with a lock on every door. Commit trust gives it one list. Who may sign is a grant in the community’s registry, not a file in the repo. A grant can name one repository or a whole organisation, and an organisation-level grant covers every repository in it. Enrolling a contributor is one grant.
An allowed-signers file in every repository, against one grant in the community’s registry.
Two identifiers, no list in the repo
The key that signs my commits lives in my trust agent, the Verifiable Trust Agent (VTA) I described in my last piece. It is never written to my laptop, and it never leaves the trust agent: my laptop sends the commit to be signed, and only the signature comes back. It is the same principle, applied to code: the key stays where it is, and the permission travels.
Trust agent is a confusing name, because it competes with “AI agent”. I’d like a better one, and Trust Over IP is working on it. Until it settles, when I mean an AI agent, I will say so.
Each commit names its signer in a signed line at the end of the commit message, using a decentralised identifier (DID): a public identifier that the signer controls, with no platform in between. When a pull request arrives, a check runs over every commit in it, with no signing tools and no build toolchain. For each commit, it does three things:
- Resolve the signer. Does that identifier publish the key that signed this commit?
- Check the signature. Is it valid for this commit?
- Ask the registry. The community’s trust registry is asked one question: may this signer commit to this repository, right now?
Three checks, and it fails closed at every one. Because the signer is an identifier rather than an email address, a contributor can rotate their key without anyone having to edit a list. The identifier publishes the new key, and the next commit resolves to it.
Even the platform’s shortcuts are held to the rule. An edit made in GitHub’s web editor and a commit from Dependabot must be signed the same way. The only exemption is a clean merge commit signed by GitHub’s own key, and only when every commit beneath it passes.
Trusted or exempt commits pass. Anything else fails: an unsigned commit, an unknown key, a signer the registry does not authorise, or a registry that cannot be reached. If the registry is down, the check fails, and the pull request waits.
Say a pull request carries 12 commits, and one of them was signed on a laptop that the contributor never enrolled. The check fails, and that one commit is the reason. Eleven good commits do not carry a bad one through.
Two identifiers on the command line. No list of signers in the repository. One identifies the registry. The other is the community’s own DID, held by its Verifiable Trust Community (VTC).
The whole configuration: two identifiers on the command line.
The commit check: resolve the signer, check the signature, ask the registry, fail closed.
The verifier cannot sign. It never opens a session with a trust agent and never touches a keyring, so a pull-request run has no signing capability at all. If the continuous integration job that runs the check is compromised, there is no signing key in it to steal. There is no registry address to configure either. The check finds it in the registry’s own identifier document, and uses the best transport both sides support: the Trust Spanning Protocol (TSP), then DIDComm, a messaging protocol built on DIDs, then plain HTTPS.
What a passing check proves
What a passing check proves is narrow on purpose. It says the commit was signed by someone this community allows to commit here, right now. It cannot say the code is good, and it would not have stopped XZ Utils, where the attacker spent years earning the right to commit. Nor can it say that the device that asked for the signature was clean. That is why the trust agent can be told to ask again before it signs: a second factor from me, or approval from another of my devices or another person, for each signature. It is opt-in, and each community decides what it will accept.
A stolen password should not be enough
Go back to 4 August. Would commit trust alone have stopped it? Not on its own, and no single check should carry the weight of a stolen account. Multi-factor authentication on the account is the first layer. Commit trust adds layers that the account does not control. The key that signs lives in my trust agent, out of my GitHub account’s reach, so taking over the account does not hand over the signature. And the right to commit lives in the community’s registry, which accepts only signed changes from an administrator acting for that community, so nobody can grant it by editing a file in the repository. For critical software, a community can also require several people to review and approve a change before it goes in.
Layers against a stolen account: the account, the key, the grant, and several people approving.
Membership does the rest. When a community grants someone the right to commit, the registry records it. When that is revoked, the record is kept for audit.
Revoking a contributor is one registry operation, not a sweep of every repository’s allowed-signers file.
It also changes what an approval means. With commit trust, a green tick on a pull request means every change in it was signed by someone the community recognises, today. I would rather not learn who could commit to my project from an incident report.
We were part of the fragmentation
In my first piece in this series, Show me the proof, not the promise, I argued that our field keeps fragmenting into more standards and implementations rather than aligning on a common set. We were part of that. Trust Tasks, which I introduced in that piece, are the named, versioned operations our stack exchanges, published as specifications in a shared public registry under Trust Over IP. For a while, the Verifiable Trust Infrastructure (VTI), which I helped build, also published its own namespace, a private set of the same operations, alongside it. All 66 task versions in that namespace are now retired, each pointing to its replacement in the shared public registry. Anyone can publish a registry. Migrating your production system to it and deprecating your own namespace are the parts that cost something. Moving was not free. It meant changing what every service in our stack sends and expects, and making breaking changes to our own integrations on the way. Keeping our namespace and calling it a standard would have been easier.
66 task versions retired from our own namespace in favour of the shared registry.
The trust registry that answers the commit check has moved on, too. A change merged on 25 September makes it refuse any grant written under an authority the writer cannot act under, and record every write, including those it refuses. By my own test, I owe you one more admission. We moved our own system onto the shared task registry, but we have not yet switched commit trust on for OpenVTC’s own repositories. OpenVTC is an LF Decentralized Trust Labs project, and we are working with them to turn it on. Until then, judge commit trust by its code, which is public.
Shared under whose rules?
A registry that decides who may commit raises a fair question, and this audience will ask it first: shared under whose rules? Part of the answer is already in the design. Each community runs its own registry, under its own authority, and decides who administers it and who it grants the right to commit. We don’t decide who any community lets commit. The next step is richer rules that each community writes for itself: who may join, which roles carry the right to commit, and who may revoke it. Those belong in open specifications, not in our code. The Trust Tasks specifications are developed in the open, in a task force of Trust Over IP’s Decentralized Trust Graph Working Group, and are versioned publicly. The process for deciding on changes is still being firmed up. Others are already building on the same foundations, such as Harvard’s Applied Technology Lab (ATL), whose Keyring app builds on the same Decentralized Trust Graph credential specifications.
Thank you to the people building OpenVTC with us, including Drummond Reed, Geoff Turk, Brendan Miller, Alberto Leon and Sankarshan Mukhopadhyay.
The wider lesson
A signature tells you who signed. Whether they are still allowed is the community’s call, and that answer belongs in one place that the community can check and change. Code is only the first place this matters. Swap the commit for a scientific dataset, a news article or a professional sign-off, and the two questions are the same: who made this, and were they allowed to? Any community can make its membership mean something that others can check. Every project should be able to say who can commit before something goes wrong. If you maintain something others depend on, try one exercise this week. List everyone who could get a commit into your main branch tomorrow, and how long it would take to remove each of them. If you are in Prague this week, I would love to chat about how your project handles that. I am running a hands-on OpenVTC workshop at Linux Plumbers, giving a talk with a live demo there on Wednesday morning, and presenting again at Open Source Summit Europe on Friday. Nobody should have to wonder who changed the code, or who allowed it.
Where this stands: the signer and the commit check are open source in OpenVTC’s verifiable-git-infrastructure repository; the trust registry is open source; and the Trust Tasks specifications are working drafts that will continue to change. The place to start is openvtc.net.
Sources
- Socket, “keyv and cacheable compromise”, 4 August 2026. socket.dev/supply-chain-attacks/keyv-and-cacheable-compromise
- Quincy Castro (Chainguard), “The keyv and cacheable npm Supply Chain Attack: Inside the Mini Shai-Hulud Campaign”, 4 August 2026.
- Adam Harvey for the crates.io team and security response working group, “Be alert: targeted attacks on prominent Rustaceans”, Rust Blog, 17 September 2026. blog.rust-lang.org/2026/09/17/targeted-attacks/
- The Linux kernel documentation, “AI Coding Assistants”. docs.kernel.org/process/coding-assistants.html
- Sasha Levin, “[PATCH] docs: add AGENTS.md as a symlink to README”, linux-kernel mailing list, 24 September 2026. lore.kernel.org/lkml/20260924134945.3095661-1-sashal@kernel.org
- Sigstore, Gitsign documentation. docs.sigstore.dev/cosign/signing/gitsign
- CVE-2024-3094, the XZ Utils backdoor, disclosed 29 March 2024.
- Glenn Gore, “The Verifiable Trust Stack”, VTI workshop, OpenID Foundation Digital Credentials Protocols working group, Geneva, 31 August 2026 (slides 24 and 43).
- OpenVTC, verifiable-git-infrastructure. github.com/OpenVTC/verifiable-git-infrastructure
- Affinidi trust registry, pull request #140, “authenticate, bind and audit registry writes; remove tr-admin/1.0”, merged 25 September 2026. github.com/affinidi/affinidi-trust-registry-rs/pull/140
- Trust Over IP, Trust Tasks registry. trusttasks.org
- Trust Over IP Decentralized Trust Graph working group, “Terminology proposal: trust agents”, discussion #34, 30 September 2026.
- Glenn Gore, “Show me the proof, not the promise”, LinkedIn, 14 September 2026. linkedin.com/pulse/show-me-proof-promise-glenn-gore-bhqbf/
- Glenn Gore, “Move the authorisation, not the keys”, LinkedIn, 30 September 2026. linkedin.com/pulse/move-authorisation-keys-glenn-gore-81oqf/