Shared-Code Singleton Retained-Authority Bricking

  • Name: shared-code singleton retained-authority bricking
  • Category: exploit pattern / proxy-and-library architecture failure / shared implementation risk / fleet-wide liveness collapse
  • Summary: Some systems fan many wallets, proxies, or vault instances into one shared implementation or library contract that is treated operationally as inert code. The bug class appears when that singleton still retains directly reachable authority — initializer ownership, upgrade auth, kill-switch logic, or another privileged execution path — and dependent instances have no independent way to survive its compromise or destruction. Once the attacker captures the singleton itself, they do not have to drain each instance one by one. They can brick, redefine, or strand an entire fleet by attacking the one shared code object every instance depends on for liveness.
  • What it is:
    • Many deployed instances delegate core behavior into one shared implementation or library contract.
    • Operators mentally classify that shared contract as just code, but it still exposes live privileged state or privileged execution surfaces when called directly.
    • The singleton can be initialized, upgraded, killed, or otherwise mutated in its own storage / execution context.
    • Dependent instances keep pointing at the singleton and do not have a separate recovery rail that can bypass it after compromise.
    • The exploit cashes out through fleet-wide outage, frozen funds, or a mass semantic rewrite of what every dependent instance does.
  • Typical preconditions:
    • The architecture uses shared libraries, shared logic singletons, or one implementation behind many proxies.
    • The shared contract is directly callable rather than permanently sealed.
    • Dangerous capability remains reachable on the singleton itself: init, initialize, upgradeTo, upgradeToAndCall, kill, selfdestruct, owner-only module wiring, or similar.
    • Dependent instances trust the singleton not only for ordinary logic but also for their only upgrade or execution path.
    • There is no independent admin, escape hatch, or code-substitution rail that survives if the singleton is destroyed or captured.
  • Common vulnerable surfaces:
    • shared library contracts whose constructor-refactored setup or owner-only maintenance functions remain callable on the library itself
    • UUPS-style implementations where upgrade logic lives on the implementation rather than on a separate proxy admin
    • minimal wallet or vault stubs whose business logic exists almost entirely in one singleton contract
    • factory systems that deploy many dependents before sealing the shared implementation instance
    • architectures that allow delegatecall-reachable self-destruction or code-mutation from the singleton context
    • systems whose operational runbooks assume implementation-side state does not matter even though the implementation also authenticates upgrades or destructive actions
  • Exploit sequence:
    1. The attacker identifies a shared implementation or library contract that still retains privileged direct-call authority.
    2. They capture that authority by direct initialization, ownership takeover, upgrade-auth takeover, or another singleton-local auth failure.
    3. They use the captured singleton authority to execute a destructive or semantics-changing action: selfdestruct, malicious upgrade, ownership rewiring, or equivalent.
    4. Every dependent wallet or proxy keeps delegating into the now-destroyed or attacker-redefined singleton.
    5. Because the singleton was also the only practical liveness or upgrade rail, dependent instances cannot cleanly recover.
    6. The result is fleet-wide bricking, frozen funds, or mass compromise from one seized code address.
  • Variants:
    • Uninitialized shared-library singleton — the library itself was never initialized, so first caller becomes owner and can use a retained kill path, as in Parity’s November 2017 freeze.
    • Implementation-side upgrade-rail capture — the implementation authenticates its own upgrade path, so initializing or owning the implementation lets the attacker use upgrade logic against the implementation itself, as in the OpenZeppelin UUPS near miss.
    • Killable shared code dependency — the decisive failure is not ownership over each child instance, but the continued existence of runtime code at one shared address.
    • Fleet-wide semantic rewrite — instead of destroying the singleton, the attacker changes what all dependents do by mutating the shared implementation they delegate into.
  • Detection heuristics:
    • For every shared implementation or library, ask what live authority still exists if I call this contract directly?
    • Inventory whether upgrade auth, owner slots, initializer guards, and destructive code paths live on the singleton itself or elsewhere.
    • Ask whether dependent proxies or stubs have any recovery mechanism if the singleton code disappears or becomes malicious.
    • Treat shared-code deployment hygiene as custody-critical: sealing the singleton is as important as initializing each proxy.
    • Review whether operational statements like only proxy storage matters or the library has no state are actually true under direct calls.
    • Simulate failure of the shared code address itself, not just misuse of the child instances.
  • Defenses:
    • Lock or initialize shared implementations and libraries immediately on deployment, even if they are never meant to hold user-facing state.
    • Remove or permanently disable singleton-side destructive capability (selfdestruct, kill switches, direct upgrade auth) when the singleton is meant to be inert shared code.
    • Keep upgrade authority on an independent admin surface when possible, rather than on the implementation that proxies already depend on for execution.
    • Give dependent instances a recovery path that survives singleton compromise, or accept that the singleton is inside the system’s custody boundary.
    • Audit one-to-many code dependencies as fleet-risk concentration, not merely as gas optimization.

