Implementation and Singleton Direct-Call Deauthorization

  • Name: implementation and singleton direct-call deauthorization
  • Category: control-surface reference / upgradeability-and-shared-code safety reference / initialization-and-bricking design note
  • Summary: Healthy proxy, library, and singleton architectures do not treat implementation contracts as passive code blobs by default. They prove something stronger: the shared code object cannot be initialized by strangers, cannot exercise privileged verbs meaningfully when called directly, cannot be destroyed or rebound without crossing an explicit review boundary, and does not remain a single unrecoverable root of trust for every dependent instance. Healthy review also keeps three facts separate instead of flattening them into implementation risk: whether many live children still depend on this exact code address, whether that address still retains standalone authority of its own, and whether that authority can still cash out into fleet-wide liveness or custody power. This note exists to preserve the positive design target that sits underneath Parity’s 2017 library freeze and the later OpenZeppelin UUPS implementation-side bricking near miss.
  • Why this note exists:
    • Many upgradeability failures are described as forgot to initialize the implementation, but the deeper design mistake is broader: the system never made the shared code object safe to exist as a live onchain contract.
    • The real security question is often not did the proxy initialize correctly? but what can happen if someone calls the implementation or singleton itself?
    • The useful design move is to treat shared code as an explicitly deauthorized dependency surface, not as a socially passive component.

The five joints a healthy shared-code dependency should keep fused

  1. Shared-code identity
    • The system should know which implementation, library, beacon, or singleton every dependent instance is actually trusting.
    • This is just code is not a security property; it is only safe if the code object cannot still accumulate separate authority.
  2. Direct-call harmlessness
    • A direct call to the implementation or singleton should either revert, do nothing security-relevant, or write only to deliberately quarantined state that cannot affect dependent instances.
    • If direct calls can still run initialize(), upgradeTo(), kill(), execute(), or equivalent privileged verbs, the dependency is still live root authority.
  3. Authority-location clarity
    • The architecture should make it explicit where ownership, upgrade authority, module-install rights, and kill-switch power actually live.
    • If any of that authority still exists meaningfully on the shared code object itself, the implementation is not passive.
  4. Mutation and destruction bounds
    • The system should know whether the shared code object can change code identity, delegate into attacker-chosen logic, or lose its runtime entirely.
    • selfdestruct, implementation-side upgrade rails, metamorphic deployment paths, and mutable beacon pointers all belong here.
  5. Dependency blast-radius containment
    • If one shared object fails, the architecture should already know how many dependent instances fail with it and whether recovery exists.
    • A dependency that can brick every child instance without relink, migration, or bounded fallback is not just code reuse; it is concentrated custody risk.

What sound design looks like

  • Initialize or disable at deployment time
    • Shared implementations and singleton libraries should be initialized into a sealed state or explicitly disabled the moment they are deployed.
    • We will initialize the proxy later is not enough if the implementation itself still has meaningful first-caller semantics.
  • Direct privileged verbs should fail outside the intended context
    • Upgrade verbs, owner-only setup paths, and destructive maintenance functions should not remain meaningfully callable on the implementation itself.
    • Context guards like onlyProxy, notDelegated, explicit immutable self-address checks, or equivalent patterns should enforce where privileged logic may run.
  • Shared code should not keep accidental admin state
    • If the implementation is not supposed to be an independently administered contract, it should not retain live owner, upgrader, or module-manager semantics that matter when called directly.
    • The safest implementation is often one whose standalone state is either inert or permanently sealed.
  • Bricking surfaces should be removed or heavily contained
    • Destructive verbs like selfdestruct, implementation-side delegatecall upgrade rails, and mutable singleton admin paths should be treated as systemic-risk surfaces, not helper conveniences.
    • If a shared dependency can disappear or swap meaning, every dependent asset path is implicitly trusting that control surface.
  • Recovery should be explicit, not wishful
    • If many instances depend on one shared code object, the architecture should already specify whether relinking, migration, or emergency exit is possible.
    • Users can probably recover later is not a real recovery path.

