What is a Git ref? A named pointer in a key-value store — a name like refs/heads/main maps to an object hash. Branches and tags are both refs.
Git provides integrity verification given a hash you trust — but no mechanism to establish or maintain that trust.
Compromised CI credential, stolen deploy key, malicious insider
Backdoored commit on main, v2.1.0 tag moved to point at it
Downstream consumers pull "the same version" — silently compromised
Force-push to remove malicious commits, move tag back — repo looks clean, no evidence remains
Once the traces are cleaned up, it's very difficult to detect that anything happened. Git itself has no record that the refs ever pointed anywhere else.
Append an entry:
ref → object hash
Witnesses cosign
the log grew by appending
Anyone walks
the witnessed log
Every logged state must descend from the one before it. History can only move forward.
Once logged, a tag is pinned to its object hash. A second object for the same tag is rejected.
Both invariants are enforced at verification, not by the witness — see slide 8.
A C2SP tlog-checkpoint over the log — not over any one ref:
No ref path, no commit hash, nothing Git-specific — so any tlog-witness implementation can cosign one. The refs live in the log's entries, which stay in the repository.
A commit SHA is the hash of the commit object body:
e4d9a1...
Each parent hash is computed the same way, so every commit hash transitively covers the entire history below it.
Given the raw bytes of a commit object, you can independently compute its hash. No trust in the sender required.
A verifier doesn't need to separately check the tree hash — checking out a commit inherently verifies its tree, since Git resolves every object by its content-addressed hash.
The witness attests append-only growth. verify establishes descent — locally.
Each must descend from the one before — local merge-base calls, nothing to fetch
Commit hashes cover their parents, so the repository can establish descent to itself
A verifier cannot know whether anything checked the log first — an attacker writing to the log ref did not
Best for: witness diversity, automated pipelines
Best for: open-source projects, getting started
Same protocol either way: request and response are HTTP messages, and an issue plus a comment simply replace the POST and its reply.
git-ratchet verify answers: is this ref covered by a witnessed log that only moved forward?
The log's stored checkpoint, from refs/ratchet/log
Log signature and witness cosignatures against the policy (e.g. 2 of 3)
Each state descends from the last; no tag logged at a second object
Live ref is not ahead of what the log covers
No special access needed — just the policy file and a clone of the repo.
Entries appended past the last cosigned checkpoint are not yet witnessed — the next checkpoint covers them.
Every push to main and every v* tag logs the ref, then checkpoints the log → GitHub Issue witness at BenBirt/git-witness
Logging and checkpointing are separate on purpose: the entry is pushed before anyone is asked to cosign it, so a witness being down cannot cost you the record.
actions/setupInstall git-ratchet from a GitHub Release
actions/logOrigin-side: record a ref, push the entry
actions/checkpointOrigin-side: get the log's head cosigned, push it
actions/cosignWitness-side: answer an add-checkpoint request
The witnessed-checkpoint pattern generalises to any mutable pointer → immutable content.
Pin published versions with witness cosignatures — detect silent replacement or modification of packages after publication
Integrate verification into module fetching (e.g. Bazel) — verify witnessed checkpoints at resolution time, before building