What sound design looks like

  • The shared implementation or library is genuinely inert when called directly. Initialization is already burned, destructive verbs are absent or permanently deauthorized, and no singleton-local owner or upgrader can still redefine what the fleet depends on.
  • Fleet-wide mutation authority lives on a separate, explicitly governed surface instead of hiding inside the same code address that every proxy, wallet, or vault delegates into for ordinary execution.
  • Child instances can survive singleton trouble. If the shared code address is destroyed, paused, or proven unsafe, operators or users still have a relink, migration, or exit rail that does not require the compromised singleton to cooperate.
  • Deployment ceremony treats singleton sealing as a launch invariant, not a cleanup task. Teams do not declare a factory or proxy fleet live until the shared code object itself has been initialized, locked, and verified in its deployed context.
  • Architecture docs say plainly which addresses are just reusable logic and which are still control planes. If one shared object can brick or rewrite the fleet, the design names that concentration honestly and bounds it accordingly.
  • Tests and incident drills model loss of the singleton itself, not only misuse of one child instance. Healthy review asks what if this exact code address disappears or turns hostile? before users are asked to trust the fleet.

Three shared-code authority questions reviewers should not collapse

  1. Is this exact shared code object part of the fleet’s live execution dependency?
    • A library, implementation, beacon, or singleton can be widely trusted even before it carries any standalone admin state.
    • The first question is simple but easy to underweight: if this code address vanished or changed meaning, would many children stop working?
  2. Does that shared object still retain standalone privileged authority of its own?
    • Initializers, upgrader state, module-install rights, kill paths, and direct-call delegatecall gadgets all belong here.
    • This is where the supposedly passive code blob stops being passive and starts behaving like a separately seizeable control plane.
  3. Does that standalone authority still translate into fleet-wide custody or liveness power?
    • A retained singleton privilege becomes catastrophic when dependent children cannot relink, migrate, or exit after the shared object is captured or destroyed.
    • Reviewers should keep 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 flattening them into one vague shared implementation risk story.

Failure-prevention checklist

  • Inventory the three questions separately before launch: which children trust 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 shared implementation, library, beacon, or singleton at deployment time with the same ceremony seriousness as a treasury admin handoff; nobody should call this directly is not a control.
  • Make direct calls to the shared code object harmless by construction: initialization, upgrade verbs, module wiring, kill(), selfdestruct, and equivalent lifecycle verbs should revert, no-op safely, or be permanently disabled in standalone context.
  • Keep authority location explicit and sparse. If ownership, upgrade permission, or destructive verbs still live meaningfully on the shared code object, document that the singleton is inside the live custody boundary rather than pretending it is passive infrastructure.
  • Separate business logic from fleet-wide mutation authority when possible so dependents do not both execute through and existentially trust the same code address.
  • Predefine recovery for shared-code failure: relink path, migration rail, emergency exit, or bounded blast radius. If losing one shared code address freezes the fleet, that should be treated as a first-class design fact before launch.
  • Test the singleton itself as an attack surface, not only the children. Run direct-call, direct-initialize, implementation-context upgrade, and code-destruction scenarios against the live deployment topology.
  • Treat one-to-many code reuse as concentrated operational risk. Gas savings do not justify a shared object that can still accumulate separate privileged state or disappear underneath every dependent instance.