Three shared-code facts healthy systems should not collapse

  1. Many children may still depend on this exact code object before it holds any standalone admin state
    • Review should begin by asking whether this implementation, library, beacon, or singleton is already part of the fleet’s live execution dependency.
    • It is only shared logic is not a comfort if loss of that one code address still freezes or semantically rewrites many children.
  2. That same shared object may also retain standalone privileged authority of its own
    • Initializers, upgrader state, module-install verbs, direct-call delegatecall gadgets, kill paths, and other lifecycle verbs belong here.
    • This is the boundary where supposedly passive code becomes a separately seizeable control plane.
  3. That retained authority may or may not still translate into fleet-wide consequence
    • The catastrophic version appears when children cannot relink, migrate, or exit after the shared object is captured, destroyed, or redefined.
    • Healthy design keeps separate many children depend on this address, this address still has mutable authority, and that authority can still freeze or rewrite the fleet instead of collapsing them into one vague implementation-hardening checkbox.

Healthy architectural shapes

1. Sealed implementations

  • Deploy the implementation, immediately run _disableInitializers() or an equivalent permanent lock, and make standalone state intentionally useless.
  • If upgrade logic lives on the implementation, add explicit context guards so implementation-side direct calls cannot exercise it.
  • This is the cleanest answer when the proxy is the only place where real application state should ever matter.

2. Explicitly inert shared libraries / singletons

  • If many instances dispatch into one library or singleton, that shared object should behave like immutable infrastructure, not like a latent wallet or admin shell.
  • Remove owner-settable lifecycle verbs when possible; otherwise ensure they cannot be reached or cannot affect the shared dependency in ways dependents cannot survive.
  • The design target is all dependent state trusts this code path without also making this code path has its own seizeable control plane true.

3. Upgrade authority separated from business logic

  • A healthier architecture keeps the review boundary around a dedicated admin or governance surface rather than around direct calls to shared business-logic contracts.
  • If the implementation itself contains the root upgrade primitive, the implementation must be treated as privileged infrastructure rather than passive logic.
  • OpenZeppelin’s UUPS fix is a useful reminder here: once upgrade logic sits on the implementation, implementation-context reachability becomes a first-class threat model item.

4. Shared-dependency blast radius bounded up front

  • If one singleton or beacon fans out across many user instances, plan the failure mode explicitly before launch.
  • Good questions are can children relink?, can they exit without the shared code?, and can one bad dependency event be contained before all instances freeze?
  • Shared code that cannot be replaced, bypassed, or safely retired should be treated like a custody hub, not like a deployment optimization.

Design questions worth asking first

  • Which live children still depend on this exact implementation, library, beacon, or singleton today, even before we ask whether it retains standalone admin state?
  • What meaningful state or privileged verbs still exist when the implementation or library is called directly?
  • Can a stranger initialize the shared code object itself, even if all proxies were initialized correctly?
  • Do upgrade, admin, or destructive verbs enforce the intended execution context, or do they also work on the implementation / singleton itself?
  • Can the shared code object be destroyed, rebound, or delegated through attacker-chosen logic while dependents still trust it?
  • If that shared code object vanished today, which user assets or control surfaces would become unrecoverable?
  • Does the architecture have a relink, migration, or emergency-exit path, or is one shared code address stays alive forever an unstated invariant?

Red flags that usually mean the architecture is treating shared code too casually

  • The implementation does not hold funds, so its state does not matter.
  • Only proxies are initialized used as if implementation initialization were operationally irrelevant.
  • The library still has owner / kill / upgrade verbs, but no one should call them directly.
  • This singleton is just a gas optimization even though every dependent instance hard-points to it.
  • The implementation can upgrade itself without a strong statement about direct-call context guards.
  • If something goes wrong we can always redeploy when dependent instances have no secure relink path.

