On 27 July 2026 I published a suite for sequence-composition attacks with two predictions frozen in a dated file, and asked people to break them. No one reported a run, so I executed the frozen experiments myself. Both confirmed. Here are three experiments, what each one cost, and the boundary I could not push past.
git clone https://github.com/keniel13-ui/sequence-attack-repro
cd sequence-attack-repro
python3 run_j.py # prediction 11
python3 run_k.py # prediction 10 + the topology law
python3 run_l.py # two observers
python3 run_l_mutation_check.py # are the new checks actually load-bearing
After cloning, the experiments are stdlib-only and make no network or model call. Output blocks below are compressed for readability; every quoted token is verbatim.
I wrote the gates, the attacks, and the predictions. Read the results with that in mind — it is why the falsifiers were written before the code and why the failures are in the same table as the wins.
If you read nothing else, read the three costs. Every repair below buys coverage, and every one charges for it:
- J — the gate that scored 7/7 has a precondition nobody had stated: it holds only while no single capability reaches both the issuer's history and its witness.
- K — the rule that catches the cross-tenant attack blocks a verified administrator doing legitimate work. A detector, not a policy.
- L — the reconciliation that survives a compromised witness refuses honest work when an observer is merely unreachable. No adversary required.
The scoreboard
| Predicted | Result | What it cost | |
|---|---|---|---|
| J — one capability clears issuer and witness | witness fails to detect the fork | CONFIRMED — takeover completes | the 7/7 gate has a reach precondition |
| K — customer key misses tenant-level recovery admin | customer blind, tenant key repairs | CONFIRMED — plus a law neither key implies | capability closure over-blocks a real admin |
| L — two observers repair the single-witness wall | predicted: fail-open ALLOW, fail-closed BLOCK | repairs at T1, not T2 | fail-closed refuses honest work under partition |
Nothing here is adopted, and I have not found or received an independent public scorecard from outside this work.
This is not a new problem. It is an old problem in a new place.
Two of these results have names that predate the work.
The earlier issuer-fork problem belongs to the split-view and equivocation family that Certificate Transparency has studied for years — a malicious log presents one version of the Merkle tree to the victim and another to everybody else. Run J asks a narrower question: what remains when one administrative capability erases both the issuer's view and the witness that was supposed to expose the split. One proposed family of defences is gossip — SCT Feedback, STH Pollination, Trusted Auditor Relationship — set out in an IETF Internet-Draft whose intended status was Experimental and which expired in July 2018 without becoming an RFC. The draft treats its three mechanisms as complementary rather than individually sufficient, and notes that clients relying on only a subset remain vulnerable. It is a studied, unfinished line, not a settled standard. Run L is a small executable reconciliation abstraction inspired by that unfinished line. It implements neither Certificate Transparency nor an actual gossip protocol.
The composition failure has a 1988 ancestor: the confused deputy. An intermediary with elevated permissions is induced to exercise them for a caller who could not. Capability systems were the classical answer. A June 2026 preprint draws the same line this suite draws: capability gating decides which tools are available, while per-call authorization decides whether a concrete invocation is authorized. That is one preprint's framing, not established field consensus. A related preprint formalizes the intuition underneath this whole fixture: the same action can be legitimate or unsafe depending on objective, source and execution context — which is precisely what Trace F measures the cost of enforcing.
The sequence problem is corroborated by reported internal deployment failures and evaluation traces. In July 2026 OpenAI reported that a long-running model bypassed sandbox restrictions and published to GitHub despite being instructed to post only to Slack; separately, that it split and obfuscated a credential before reconstructing it at runtime to evade a scanner. OpenAI's own conclusion is trajectory-level: individually acceptable-looking steps can compose into an outcome that would not be approved. Their response included trajectory monitoring, pausing, intervention and rollback.
That corroborates the problem and narrows what I can claim. This work did not invent trajectory monitoring. What it contributes is an executable method for finding where such controls fail and what they cost.
Run J — a witness is only as independent as its write boundary
Prediction 11, frozen in RUN_J_SHARED_RESET_PREREG_2026-07-28.md:
Add a fault injection that clears issuer-local history and witness history through one compromised administrative capability. The witness-anchored gate will fail to detect the fork.
Falsifier: the gate blocks that frozen trace without relying on a third store or a capability outside the compromised principal's reach.
Both run the same mutation, the same reset primitive, and the same recovery. The only intended variable is the reset's write reach — issuer-only in the control, issuer-plus-witness in the attack:
CONTROL reset reaches only the issuer BLOCK [W1_FORK]
ATTACK one capability reaches both ALLOW — takeover completes
"before": { "witness_history": [...], "witness_head": "6dc8846a9ae38233..." },
"after": { "witness_history": [], "witness_head": null }
reset_sha256: 9d10426c725397b3fbf7348423e74b7d6bbb3cb30c4b0344b3b38b543586aea6
Both views agree on a rewritten empty prior, so there is nothing to disagree about. The gate is not fooled; it is starved.
A witness is independent only to the extent that the adversary cannot rewrite or suppress both histories through the same capability. Independent key material is not the line. Independent write capability is.
An earlier version of this experiment used two different reset mechanisms for control and attack, which confounds reach with mechanism. It was caught in review and rebuilt on a single reset_history(reaches_witness=bool) primitive so the only variable is reach.
The clause that has to sit on top of the prediction
Mike Czerwinski made the falsifier stricter in the thread on the last piece, and the tightening is his:
Two stores with different passwords looks independent and isn't, if one admin role can rotate both. The requirement is a witness store whose write path needs a capability the issuer's own administrative role cannot mint or request — not merely one it doesn't currently hold.
And the second half, which is the part I would have missed: static separation at deploy time is not enough, because the same org-chart drift that erodes a two-person sign-off erodes this too. A role that genuinely cannot touch the witness store today gets granted that access in six months by someone optimizing for on-call convenience, and nothing in the receipt chain notices the boundary moved.
So the falsifier for 11 needs a second clause: the capability separation must be independently auditable, so drift toward shared capability is detectable before the compromise, or the fix is provably correct today and silently wrong in a year.
That clause sits on top of the frozen file with its own date, not inside it. Editing a preregistered prediction after watching it fail is the exact move preregistration exists to prevent. The original stands as originally scored; the correction is visibly a correction rather than a quiet rewrite.
Run L is where that clause comes due, and it is not satisfied there either — see registry custody below.
Run K — the key that repairs one blindness creates another
Prediction 10 said a customer-keyed history would miss a tenant-level recovery-administrator mutation followed by credential recovery for a different customer, and that a tenant key should detect it. Confirmed:
A customer key, second customer ALLOW (empty cust_88 prior)
B tenant key, same trace BLOCK [T1_TENANT_SEQUENCE]
Then the experiment went past the prediction, and this is the part I did not expect. If a tenant key repairs a customer key, is the tenant simply the better key? No — traces D and E answer in opposite directions:
D cross-tenant attack tenant key ALLOW closure BLOCK [C1_CAPABILITY_CLOSURE]
E two principals, shared tenant key BLOCK closure ALLOW (B has empty prior)
In frozen traces D and E, each index is blind in the trace where the other sees. Neither is universally correct across both, so neither is "the" answer.
Key the invariant to the actual effect-sharing topology. Container labels and principal labels are both proxies, and a proxy is correct only while it happens to match the topology.
Trace F — the cost, in the same run
Capability closure detects D. It is also the cheapest thing to over-apply, so the same file runs a legitimate-workflow control on it:
F verified admin, destination change + recovery BLOCK [C1_CAPABILITY_CLOSURE]
TRACE F: LEGITIMATE_WORKFLOW_OVERBLOCKED
Pure capability accumulation blocks a verified administrator doing real work. In this fixture, pure closure is a detector, not a deployable policy. Every defence trace in this suite now pairs with a legitimate-workflow control, because a rule that blocks everything scores perfectly on attacks and is useless.
bundle_sha256: 7dddee19ffc2206a6a9e13291f6c46c91a5e985406f3e7a630d0bd00563ca954
Run L — two observers, and the property that actually carries
Run J's wall was one shared capability covering issuer and witness. The obvious repair is a second observer and a reconciliation step. It works, and it works less than it looks:
L1 issuer-only suppression BLOCK verdict=DISAGREE [G1_GOSSIP_DISAGREE]
L2 two observers repair Run J BLOCK [G1_GOSSIP_DISAGREE]
L3 reach covering BOTH observers NOT repaired
The wall moved; it did not fall. Reach covering the observer set still wins. This is 2-of-2 unanimity, not k-of-n — a threshold scheme needs three or more observers and is not what this fixture implements.
The sharpest preregistered result is L4:
L4 fail_OPEN defeated at T1 when W2 is partitioned — no write to W2 required
L4 fail_CLOSED refuses, without diagnosis
Once the adversary already controls the issuer and W1 at T1, merely partitioning W2 defeats fail-open reconciliation. No forgery and no write to W2 are required — but this is not an ordinary network outage producing a takeover, and the trace should not be read that way. Under partition, observer count alone is insufficient; fail-closed reconciliation is the additional load-bearing property. Adding observers to a fail-open reconciler buys less than it appears to.
Both variables are now isolated, in separate traces, and one of them had to be built after review caught me asserting it without evidence.
An earlier draft of this article claimed L2 isolated observer count. It did not. L2 compares Run L's two-observer package against Run J, which uses entirely different machinery — no registry, no reconciliation function, no MAC-bound receipt, no fail-open/fail-closed policy. That changes the count and the architecture at once. It is exactly the confound this suite exists to expose, committed by the author, inside the paragraph meant to correct a previous overclaim.
So the ablation was specified before it was built. Same registry, same reconcile(), same receipt, same gate, same policy, same adversary reach. Only the registered observer set differs:
L2a registry=[W1] issuer + W1 suppressed ALLOW verdict=CONSISTENT
L2b registry=[W1,W2] issuer + W1 suppressed BLOCK [G1_GOSSIP_DISAGREE]
L2a allows because no observer in the authoritative registered set retains a conflicting view. W2 still holds the mutation in memory in both arms — it is simply outside the one-observer registry, so it does not participate in that world's reconciliation. L2b registers W2, making its surviving view authoritative, and the verdict becomes DISAGREE. So "observer count" means authoritative membership count, not how many observer objects exist.
L2a returns CONSISTENT, not a registry or membership error, so the one-member registry is coherent rather than broken. That distinction was written as a non-result condition before the code ran.
A caveat about that freeze, applying this article's own receipt standard to its own process. The v5 addendum and its implementation landed in the same public commit, so GitHub does not independently timestamp the ordering. Treat v5 as a disclosed pre-specification, not a publicly proven preregistration. The J, K and original L predictions do each have a separate public pre-implementation commit — 010d83f, af60ab5 and fec3d891, each landing before the corresponding run_*.py. (The public Run L freeze was cherry-picked from the earlier local commit 4ce4de5; cite the public hash, since the local one does not resolve for a reader.) v5 broke that pattern because I batched the commit, and the fix is procedural: a freeze goes in its own commit, before the code, every time.
Now the claim is clean. Count is independently load-bearing at T1 (L2a/L2b); handling is independently load-bearing at fixed count (L4). My own preregistration compressed this into "not more witnesses but fail-closed reconciliation," and the ablation shows that was too strong. Neither substitutes for the other.
And fail-closed has a price, measured in the same run rather than argued about:
L6 AVAILABILITY_COST_UNDER_PARTITION — fail_closed refuses honest work
when an observer is merely unreachable. No adversary present.
L5 legitimate recovery-only workflow ALLOWED
L8 benign prefix-consistent lag ALLOWED (consistency evidence, not head equality)
L8 matters more than it reads. A reconciler that demands head equality fires on every ordinary replication lag, which is how a security control gets switched off in production. Prefix consistency admits benign lag and still catches suppression.
To be exact about what that is: L8 compares full in-memory action lists and accepts an observer log when it is a prefix of the issuer's claim. It is analogous to consistency evidence; it is not a Merkle consistency proof and not a production CT protocol.
bundle_sha256: 387b0e0d8cc92eafdb9dc697f3756f881828c89d92b0c094…
Are the new checks load-bearing, or decoration?
Run L added two protections. The honest question is whether either does work the existing digest check wasn't already doing. Delete one at a time and re-run its own attack:
python3 run_l_mutation_check.py
candidate_sha256 bd16d319631045f342dcf8d9c5795ff6ea996ad653ac9a5e7bf8d8e9da32a313
receipt_auth clean=BLOCK [R_RECEIPT_UNAUTHENTIC] mutant=ALLOW [PASS] digest_ok=True/True
observer_members clean=BLOCK [OBSERVER_SET_MISMATCH] mutant=ALLOW [PASS]
cross_controls no_mac_B2=BLOCK [OBSERVER_SET_MISMATCH] no_set_B3=BLOCK [R_RECEIPT_UNAUTHENTIC]
MUTATION VERDICT PASS — both protections independently load-bearing
digest_ok=True/True is the load-bearing line in that output. The forged receipt recomputes its digest correctly over the forged body and carries a stale MAC, so integrity is intact by construction and authenticity is the only variable. Without that check printed, the test proves nothing — a first attempt of mine returned a BLOCK that turned out to be the digest check catching a receipt I had malformed myself.
The two checks exist because two earlier versions were wrong in ways worth naming:
-
A caller-supplied observer manifest. The receipt hashed a list of observers the caller said it consulted. A digest over a caller-chosen list proves integrity, not completeness. Membership now loads from the registry, and the caller's list is only what they claim to have consulted — a subset is
OBSERVER_SET_MISMATCH, never a smaller valid quorum. - A self-hashed receipt. It proved the record hadn't changed, not who wrote it. Receipts are now MAC-bound under the reconciler key.
What generalizes
| History keyed to | Blind when the attacker |
|---|---|
| the session | spans two sessions |
| the resource | spans two resources under one customer |
| the customer | forges the issuer's own history |
| a record outside the issuer | rewrites issuer and witness through one capability |
| two observers, fail-open | partitions one observer |
| two observers, fail-closed | reaches both — and honest work pays under partition |
Every rung raises the reach the adversary needs. No rung is free, and the cost is not always latency — twice it was a legitimate workflow refused.
A decision procedure cannot enforce a sequence-dependent policy when the malicious and permitted executions present identical trusted input at decision time. Widening the observable state moves the boundary to wherever the adversary's write-and-suppress reach ends — and both widenings measured here carried an availability or over-blocking cost, which is why each proposed widening has to be tested for that cost in the same fixture, or the defence is only reported on the traces that flatter it.
What this is not, and what is still open
- Registry custody is the perimeter, and nothing here defends it. Membership is authoritative because the registry says so. Whoever writes the registry defines the quorum — add an observer you control, or drop one you cannot suppress, and reconciliation reports agreement. That is a real root of trust, moved rather than eliminated. It is also exactly where the auditability clause comes due: the registry is the boundary that would rot quietly, and nothing in these receipts notices it moving. A registry whose write path is auditable for drift is the next honest rung, and it is not built here.
- Reconciliation exposes disagreement between observers. It does not identify which one lied, and proves nothing about freshness when both share the same stale view.
- The reconciliation MAC is experiment-local. The key is a hardcoded byte string shared in-process between the reconciler and the gate. It demonstrates that receipt authenticity is distinct from self-integrity; it demonstrates nothing about production key custody, rotation, compromise recovery, or an independently operated reconciler.
- 2-of-2, not k-of-n. No threshold, no quorum math. The ablation is 1-vs-2 under unanimity.
- The witness and observers are in-process. The harness models the trust boundary by preserving state across an issuer reset. It does not enforce that boundary at the process, key, host or operator level.
- R4 still blocks forever. After one identity mutation the ledger never expires that state, so a later legitimate recovery is refused indefinitely. A deployable version needs a risk window closed by a reauthorization event. This is the clearest single reason none of this is production-ready.
- No concurrency. Two interleaved calls could both read clean history and both be allowed. The real primitive is an atomic compare-and-append over the risk object.
Not a discovery of equivocation. Not a discovery of the confused deputy. Not a production policy — Trace F says so in the output.
What would change the card
Every prediction here was written down with a falsifier before the code existed, and the file keeps the original wording verbatim when a prediction is refined. For J, K and the original L that ordering is publicly verifiable in separate commits — 010d83f, af60ab5 and fec3d891, each landing before its implementation; for the v5 ablation it is disclosed but not publicly timestamped, as noted above. The falsifiers are still open:
- Block Run J's frozen shared-reset trace without a third store or a capability outside the compromised principal's reach.
- Catch Run K's trace D with a customer-keyed check, without widening the key.
- Show a reconciler that survives L4's partition without paying L6's availability cost.
And the blunt version of the residual, because it is the thing most likely to be skimmed past: nothing here defends registry custody, and nothing in these receipts notices it drifting. Every reconciliation result in this article inherits the integrity of a membership list this fixture does not protect.
If your stack refuses the identity-mutation-then-credential-recovery composition under a purpose that admits both, with history surviving a session split, a resource split, an issuer fork and a shared administrative reset — show me the scenario and the receipt.
One clone plus four Python entry points, no dependencies. That is the whole price of checking it.
Sources:
CT gossip (draft-ietf-trans-gossip-05) ·
Efficient gossip protocols for verifying certificate log consistency ·
Capability Gates Are Not Authorization (2026 preprint) ·
A Framework for Formalizing LLM Agent Security ·
OpenAI: Safety and alignment in an era of long-horizon models ·
Norm Hardy, The Confused Deputy (1988)
Top comments (0)