Sentinel-Lane Separation and Default-Value Deauthorization

  • Name: sentinel-lane separation and default-value deauthorization
  • Category: control-surface reference / sentinel-semantics safety note / default-value discipline / placeholder-authority design companion
  • Summary: Healthy systems do not let absent, uninitialized, placeholder, native-asset alias, unused slot, or failed parse share a runtime value with anything that can authorize minting, withdrawal, ownership, verification, or configuration. Sentinels and defaults are sometimes unavoidable, but they must stay lane-local, typed, and economically inert until a narrower proof upgrades them into live authority. This note exists to preserve the positive-design target that sits underneath Nomad, Qubit, Aztec v1, Revest, and related failures: the system should never be able to confuse nothing meaningful happened yet with this action is now allowed.

Why this note exists

  • Many exploit notes correctly say bytes32(0) was accepted, address(0) doubled as ETH, or a blank slot got reused, but the deeper design lesson is broader than any one bridge or parser bug.
  • The recurring healthy-side question is: how should a protocol represent placeholder and default states so they never inherit authority by accident?
  • Nomad is the verifier-state warning: untouched proof storage and pre-approved root state both resolved to bytes32(0).
  • Qubit is the asset-lane warning: address(0) was both a native-asset placeholder and a value accepted by a generic ERC-20-looking deposit path.
  • Aztec v1 PoC zkDAI is the authorization warning: failed recovery and absent ownership both converged on address(0).
  • Revest Finance is the object-lifecycle warning: a supposedly blank series slot became live authority because the code never proved the destination object was truly unused.
  • These incidents differ in surface area, but they rhyme on one control surface: the system allowed a placeholder-shaped state to cross a trust boundary without first being deauthorized or retyped.

The six joints a healthy sentinel design should keep fused

  1. Default state versus authorized state

    • Untouched storage, parse failure outputs, zero addresses, zero hashes, empty arrays, and uninitialized IDs must never by themselves satisfy authorized, proven, owned, funded, or processable predicates.
    • If absence and authority can share the same raw value, reviewers should assume the boundary is already brittle.
  2. Lane-local meaning

    • A sentinel that is valid in one narrow lane should not leak into broader generic paths.
    • address(0) may be meaningful inside one explicit native-asset branch; that does not make it a valid token contract everywhere else.
  3. Typed upgrade from inert to live

    • Placeholder or bootstrap state should require an explicit transition before it can become executable truth.
    • The next function happened to read the same raw value is not a transition rule.
  4. Negative-result deauthorization

    • Failure outputs should fail closed.
    • If ecrecover fails, if proof lookup misses, if a slot was never initialized, or if parser validation did not complete, the result should become reject, not fall through to a value that some later check may honor.
  5. Object-identity proof before reuse

    • Reusing IDs, slots, resource mappings, or configuration objects must require proof that the target is truly blank, retired, or lane-compatible.
    • Currently reads as default is too weak when a race, prior issuance, or aliasing can make the same value carry hidden meaning.
  6. Sentinel observability and reviewability

    • Operators and auditors should know exactly which sentinels exist, what each one means, and which functions are allowed to consume them.
    • A system with undocumented placeholder semantics is inviting cross-lane authority drift.

What sound design looks like

  • Absence stays absent.
    • Default mapping reads, missing proof records, failed recoveries, and uninitialized object IDs should resolve to explicitly non-authoritative states rather than values that later predicates can reinterpret as success.
  • Sentinels are scoped to one lane only.
    • Native-asset aliases, bootstrap constants, placeholder IDs, and reserved addresses should be accepted only by the narrow code paths that were built for them.
  • Authority requires a positive proof object.
    • A system should not infer authorized from not obviously different from a sentinel; it should require a typed record showing why the action is live.
  • Blank-looking targets are proved blank before reuse.
    • Unused storage slots, resource IDs, series IDs, and verification-state containers need stronger checks than current value equals zero before code repurposes them.
  • Failure outputs are structurally inert.
    • The safest parser, verifier, or recovery API is one whose failure mode cannot be mistaken for a meaningful domain object.
  • Bootstrap and migration states expire cleanly.
    • Special initialization-time values should be impossible to encounter later on the live execution path, or they should remain explicitly non-executable forever.

Useful positive design shapes

1. Typed enums and explicit status objects

  • Prefer Unproven | Proven(root) | Processed, Unset | Active(owner), or equivalent typed representations over overloaded raw bytes32(0) / address(0) / 0 values.
  • The healthy goal is that callers cannot accidentally ask one raw value to answer two different security questions.

2. Lane-specific adapters for sentinels

  • Keep native-asset handling, bootstrap roots, placeholder IDs, and recovery defaults behind narrow adapters that convert them into typed objects before they reach generic business logic.
  • Generic token, verifier, or object-management code should reject those sentinels by default.

3. Explicit isBlank proofs before object reuse

  • Reusing a slot, series, resourceID, verifier cell, or config object should require checks that the object has no prior issuance, no active rights, no historical ownership, and no pending lifecycle edge state.
  • Storage currently equals zero is only one weak signal among many.

