Proof-State Sentinel Collapse / Default-Value Authenticity Aliasing
- Name: proof-state sentinel collapse / default-value authenticity aliasing
- Category: exploit pattern / bridge security failure / verifier-state aliasing / trusted-root contamination / initialization-migration failure
- Summary: Some bridge bugs do not forge a real proof, signer set, or authenticated leaf at all. They collapse two verifier meanings into one runtime value:
no proof recorded yet,legacy placeholder,bootstrap committed root, ordefault mapping valuebecomes indistinguishable fromacceptable bridge truth. Once untouched or migration-shaped verifier state can satisfy execution predicates, the bridge stops asking whether a message was actually proven and starts treating absence itself as authorization. Nomad is the canonical case:messages[_messageHash]defaulted tobytes32(0),confirmAt[bytes32(0)]had been pre-approved during initialization, andprocess()therefore accepted unproven messages as if they had already been proven under an acceptable root. - What it is:
- A bridge keeps some explicit or implicit proof-state inventory: roots, headers, proof timestamps, message-to-root mappings, replay bits, or legacy status constants.
- One state value is supposed to mean
unset,none,not proven,legacy, orbootstrap. - A refactor, migration, initializer, or overloaded storage layout lets that same value also participate in
acceptable root,message proven, ormessage executable nowlogic. - The verifier therefore stops requiring an explicit proof record before release and instead lets default or bootstrap-shaped state impersonate authentic bridge truth.
- The exploit does not need a valid proof once the verifier can manufacture authenticity locally from aliased state.
- The sharp structural failure is always the same: the bridge no longer keeps
proof record exists,referenced proof artifact is acceptable, andmessage is executable nowas independently checkable facts.
- Typical preconditions:
- The bridge stores proof state in raw values such as
bytes32,uint256, or enum discriminants without an explicit existence bit or domain separation. - Initialization or migration code pre-approves a committed root, header, or checkpoint using the same value domain that runtime proof state later reads from.
- The execution path checks
is referenced root acceptable?before provingthis message has a recorded proof entry. - Missing map entries, zero values, legacy sentinels, or partially initialized state can still flow into live verifier predicates.
- Operators assume untouched storage is harmless background rather than part of the custody boundary.
- One successful counterfeit execution can immediately reach high-value release or mint paths.
- The bridge stores proof state in raw values such as
- Common vulnerable surfaces:
messageHash -> rootmappings without explicitexistsstate- optimistic root-confirmation tables where
0or1carry both bootstrap and runtime meaning - migrations that reinterpret an old status field as a new root pointer or execution predicate
- initializers that pre-approve roots or checkpoints without asserting they are disjoint from default / sentinel values
- replay or spent flags whose default value can be mistaken for
proven and not spent - bridge executors that gate on
acceptableRoot(root)or similar helpers without first proving a message-specific record exists - repair scripts or rollback procedures that can write bootstrap-looking values back into live verifier state
- Exploit sequence:
- A bridge upgrade, initializer, or storage convention makes a sentinel or default value collide with acceptable proof state.
- The attacker chooses or crafts a message whose verifier lookup returns that aliased value without passing a legitimate
prove()or attestation flow. - The execution path checks only the downstream acceptance predicate and not the separate existence predicate for a proof record.
- The verifier therefore returns success for a message that was never actually proven.
- The bridge processes the forged message and releases or mints real value.
- If calldata is reusable and custody is deep, copycats can repeat the same counterfeit-execution pattern until operators pause the route.
- Variants:
- Bootstrap-root collision — a bridge initializes a committed root or header into a value that untouched message-proof state also returns by default. Nomad is the canonical exemplar.
- Legacy-status reinterpretation — a migration preserves historical sentinel values, then new execution logic treats one of them as acceptable proof state under the new semantics.
- Untouched-slot execution — a missing message entry is not distinguished from a real proof record, so
acceptableRoot(messageRoot)or an equivalent check succeeds on empty storage. - Partially initialized verifier — deployment or recovery scripts leave one verifier table in bootstrap mode while another already serves runtime execution, so absent state inherits live release authority.
Three verifier-state promotions reviewers should not collapse
-
Some verifier-shaped state exists
- A root pointer, proof timestamp, status flag, replay bit, message-to-root entry, or legacy sentinel is readable somewhere in storage.
- That is only the weakest fact:
some state value came back.
-
That state deserves to count as this message’s proof truth
- The harder question is whether the returned value actually means
this exact message has a recorded proof entry under the semantics this route currently trusts, rather thanslot untouched,bootstrap placeholder,legacy leftover, ordefault map value. - Most incidents in this family break here: the bridge promotes absence-shaped or migration-shaped state into
message is proventruth before any real proof artifact exists.
- The harder question is whether the returned value actually means
-
That accepted proof truth deserves execution authority right now
- Even a correctly recorded proof state should still pass route-local execution timing, replay, and bounded-release checks before it reaches live custody.
- This matters because proof-state bugs are usually catastrophic only when one mistaken
provenanswer immediately inherits full release or mint authority.
Healthy review gets materially better once teams stop flattening those three promotions into one vague moment called message proven.
- Detection heuristics:
- Ask separately:
does a proof record for this exact message exist?andis the referenced proof artifact currently acceptable? - Enumerate every storage default returned by verifier maps and ask whether execution would treat any of them as meaningful today.
- Review initializers, migrations, and rollback scripts like custody code, especially any line that pre-approves a root, header, or checkpoint.
- Trace whether
process()/execute()/withdraw()can succeed for untouched message state on a fresh deployment, cloned state snapshot, or rolled-forward upgrade. - Treat zero, empty, legacy, and bootstrap values as adversarial inputs in tests rather than convenience cases.
- Monitor impossible transitions such as
execution succeeded without prior proof write,trusted bootstrap root equals runtime sentinel, ormessage slot absent but acceptable-root check true.
- Ask separately:
- Quick classifier against nearby families:
- Stay here when the decisive failure is that
absence, default, bootstrap, or legacy verifier state itself inherited execution authority. - Use proof-to-message-binding-failure when the trusted root remained real but the verifier no longer proved that the specific attacker-chosen leaf or message actually participated in it.
- Use bridge-verification-failure when the broader lesson is simply that counterfeit bridge truth crossed the verifier, and the narrower state-aliasing mechanics are not the interesting reusable part.
- Use degraded-mode-authenticity-failure when the route becomes unsafe because outage or failover handling broadens bridge truth, not because untouched proof state already counts as authentic.
- Use message-source-authentication-failure when the contract authenticated a message from the wrong emitter or channel even though verifier-state encoding itself remained sound.
- Stay here when the decisive failure is that
What sound design looks like
- One verifier should keep three facts disjoint:
some state value exists,this exact message has a real proof record under current semantics, andthat proven message is executable now. Healthy bridges do not let one raw storage read silently stand in for all three. - Proof existence is explicit. The bridge should store
message has a proof recordseparately from any root, header, or timing metadata instead of inferring existence from a raw value that untouched storage can also return. - Bootstrap trust and runtime proof trust stay disjoint. A committed root or initial checkpoint may be trusted as a starting anchor, but that should not by itself make any unproven message executable.
- Execution eligibility is stricter than root acceptability. A healthy bridge asks
does this exact message have an explicit proof record, is that record bound to an acceptable trusted artifact, and is it executable now? - Migrations prove non-aliasing before they unlock execution. A refactor that changes what one slot means should assert on production-shaped state that old values cannot satisfy new proof predicates.
- Default-shaped state fails closed. Fresh deployments, empty maps, zero-filled slots, and partially initialized replicas should all revert long before any release path comes into view.
- Verifier state is typed for operators too. Manual repair flows, rollback tooling, and emergency scripts should write explicit states rather than raw placeholders that the runtime path may later reinterpret.
- The positive-design companion is bridge proof-state sentinel separation and default-value discipline — this note preserves the negative family; that note is the design checklist for how verifier state should have been represented.
Failure-prevention checklist
- Treat
state value exists,state counts as this message's proof truth, andaccepted proof truth can execute against live custody nowas separate promotions in code, monitoring, and ops; do not let one successful map lookup silently stand in for all three. - Encode proof existence explicitly with an enum, boolean, or structured record; do not infer
provenfrom raw root, timestamp, or status values alone. - Keep trusted-root inventory separate from per-message proof inventory, and require execution to prove membership in both lanes independently.
- Assert at initialization and upgrade time that bootstrap roots, headers, and legacy sentinels cannot equal runtime
unsetornot provenvalues. - Make executable states impossible by default: untouched storage, empty proof records, and zero-valued artifacts should never satisfy release predicates.
- Audit every
acceptableRoot,knownHeader,isValidState, or similar helper for the prerequisitedoes this message-specific record actually exist? - Test fresh deployments, simultaneously deployed source / replica pairs, partially initialized replicas, migrated snapshots, and rollback states as hostile cases.
- Monitor for impossible transitions such as
message executed without prior proof writeortrusted bootstrap root equals verifier sentinel. - Treat deployment, recovery, and repair scripts as bridge-authenticity code. A one-line bootstrap convenience can redefine live custody truth.
- Add bounded-release controls so one verifier-state aliasing bug cannot immediately empty every custody pool.
- Document verifier-state invariants in code comments and runbooks so future cleanup work does not quietly reintroduce aliasing.
Audit prompts
- Which verifier-state promotion is happening at each step here: raw state readability, accepted message-specific proof truth, or live execution authority against custody?
- What exact state proves that this message has a recorded proof, and can untouched storage imitate it?
- Which values can appear in verifier maps on a fresh deployment, and what would execution do if it saw them right now?
- Do initialization or migration paths ever write a trusted root, header, or checkpoint that collides with a default or sentinel value?
- Does the executor prove
message record existsbefore asking whether the referenced root or header is acceptable? - Can old storage written under previous semantics decode into executable proof state after an upgrade?
- Are replay flags, spent markers, or challenge-window fields also vulnerable to default-value aliasing in the same flow?
- Which test demonstrates that an untouched message slot can never satisfy the live execution predicate?
- If responders had to hot-patch verifier storage manually, what invariant stops a repair script from writing values the bridge already interprets as proven?
- What onchain cap or delay contains loss if one aliasing bug still escapes review?
Lessons that should already be standard
A value came back from storage,this message is proven, andthis message may execute noware three different claims.- Default storage is part of bridge security, not neutral background.
Trusted bootstrap root existsis a different claim fromthis exact message was proven.- A bridge should prove proof existence before it proves proof acceptability.
- Raw root pointers are weak proof-state encodings. If
bytes32(0)or one legacy enum variant can still show up through absence, the bridge needs a stronger representation. - Upgrade compatibility is not safety unless old values are shown unable to satisfy new execution predicates.
- Initializers, migrations, and repair scripts deserve the same paranoia as withdrawal logic because they can silently redefine what the verifier treats as truth.
- If untouched storage can sit one branch away from custody release, the bridge is already relying on refactor luck.
Where this pattern stops
-
This pattern stops once a message-specific proof record really exists and the sharper failure is that the verifier no longer binds the attacker-chosen leaf to the trusted root; from there, use proof-to-message-binding-failure.
-
It also stops when the bridge truth widened because degraded observers, failover signers, or emergency modes inherited more authority than intended even though untouched storage itself was not executable; from there, use degraded-mode-authenticity-failure.
-
If the most reusable lesson is simply
the bridge accepted forged authenticity somehow, keep the higher-level framing under bridge-verification-failure and treat state aliasing as incident-specific texture rather than the main pattern. -
High-signal incidents:
- trusted-root failure (2022-08-01) — the canonical default-value aliasing exploit: unproven messages read back as
bytes32(0), bootstrap logic had already madeconfirmAt[bytes32(0)] = 1, andprocess()therefore treated untouched message state as acceptable proof.
- trusted-root failure (2022-08-01) — the canonical default-value aliasing exploit: unproven messages read back as
Internal linkages
- Parent family: bridge-verification-failure.
- Positive-design companion: Bridge proof-state sentinel separation and default-value discipline.
- Adjacent message-binding family: proof-to-message-binding-failure.
- Adjacent failover family: degraded-mode-authenticity-failure.
- Canonical incident: nomad-bridge-invalid-proof-acceptance-2022-08-01.
- Bounded-loss companion: Bridge bounded release design.
- Bridge-truth companion: Bridge truth as a single security object.
Control surface
-
The leverage sits in verifier-state representation: message-proof inventories, trusted-root inventories, initializer writes, migration semantics, replay markers, repair scripts, and the exact execution predicate that converts those states into custody authority.
-
The recurring mistake is not just
zero value bug; it is that the bridge letssome storage value exists,this exact message has proof truth, andthat truth may spend from custody nowcollapse into one overloaded lane. -
The sharp review questions are: which value says
this message was proven, could that same value ever appear without a proof, and what later gate stops one mistakenprovenanswer from immediately becoming full release authority? -
Sources:
-
Last reviewed: 2026-07-29 UTC