Failure-prevention checklist

  • Inventory the three facts separately before launch: which live children still depend on this exact shared code object, what standalone authority still lives on it, and what surviving user or operator rail still works if that object disappears or turns hostile.
  • Seal every implementation, singleton library, beacon logic contract, and shared code dependency at deployment time; the shared object should become inert before any child instance or user asset depends on it.
  • Make direct calls fail safely by construction. initialize, upgradeTo, module-install verbs, kill, selfdestruct, and similar lifecycle functions should either revert outside the intended context or be permanently disabled.
  • Keep authority location explicit and reviewable. If fleet-wide mutation power still lives on the shared code object, document that plainly and treat it as part of the custody boundary instead of passive infrastructure.
  • Remove or isolate destructive verbs from shared code whenever possible. If destruction or code-identity mutation is unavoidable, bound who can invoke it, how it is reviewed, and how dependents survive it.
  • Separate upgrade authority from execution dependency when architecture allows. Dependents should not both execute through and existentially trust the same unsealed code address if a safer admin split is available.
  • Require blast-radius analysis before launch: how many dependents trust this object, what funds or permissions freeze if it fails, and what realistic relink, migration, or exit path still works after compromise.
  • Test shared code directly, not just through proxies or wallet stubs. Include direct initialization, direct upgrade attempts, implementation-context delegatecall paths, and code-destruction scenarios in release gates.
  • Monitor production for impossible standalone behavior on supposedly inert code objects: direct calls, post-deploy initialization events, owner changes, upgrade events, or code-hash changes.
  • Treat implementation identity pinning as a real control. Governance, deployment tooling, and incident-response runbooks should all know exactly which shared code object children trust and how that identity is verified.
  • Do not let unsealed shared code accumulate quiet state over time. If the standalone contract can store owners, module pointers, or approvals, reviewers should assume that state matters until proven otherwise.

Audit prompts

  • Which deployed children trust this exact shared code object today, and what breaks first for them if runtime code at that address disappears or changes meaning?
  • What exact guarantees make a direct external call to this implementation, library, or singleton harmless today?
  • Which privileged verbs remain callable on the shared code object itself, and what enforces that they cannot be exercised in standalone context?
  • Can the implementation or singleton still be initialized, reinitialized, upgraded, destroyed, delegated through, or ownership-transferred directly?
  • If the shared code disappeared or changed semantics tomorrow, which dependent assets, admin actions, or user exits would stop working first?
  • Where does fleet-wide authority really live: proxy admin, beacon admin, implementation, library owner, governance executor, or some hybrid surface reviewers might mistakenly collapse into just code?
  • Do deployment artifacts prove the shared code was sealed at birth, or is the argument only we do not intend to call it directly?
  • Are there any paths where implementation-side state matters economically even if proxy-side balances hold the assets: upgrade auth, kill switches, module registry, signer lists, or recovery verbs?
  • Does the design provide a credible relink, migration, or emergency-exit rail that still works after shared-code compromise, or is recovery only hypothetical?
  • If the implementation contains upgrade logic, what exact context guards stop implementation-side calls from mutating or destroying the dependency itself?
  • What telemetry would tell operators that a supposedly inert shared code object just became active, and how quickly can they contain the blast radius?

Lessons that should already be standard

  • Shared execution dependency, retained standalone authority, and fleet-wide consequence are different questions. Healthy review does not flatten many children trust this address, this address still has privileged verbs, and those verbs can still freeze or rewrite the fleet into one hand-wavy implementation risk label.
  • Shared code is part of the control plane whenever many live instances depend on its continued integrity or existence. Calling it just code does not make it passive.
  • Proxy safety is not enough if the implementation remains ownable, killable, or upgradable in standalone context.
  • Direct-call harmlessness is a launch invariant, not a post-audit cleanup item.
  • If one code address can brick a fleet, that address is a custody dependency whether or not it holds balances directly.
  • Recovery is part of the security model. A design with no surviving relink or exit path has already accepted catastrophic shared failure as normal trust.
  • Standalone implementation state is only irrelevant when the architecture proves it cannot become authoritative.
  • Release engineering matters as much as contract shape. A theoretically safe pattern deployed without sealing the shared object is still an unsafe live system.
  • Governance and operators need implementation identity discipline too; you cannot safely review or respond to incidents around a dependency whose trusted code object is ambiguous.

Where this note helps most

  • Start here when the sharper question is what exact guarantees should a shared implementation or singleton satisfy before the system may treat it as passive code?
  • Drop into unprotected-initializer-reinitialization when the exploit-side question is specifically how constructor-style setup stayed callable or replayable.
  • Drop into privileged-upgrade-abuse when the attacker already controls the formal upgrade authority and simply uses it directly.
  • Pair with execution-envelope-identity-pinning when the sharper question becomes how governance or review should pin the identity of an upgradeable executable object before privileged execution.
  • Pair with decommissioning-authority-extinguishment-discipline when the implementation or singleton is supposed to be retired, migrated away from, or left only as a bounded residual dependency.

Internal linkages

Control surface