4. Fail-closed negative-result APIs

  • APIs for recover, lookup, decode, resolve, and validate should make failure impossible to treat as a valid domain value.
  • Typed failures, reverts, and distinct status codes are safer than return address(0) or return bytes32(0) when the rest of the system already handles those values elsewhere.

5. Bootstrap-value quarantine

  • Initialization constants, empty-tree roots, temporary placeholders, and migration sentinels should be walled off from live authorization logic.
  • If the system must retain them, keep them in separate fields or namespaces that live runtime checks never treat as proof artifacts.

6. Sentinel inventories in audits and ops

  • Maintain a short inventory of every meaningful zero, sentinel, placeholder, and reserved ID in the system.
  • Reviewers should know each value’s lane, allowed consumers, and deauthorization rule.

Design questions worth asking first

  • Which raw values in this system mean unset, missing, native, failed, bootstrap, unused, or legacy?
  • Can any of those values also satisfy an authorization, verification, ownership, reserve-admission, or object-reuse predicate?
  • Are sentinel values accepted only in one narrow lane, or can generic code paths consume them too?
  • When an API fails, what exact value does it return, and could that value be meaningful somewhere else in the protocol?
  • Before reusing a blank-looking slot or ID, what proves it is truly inert rather than merely currently zero-shaped?
  • Which bootstrap or migration values can still appear on live execution paths after launch, and what makes them harmless there?

Red flags

  • Zero means none here and ETH there, but the code paths are easy to keep straight.
  • A missing proof just returns bytes32(0), which is fine because acceptable roots are checked later.
  • Failed recovery gives address(0); that just means no signer.
  • If the slot still looks empty, we can reuse it.
  • The placeholder is only for initialization / migration when runtime code can still read it later.
  • It is just a sentinel value used as an excuse not to document lane boundaries.
  • The generic path also accepts the special-case alias for convenience.

Failure-prevention checklist

  • Enumerate every sentinel, default, reserved, and placeholder value in the protocol: zero addresses, zero hashes, empty roots, native-asset aliases, blank IDs, failed-parse outputs, and migration constants.
  • Ensure none of those values can directly satisfy authorized, owned, proven, funded, processable, or unused and safe to reuse predicates without an additional positive proof.
  • Keep sentinel acceptance lane-local. Generic token, verifier, and object-management paths should reject values that are only meaningful in specialized branches.
  • Replace ambiguous raw-value APIs with typed status objects or explicit failure channels where practical.
  • Require stronger blankness proofs before object reuse: no prior issuance, no live ownership, no queued lifecycle transitions, no historical mapping that could reactivate rights.
  • Separate bootstrap and migration state from live runtime state. Do not let initialization placeholders sit in the same fields or namespaces later used for ordinary authorization.
  • Make parser, lookup, and recovery failure structurally inert. A negative result should not reuse a domain value that other code can honor.
  • Add tests for default read, failed recovery, sentinel on generic path, bootstrap value on live path, and blank-looking object reuse scenarios.
  • Monitor production for impossible sentinel usage: zero-address assets on generic routes, zero-root proof execution, default-ID reconfiguration, or other placeholder-shaped values crossing high-privilege boundaries.
  • Document each sentinel’s allowed consumers and deauthorization rule so audits and incident response can reason about them quickly.

Audit prompts

  • What values in this codebase are simultaneously used as defaults and as meaningful domain objects?
  • Which generic functions can currently consume zero-address, zero-hash, empty-root, or placeholder-ID inputs?
  • If a lookup or recovery function fails, where does the resulting value flow next, and who might mistake it for authority?
  • Are any bootstrap constants, migration sentinels, or native-asset aliases stored in fields later consulted by ordinary runtime checks?
  • Before a supposedly blank slot or ID is reused, what proves it never previously carried economic or authorization meaning?
  • Can a caller craft calldata so that a special-case sentinel enters a broader path that was never supposed to understand it?
  • Where does the design force a typed transition from inert placeholder to live authority, rather than inferring the upgrade from ambient state?
  • What telemetry would show operators that a placeholder-shaped value just crossed into an authorization path?
  • Are reviews focusing only on business logic while ignoring the parser / recovery / default-value layer that supplies its inputs?
  • If two engineers describe what 0, address(0), or bytes32(0) mean in this protocol, would they give the same answer for every lane?

Lessons that should already be standard

  • Default values are protocol state, not empty air.
  • A sentinel that is safe in one lane is dangerous everywhere else until proven otherwise.
  • Failure outputs must be deauthorized, not merely recognizable to careful humans.
  • Looks blank right now is not enough proof to reuse an object that may have prior issuance or latent rights.
  • Bootstrap convenience becomes runtime authority unless the design explicitly walls it off.
  • Generic code paths should be hostile to special-case aliases, not permissive toward them.
  • Many we forgot a check bugs are actually one raw value was allowed to answer two different security questions bugs.
  • Audits that do not inventory sentinels, defaults, and placeholders are often leaving a real control surface unmodeled.

How to use this note with the exploit corpus

Internal linkages

Control surface