Abstract
Human authorization is the load-bearing control in a policy-gated agent system, and it is worth exactly as much as its weakest enforcement point. This report describes the nonce-bound approval token design, a concurrency defect that made single-use enforcement decorative, the atomic rewrite that fixed it, and the testing technique that caught the original guard passing against a mutation.
Summary
A consequential change routes to a named human, who receives a link that authorizes that specific change. Two properties have to hold for that link to mean anything: it must be bound to the exact proposed change, and it must be usable once. The first held. The second did not, and the way it failed is more interesting than the fix.
Context
Approval is the point where a system that merely observes an agent becomes a system that governs one. Everything upstream — classification, diagnosis, test runs — produces a proposal. The approval step is where authority is granted, and it is the step a security reviewer will attack first.
Enforcement therefore cannot live in the interface. A disabled button is a rendering decision; the property has to hold against a caller who never loads the page.
Design
Each approval carries a nonce: 32 random bytes, hex-encoded, as the token’s primary key. The token row records which fix and which incident it authorizes, an expiry, and a consumption timestamp.
Two call paths are deliberately distinct, and the distinction is not cosmetic:
| Path | Method | Behaviour | Why |
|---|---|---|---|
peekApprovalToken | GET | Read-only. Never consumes. | A page refresh or a mail client prefetching the link must not burn a valid approval. |
consumeApprovalToken | POST | Atomic validate-and-consume. | The only path that grants authority, and it grants it once. |
The defect
Validation checked that the token was unused with a SELECT. Consumption set the timestamp with a separate UPDATE, issued much later — after a database round trip to load the fix and its incident, and after the data-class policy check.
Two concurrent posts of the same approval link therefore both passed validation and both proceeded to merge. The architecture documentation claimed a detector would fire on a duplicate approval; it would have fired on nothing, because nothing was preventing the thing it detects.
The fix
Validation and consumption collapse into one statement, with expiry moved into the same predicate — split across statements, expiry was a second race, just a slower one.
UPDATE approval_tokens SET used_at = now()
WHERE token = $1 AND used_at IS NULL AND expires_at > now()
RETURNING fix_id, incident_idCorrectness rests on the UPDATE’s own predicate rather than on an explicit transaction or an advisory lock. Postgres serializes concurrent updates to the same row; the loser re-evaluates used_at IS NULLagainst the winner’s committed result and matches nothing. This holds at READ COMMITTED, which is the default.
Verification
The regression test models what Postgres actually guarantees — that statements serialize individually, not in pairs — rather than asserting the shape of a SQL string. That choice was forced: a string-matching mock cannot distinguish the safe form from the unsafe one, since both contain the same clauses. A 25-way stampede against one token yields exactly one winner.
This technique found more than one paper guard in this codebase. A separate test asserting that project creation persisted a denial policy passed even with the column removed from the insert, because it matched a RETURNINGclause rather than the insert’s column list. Of roughly a dozen regression guards examined, two guarded nothing as first written.
Known limitations
- Scope. This atomic enforcement is in the approvals service, on the token-authenticated path. The equivalent dashboard path is session-authenticated and does not use approval tokens; it is a different mechanism and is not covered by this result.
- One keypair per role, not per person. The approver identity attached to an approval is a string the caller supplies — an email address — not something cryptographically bound to that individual. There is no SSO or OIDC identity binding.
- Key custody is a configuration boundary.The signing keys for the approver and executor roles are encrypted under a different secret than the agent’s, and the wrong secret genuinely cannot decrypt the other domain’s key material. But possession of an environment string is the entire authorization model, with no external check. The accurate statement is that the agent process does not currently hold that credential — never that it cannot obtain it.
Open questions
- What does per-approver identity binding cost in practice? Keyless signing with short-lived, identity-bound certificates removes the long-lived private key entirely, which is the strongest answer to “who could forge an approval” — but it introduces an external dependency on an identity provider at the moment of approval.
- Approval fatigue is the failure mode that breaks human authorization in the field. We have no measurement of it, and a control that is technically sound and reliably rubber-stamped is not a control.