A four-day window in which the verification service signed and returned receipts for evaluations it never performed.
Between 2026-08-21 and 2026-08-25, an outage of AgentOracle's /evaluate verification service caused it to sign and return receipts for evaluations it had never performed. It was discovered 2026-08-25 from the receipts themselves; the signed layer had correctly halted every affected request, and the human-readable body did not disclose that.
Five days undetected is the shape of this incident. Discovery from the receipts themselves — not from a customer complaint, not from a monitoring alert — is what made that window recoverable at all. The signed layer was doing what a signed layer is supposed to do; the presentation next to it was not.
v_recommendation: unverifiable and verdict: halt. No degraded receipt in the entire incident carried verdict: act. The cryptographic layer was right the whole time.verify recommendation. A caller integrating on the body alone had no signal that no verification had occurred. A caller integrating on receipt verification would have halted correctly.17,784 verdicts were issued during the affected window. 17,703 carried a byte-identical canned reason string — 99.54% — every one of them a valid Ed25519 signature over a receipt that correctly derived verdict: halt. The remaining 81 evaluations completed before the balance drained fully; those receipts are unaffected.
The cryptographic layer was right the whole time. The presentation layer understated it. For the full duration of a total upstream verification outage, the signed evidence artifact carried v_verdict: unverifiable and v_gate: halt. The v0.3 receipt derivation reaches act only through an evaluated, adversarially-resistant claim set. A response with no evaluated claims cannot express act in the signed layer, and none did.
If a customer had integrated on receipt verification, we would have nothing to disclose to them beyond the body text they saw next to a correct receipt.
| Commit | When (UTC) | Change |
|---|---|---|
4a8b37177 | 2026-08-27 01:52Z | Handler early-returns HTTP 503 not_evaluated when no source produced a parseable claim set. No confidence, no verdict, no receipt, no cache write. |
4a8b37177 | 2026-08-27 01:52Z | Cache admission gated on evaluation completeness, across both the full-response and per-claim namespaces. |
98b3b7e78 | 2026-08-27 04:41Z | Reverted a co-shipped change that collapsed un_probed_not_cleared into the generic unverifiable. Both halt, but the two states are distinct at the receipt layer. |
c28c5dd6f | 2026-08-28 02:13Z | Recommendation text derives from the caller's threshold instead of a hard-coded 0.80. Present since min_confidence became caller-tunable; unrelated to the outage but the same disclosure-asymmetry class. |
49261a37b | 2026-08-28 03:50Z | The adversarial-flag confidence penalty re-checks the applied threshold, so a flagged response can no longer report act in the body when the receipt-side gate would halt. Same disclosure-asymmetry family as the row above. |
a8ce2a18d | 2026-08-28 18:02Z | Mapping binding derived at boot from the published mapping document; /v1/compose, /v1/v_gate, /v1/sign, and /v1/sign/batch return 503 until their verdict path or auth is completed. See the externally-reported defect below. |
The pre-registration for the benchmark run whose collection window this outage sat inside carries an append-only amendment (2351e29f6) withdrawing the pre-registered headline for the same reason. This note and that amendment describe the same withdrawal.
On 2026-08-28, @giskard09 independently ran the published recompute procedure against /v1/conformance/sample and reported two things (x402-foundation/x402#3234). Both are correct.
First, the sample's v_gate.mapping_hash was a hand-typed placeholder rather than a digest of any mapping document. The envelope's signatures were genuine — both issuers verified, canonical bytes recomputed — so the cryptographic layer faithfully committed to a mapping binding that had never been computed. A relying party following our published procedure would resolve that hash, fail to match it, and correctly halt. This is the same failure class as the first defect above: a genuine signature over content the service never produced.
Second, we found the same hard-coded constant was used by the live POST /v1/compose and POST /v1/v_gate endpoints, both reachable without authentication. It was not confined to a fixture. In the same audit we discovered POST /v1/sign and POST /v1/sign/batch signed caller-supplied bytes under the production issuer key with no authentication — a forgery oracle for this receipt format while it was open.
The same string was reported five weeks earlier. On 2026-07-28 Michael Msebenzi (headlessoracle) reported that the mapping hash carried in our published fixtures was a placeholder that had never resolved. We repaired the fixtures within twenty hours and computed the correct mapping hash on 2026-07-29 — the value the conformance sample carries today. The identical placeholder string remained in the production constant for five more weeks, and the defect above is that same string. The July fix was scoped to the artifact that had been reported rather than to the class, so the correct value existed, published, in one location while the placeholder kept being stamped from another.
Fixed in a8ce2a18d, 2026-08-28 18:02Z UTC. The mapping binding is now derived at boot from the published mapping document, or the process refuses to start. All four endpoints return 503 not_issuing; /v1/sign* require an authorization header, and the composed endpoints will resume issuing only when their verdict path is completed. The private issuer key was never exposed — an oracle that signs what you hand it is not the same as a stolen key — and closing the routes ended the capability.
Bound on the exposure window. Requests to these routes were not counted; the request-tracking helper is called zero times in the composing module. Log retention is roughly one hour. We cannot enumerate what was signed, or by whom, between 2026-06-23 and 2026-08-28. Absence of known forgery is absence of evidence, not evidence of absence, and it must be said that way rather than leaned on.
Alongside the sample defect, @giskard09 also ran the full verification protocol against a genuine /evaluate receipt: cryptographic signature check, mapping-hash match against a SHA-256 he computed himself of the live mapping document, and the Section 4.3 recompute of candidate_recommendation and candidate_gate. All three held. In his own words:
"The recompute requirement stands as demonstrated — we ran the full leg independently (signature, mapping_hash, §4.3 steps 3-6) and it held. The signature-implies-issuance step is a separate claim from that, and it shouldn't carry the same weight until kid separation lands on your side. That's not a knock on the recompute work, it's just a different property being verified."
That split is right, and it names the property still open. Same-kid conflation. The kid ao-composed-2026-06-ed25519-c3abfce3 currently signs both receipts where AgentOracle evaluated the claim (/evaluate) and receipts where the service only signed bytes a caller handed it (/v1/sign*). A relying party cannot distinguish those provenances from a receipt alone.
This is a property of the composed format, not of one operator’s implementation. It is present on both issuers we have checked. @poteshniy independently identified the same conflation on Base-side composed issuance during this review window, and the two of us have converged on a format-level fix rather than two separate operational patches.
The fix is an issuance-path assertion: a role parameter in the JWS protected header naming what the signature attests, checked against a role member on the signing key in the issuer’s published JWKS, so a verifier resolves what a signature attests from the same fetch that already resolves the key. Semantics are additive — a receipt carrying no role resolves to unknown, not to malformed, so every receipt issued before role annotation keeps verifying. unknown is not a pass: it records that the issuance path was not established, and a relying party whose policy requires an established path treats it as its own fail.
Kid separation remains worth doing operationally on both sides as defence in depth, but it is no longer the durable fix — the assertion is, because it survives key rotation and does not require any verifier to carry a mapping table it has to keep current. No date is set here. The old kid stays in JWKS so nothing already issued is invalidated.
/v1/compose, /v1/v_gate, /v1/sign, and /v1/sign/batch routes were not instrumented. We cannot enumerate what was signed under the same kid between 2026-06-23 and 2026-08-28.act verdict. The v0.3 derivation reaches act only through an evaluated, adversarially-resistant claim set, and no degraded request produced one.v_recommendation state rather than compress four states into three prose bands.not_evaluated value at the receipt layer (v0.4 discussion). Non-evaluation currently produces no receipt at all — see 4a8b37177 above. No signed artifact records that the service was consulted and could not answer, so the distinction is absent from the record rather than collapsed within it: an auditor reading the records later cannot separate “asked and unanswerable” from “never asked.”851269789 — before this note published. Discovery of this incident took roughly six hours; A1 is the alarm that would have caught it in one, which is why it went first. A2 (credit balance), A3 (rate-limit and upstream-error) and A4 (response-shape anomaly) remain open.joe@agentoracle.co. Commits 4a8b37177, 98b3b7e78, c28c5dd6f, 49261a37b, a8ce2a18d. External review thread: x402-foundation/x402#3234. Benchmark amendment: agentoracle-benchmark-a-b@2351e29f6.
Corrections to this note are added here, dated. The body above is not edited after publication.