Audit prompts

  • Which exact children trust this shared implementation, library, beacon, or singleton today, and what fails first for them if the code at that address disappears or changes meaning?
  • What meaningful authority still exists if an arbitrary external caller interacts with the shared object directly instead of through a child proxy / wallet?
  • Can the shared code object itself still be initialized, reinitialized, upgraded, delegated through, paused, or destroyed, and which exact storage or context guards make that impossible?
  • If the singleton vanished today, which user assets, admin operations, or recovery paths would become unrecoverable because children cannot relink or exit without that code address?
  • Does any lifecycle verb assume implementation-side state does not matter even though the implementation itself authenticates upgrades, module installation, ownership, or destructive actions?
  • Are constructors, initializer locks, _disableInitializers(), onlyProxy / notDelegated-style context guards, and direct-call kill-path removals actually present on the deployed artifact, not just in a later version or coding guideline?
  • Where is fleet-wide authority concentrated: a proxy admin, a beacon, the implementation itself, a singleton library owner, or some hybrid surface that reviewers might accidentally flatten into just shared code?
  • What exact blast radius follows from compromise of each shared dependency object, and does the protocol have any realistic operator or user action that still works after that dependency is captured or destroyed?

Lessons that should already be standard

  • Shared execution dependency, standalone authority, and fleet-wide consequence are different questions. Healthy review does not collapse many children trust this address, this address still has privileged verbs, and those verbs can still freeze everyone into one hand-wavy shared-code risk label.

  • Shared code is not passive just because user balances live elsewhere. If many live instances depend on one contract’s continued integrity or existence, that contract is part of the custody and liveness boundary.

  • Initialize the proxies is not enough. Reviewers must also ask whether the implementation or library itself can still accumulate first-caller authority or exercise privileged verbs in its own context.

  • A singleton that can be destroyed, rebound, or self-upgraded is a control plane, not a gas optimization.

  • Fleet-wide architecture review should start from blast radius: what happens if this one code address is captured or disappears? is more important than did each child instance pass unit tests?

  • Recovery is a security property. If dependents have no relink, migration, or exit path after singleton compromise, then the design has already accepted catastrophic shared failure as normal-state trust.

  • Direct-call deauthorization should be treated as a launch invariant, not a cleanup chore for later hardening.

  • Where this pattern stops:

    • If the decisive exploit is simply replayable setup on one live instance, unprotected-initializer-reinitialization is usually the cleaner frame.
    • If the attacker captured an ordinary proxy admin or deployer key and then used a legitimate upgrade path to swap implementations, privileged-upgrade-abuse is usually the sharper lens.
    • This note begins when the supposedly passive shared code object is itself the compromised root and the fleet-wide dependency on that one object is what makes the outcome catastrophic.
  • High-signal incidents:

    • [[../../../incidents/governance-admin-upgrades/parity-multisig-walletlibrary-uninitialized-selfdestruct-freeze-2017-11-06|Parity multisig WalletLibrary uninitialized-singleton selfdestruct freeze (2017-11-06)]] — Parity’s post-July-2017 wallet stubs all delegated into one shared WalletLibrary; because the library itself was left uninitialized and still exposed initWallet() plus kill(), one caller could seize and destroy the singleton, freezing every dependent wallet.
    • [[../../../incidents/governance-admin-upgrades/openzeppelin-uupsupgradeable-uninitialized-implementation-selfdestruct-bricking-near-miss-2021-09-09|OpenZeppelin UUPSUpgradeable uninitialized-implementation selfdestruct bricking near miss (2021-09-09)]] — UUPS implementations carried their own upgrade rail, so an uninitialized implementation could be claimed directly and then used to delegatecall into code that destroyed the implementation, potentially bricking every proxy that depended on it.

Internal linkages

Control surface