You pressed Revoke.
The dashboard said Revoked.
A few moments later, your AI agent still changed a record in the external system.
Did the revocation fail? Did the agent ignore it? Or had the operation already crossed a point where revocation no longer meant cancellation?
Those are three different failure investigations. A green preflight check cannot answer any of them.
The example below is fictional. It describes a failure mode to test, not an incident we have observed in a production ARC deployment.
One operation, two competing events
Consider an agent authorized to change a purchase order in an ERP system.
- The operator approves an exact task and action.
- The agent passes its role/scope/approval preflight checks.
- The gateway checks the approval and prepares an outbound request.
- The operator revokes the approval.
- The gateway forwards the request anyway.
- The ERP accepts the change.
The important question is not simply “Was there approval?” There was.
It is: “At what boundary did that approval become authority to dispatch this exact operation?”
Here is the vulnerable shape in pseudocode:
if (await approvalIsCurrent(operation)) { await prepareRequest(operation); await forwardToERP(operation); } The approval may be current at the check and revoked before forwardToERP executes. This is a time-of-check/time-of-use race (TOCTOU), a class of weakness documented as CWE-367.
The gap is not unique to AI. Agents make it harder to notice because they can queue, delegate, resume, retry, and hand off operations long after an initial approval.
“Revoked before execution” is not yet a specification
Before calling this a security failure, define the revocation contract.
Case A — revocation committed before dispatch admission.
If revocation and admission share an authoritative ordering, the revoked grant must not admit a new protected dispatch. A fail-closed decision and credible no-forward evidence are the expected outcomes.
Case B — revocation committed after dispatch admission.
An operation may already be legitimately admitted or in flight. Revocation does not automatically undo an external write, recall a network packet, or cancel a remote transaction. If the product promises cancellation of not-yet-sent requests, that is an additional contract requiring its own dispatch/cancellation synchronization.
The UI's “Revoked” label is not enough to decide which case occurred. Neither are timestamps from two hosts whose clocks and authority propagation may differ. The evidence must show the relevant ordering inside the authority domain.
This distinction also appears in ordinary token systems. OAuth 2.0 Token Revocation (RFC 7009) explicitly discusses propagation delay between servers. A successful revocation response is not, by itself, a complete proof of every downstream executor's state.
A final recheck helps. It does not make the gap atomic.
One inexpensive improvement is to revalidate immediately before forwarding, against the current authorization state and the exact operation:
- Authenticated actor and task/run identity
- Approved action and immutable request digest
- Destination, effective tool/adapter identity, and scope
- Authorization epoch or revision, expiry, and one-use state
If any required fact is stale, mismatched, or unavailable, refuse the dispatch.
That catches revocations occurring between the original preflight and the last check.
But imagine this interleaving:
Gateway Authority / operator ------- -------------------- Final authorization check: ALLOW Revocation commits Forward request to ERP ERP changes record Moving a check closer to forward narrows this window; it does not eliminate the window. Reporting this sequence as a safety PASS would conceal the part of the system that was never protected.
The stronger design target is a serialized admission/consumption boundary. The authoritative gate orders revocation and admission, checks the current policy/epoch, consumes or reserves the grant, and binds an immutable operation before the local executor can start.
That gives a precise, limited claim: revocations ordered before admission deny admission. It is not the same as atomic execution at a remote ERP. Dispatch can fail after local consumption; an HTTP error can arrive after a remote effect has committed.
Four tests I would require before trusting the claim
You do not need a production ERP to expose the first race. Start with a deterministic scheduler and an instrumented test receiver. Pause execution at named boundaries, then insert a revocation deliberately.
| Fixture | Forced ordering | Expected finding |
|---|---|---|
| R0 — live-grant control | Current grant → admission → send | One admitted attempt, one observed forward; validates that the test does not pass merely by refusing everything |
| R1 — revoke before admission | Preflight → revocation committed → authoritative admission | Admission denied, zero gateway forwards; independent observation required |
| R2 — revoke after final check | Preflight → last non-atomic check → revocation committed → send | Gap exposed, not a security PASS, if the request can still be forwarded |
| R3 — revoke after admission | Serialized admission → revocation committed → local forward | Record an already-admitted/in-flight attempt; do not claim the revocation cancelled it |
For a system that promises cancellation up to physical send, add an explicit fifth test for a queued admitted operation revoked before egress. The admission-only contract does not satisfy that stronger promise.
A small deterministic Node.js fixture can illustrate this ordering in memory. A real acceptance test needs the protected gateway, its authoritative store, and independently observed egress. Simulation is not production verification.
The negative proof needs a real observer
A gateway returning DENY is an assertion about its decision, not proof that no request escaped.
For the no-forward claim, verify all relevant egress paths with an independent test receiver or an equivalently complete, authenticated observer. Correlate observations with the exact logical operation and attempt. An empty ERP row alone is weaker: a request may have been sent and rejected, delayed, or rolled back.
Likewise, HTTP 409, timeout, or a failed client response does not prove that an ERP write did not occur. If the side effect is uncertain, preserve UNKNOWN until reliable reconciliation establishes the outcome. Do not retry solely because the client saw an error.
What I want a receipt to prove
A useful receipt separates the facts that tend to get collapsed into one green status:
| Evidence | Question it answers |
|---|---|
| Approved subject and request digest | Which exact operation was approved? |
| Authority revision and ordered admission/revocation record | Was the grant current at the authoritative gate? |
| One-use consumption and dispatch attempt identity | Which attempt was admitted, and could it be replayed? |
| Independent gateway egress observation | Did a request actually cross the forwarding boundary? |
| Authoritative downstream effect/readback | What changed at the destination, and can it be attributed to this operation? |
| Verdict with policy/fixture revision | Which limited claim do these observations support? |
An illustrative verdict might read:
fixture: R2 observation: final_check=ALLOW; revocation_before_forward=true observation: gateway_forward_count=1 (instrumented receiver) verdict: KNOWN_GAP — no atomic check-to-forward guarantee That is a successful detection test. It is not a successful security control.
A different fixture may legitimately return PASS for “revocation before serialized admission prevents a local start.” It must not silently upgrade that finding into “all in-flight external effects can be cancelled.”
Where Agent Role Contracts fits — and where it does not
I maintain Agent Role Contracts (ARC), an open-source checker for supplied role, task, scope, review, and action declarations.
The published ARC 1.0.1 preflight checks do not authenticate a live operator's revocation, mediate remote network forwarding, or prove ERP side effects. A declaration PASS is not runtime permission. Our integration documentation makes that boundary explicit.
We have proposed an assurance design with an ordered dispatch admission point and one-use local consumption, and we track related research questions around gateway and independently observed ERP effects. These are design and research boundaries, not an end-to-end production revocation guarantee or a completed ERP validation.
The practical goal is to distinguish a valid declaration, a current runtime admission, an observed forward, and an independently verified external effect — without pretending one receipt proves all four.
The question for engineers shipping agents
Suppose the operator revokes an approval after your gateway's final authorization check but before the outbound network call.
What actually stops the send in your system?
A serialized gate? A cancellable dispatch queue? A downstream authorization check? Or nothing, with the race documented as a known gap?
I'd be interested in concrete answers using this format:
authority store | admission point | revocation ordering | no-forward observer | in-flight policy | remaining gap
A system that shows the gap clearly is easier to improve than one that calls an untested window PASS.
Related discussion: Your AI Agent Passed Preflight. Which Receipt Proves the Change Landed?. Technical background: MITRE CWE-367, RFC 7009, and the ARC research issue.
