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, or default mapping value becomes indistinguishable from acceptable 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 to bytes32(0), confirmAt[bytes32(0)] had been pre-approved during initialization, and process() 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, or bootstrap.
    • A refactor, migration, initializer, or overloaded storage layout lets that same value also participate in acceptable root, message proven, or message executable now logic.
    • 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, and message is executable now as 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 proving this 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.
  • Common vulnerable surfaces:
    • messageHash -> root mappings without explicit exists state
    • optimistic root-confirmation tables where 0 or 1 carry 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:
    1. A bridge upgrade, initializer, or storage convention makes a sentinel or default value collide with acceptable proof state.
    2. The attacker chooses or crafts a message whose verifier lookup returns that aliased value without passing a legitimate prove() or attestation flow.
    3. The execution path checks only the downstream acceptance predicate and not the separate existence predicate for a proof record.
    4. The verifier therefore returns success for a message that was never actually proven.
    5. The bridge processes the forged message and releases or mints real value.
    6. 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

  1. 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.
  2. 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 than slot untouched, bootstrap placeholder, legacy leftover, or default map value.
    • Most incidents in this family break here: the bridge promotes absence-shaped or migration-shaped state into message is proven truth before any real proof artifact exists.
  3. 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 proven answer 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? and is 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, or message slot absent but acceptable-root check true.
  • 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.

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, and that 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 record separately 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, and accepted proof truth can execute against live custody now as 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 proven from 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 unset or not proven values.
  • 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 prerequisite does 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 write or trusted 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 exists before 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, and this message may execute now are three different claims.
  • Default storage is part of bridge security, not neutral background.
  • Trusted bootstrap root exists is a different claim from this 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 made confirmAt[bytes32(0)] = 1, and process() therefore treated untouched message state as acceptable proof.

Internal linkages

Control surface