What a Signed Build Verdict Actually Proves
Most organisations that automate an approval step end up storing the approval as a record. The record says what was approved, when, and against which criteria. Everyone downstream reads it and acts on it, and for a long time nothing goes wrong, because nothing has any reason to write a false one.
That is the part worth examining. The record is trusted because nobody has forged one yet, not because forging one is hard.
Our publishing pipeline produces a verdict file for every piece of media before it can go out. The verdict names the item, lists the gates it passed, and carries a SHA-256 hash of the file’s contents. The hash does real work: it binds the verdict to a specific set of bytes, so a verdict cannot be reused for a different file, and a file edited after approval no longer matches the verdict that approved it.
On 22 September we added something the hash was never able to do.
The gap between integrity and provenance
A content hash answers one question precisely: are these the bytes the verdict describes? It answers a second question not at all: did a gate actually produce this verdict?
Those two questions look adjacent and are not. Consider a verdict file with a correct hash for the file it names, a complete list of gates, and every gate marked pass. Every integrity check passes, because every integrity claim in it is true. The bytes are the bytes. The hash is the hash. What the file does not establish is that any gate ever ran. A person — or a script, or an agent acting on a reasonable-sounding instruction — can compute a SHA-256 of a file in one line and write the surrounding JSON by hand in thirty seconds.
This is the distinction between integrity and provenance. Integrity says the artefact has not changed. Provenance says where the claim about it came from. A hash gives you the first. Only a secret held by the gate can give you the second.
For most of the pipeline’s life that gap was theoretical, and I want to be honest that it stayed theoretical. Nobody forged a verdict. The gap mattered anyway, because automation changes who is in a position to write one. When a human publishes, a hand-written approval requires a person to decide to fake it. When an agent publishes, a hand-written approval requires only that something in a long chain produce a plausible-looking file — under time pressure, after a failure, as a workaround somebody meant to remove later.
What we actually shipped
The verdict now carries a keyed message authentication code alongside the hash. The signature is computed over three things: the content hash, the full gate result set, and the gate revision — an identifier for which version of the rule set produced the verdict. A shared secret, BOOK_VERDICT_SECRET, is the key. It lives in the credentials file at mode 0600 and is not in version control.
Three properties fall out of that construction, and each one closes a specific hole.
A verdict written by hand has no valid signature. Producing one requires the key, and the key is held only by the code path that runs the gates.
The gate results cannot be edited after signing. The signature covers the gate dictionary, not just the hash. Flipping one gate from fail to pass in an otherwise genuine verdict invalidates it.
A verdict cannot outlive the rules that produced it. Because the gate revision is inside the signed message, a verdict signed under an older rule set is detected as stale rather than silently honoured. If we tighten a gate, every verdict signed before the change stops being publishable. That is deliberate, and it is the property people find most uncomfortable.
There is a fourth property, which is a cost rather than a feature: losing the key invalidates every verdict ever signed with it. The affected items must be re-rendered and re-gated, not re-signed. Re-signing an old verdict with a new key would be asserting that a gate ran when no gate ran — exactly the forgery the whole mechanism exists to refuse. A system that lets you quietly re-issue its own approvals has not solved the problem, it has moved it.
We attacked it before we trusted it
The temptation after shipping a security property is to describe it and move on. The description is not evidence. We ran four cases against the finished code:
| Attack | Result |
|---|---|
| Hand-written verdict, correct hash, complete gate dictionary, no signature | Refused |
| Signature minted with a guessed key | Refused |
| Genuine verdict with one gate edited after signing | Refused |
| A real gate run writing a real verdict | Accepted |
That last row matters as much as the three above it. A check that refuses everything is not a check, it is an outage. The fourth case is what distinguishes a working gate from a broken one, and it is the case people forget to run.
★ The pattern worth stealing: a security control is not finished when it is written. It is finished when you have tried to defeat it and can state exactly what you tried. "It should refuse a forged verdict" is a design intent. "A verdict with the correct hash and a full gate dictionary was refused" is a result. Only the second one belongs in a governance conversation.
The part that nearly went wrong
The signing scheme was not written here. It came from a canonical implementation shared across our projects, and our copy was seventeen days behind it — the canonical version had gained signed verdicts on 5 September and our vendored copy had not.
A separate audit noticed the drift and blocked the pipeline, which was correct: running a gate that is missing an upstream fix is worse than running no gate, because the missing fix is invisible and the pass is not. Three long-form videos had been rendered, gated and marked pass by their own uploader, and none of them could publish until the drift was resolved.
The obvious fix was to copy the canonical file over ours. We did that, and it broke things in the other direction.
Our copy had gained capabilities the canonical one did not have: a function defining which gates are required per media form, and an integration with a per-project gate configuration. Canonical had the signing. Ours had the gate selection. A straight copy took the signing and threw away the gate selection — a cross-implementation parity suite that had been passing 23 of 23 fell to 21, then to a state where one side could not name a version at all.
The correct fix was a merge, not a replacement: canonical’s signing spliced into our implementation, keeping our interface. Parity then ran 23 passed, 0 failed, including both directions of the awkward case — a verdict signed by the Python implementation verifying under the JavaScript one, and the reverse.
★ Insight ─────────────────────────────────────
Vendoring makes divergence look one-directional. The natural mental model is “canonical is ahead, we are behind” — and that model says re-vendor. But a vendored copy that gets used accumulates local improvements, so drift runs both ways, and a straight overwrite is a silent downgrade of whatever the local copy learned. The parity suite is what surfaced it. Without a test that exercises both implementations against each other, the overwrite would have looked like a clean upgrade and shipped.
─────────────────────────────────────────────────
Why this is a board-level distinction, not a plumbing one
Strip out the cryptography and the question underneath is about delegated authority.
Every organisation now runs processes where an automated step signs off on something a person used to sign off on: a deployment gate, an invoice matching rule, a compliance check, a content approval. The governance question is not “do we have an approval step.” Almost everyone does. It is:
Can the record of an approval be produced by anything other than the approval itself?
If the answer is yes, then the record is documentation, not control. It is genuinely useful — it tells you what was supposed to happen, and it is what an auditor will read — but it cannot survive an adversarial reading, and it will not survive the specific failure where something upstream writes a plausible record to get past a blockage.
Three questions worth putting to any automated approval in your own business:
- Could a person write this approval record by hand, and would anything downstream notice? If the record is a row in a database or a file in a bucket with no keyed signature, the answer is almost always no, nothing would notice.
- What happens to existing approvals when the rules change? If old approvals stay valid under new rules, you have approvals that nobody has ever assessed against your current standard.
- Has anyone tried to defeat it? Not reviewed the design — tried it, with a forged record, and written down what happened.
We were running a pipeline that passed the first question badly for months. It was not an incident. It was the kind of gap that stays theoretical right up until the day an agent, doing its best with a broken build, writes the file that unblocks itself.
The three videos published the same day the merge landed, after nineteen days sitting rendered on disk. The delay was the system working: a gate refused to certify work it could not prove had been checked, and it held until the proof existed.
This kind of review — where automated approval actually sits in a business process, and whether its records can be forged — is part of the digital governance work we do through Ganda Tech Services and its managed IT division, Cloud Geeks.
Digital Transformation Roadmap 2026
A 12-month framework for Australian SMBs ready to modernise — phases, tools, and milestones.
Almost done
Check your inbox and click the confirmation link to get your download.