★ Reinitializable implementation (no _disableInitializers)
A post-deploy hygiene & change mgmt factor in the v1.7.0 rubric. Measured per protocol on a s cadence.
Methodology how we score #
**What this measures** This factor identifies whether an upgradeable proxy's implementation contract fails to call _disableInitializers() in its constructor, leaving the initialize() function callable on the bare implementation address. OpenZeppelin's upgradeable contract guidance recommends including "a flag so that you can't initialize a logic contract more than once" to prevent this vulnerability. Without _disableInitializers(), an attacker can call initialize() directly on the implementation contract, claim ownership or admin roles, and — depending on how the proxy's storage is structured — potentially execute a privileged drain through the proxy.
**Why it matters** Reinitializable implementations represent a proxy takeover pattern that is both automated and deterministic: an attacker scans newly deployed upgradeable contracts for unprotected initialize() functions on the implementation address, calls them, and establishes an unauthorized admin role before the legitimate protocol team or users are aware. The Dedaub/SEAL911 "CPIMP" research documented this attack class publicly in 2024; USPD lost $1M after an attacker front-ran the initialize transaction and waited 78 days for TVL to accumulate before executing the drain. The Raft protocol loss ($3.3M, 2023) illustrates a related delegatecall storage pattern where unguarded initialization state caused attacker funds to route unexpectedly. OpenZeppelin's proxy pattern documentation explicitly flags that implementation contracts must be treated as separately attackable surfaces.
**Green / Yellow / Red** Green is assigned when the implementation contract calls _disableInitializers() in its constructor (verified via static analysis or source inspection), or when the implementation is deployed behind an initializer modifier that is already set before deployment completes. Yellow covers cases where the initialize function exists and is unprotected on the implementation but the proxy's admin is a trusted multisig that has already initialized the state, reducing the exploitability window. Red is assigned when static analysis confirms the implementation contract has an initialize() function callable by any address with no _disableInitializers() call in the constructor.
**Common gray cases** This factor is grayed when the proxy architecture does not use an upgradeable pattern (immutable contracts, Beacon-without-initialize, or UUPS with fully locked implementation), or when the implementation's constructor is not accessible for analysis.
**Notable historical examples** - **Raft** ($3.3M, 2023): Delegatecall into implementation with uninitialized storage slot caused funds to route to the burn address; pattern adjacent to unguarded initialization state.
**★ Critical factor** This factor alone is sufficient to trigger a D or F grade under rubric v1.7.0 — a single red assessment here overrides all other category scores. An unprotected initialize() function on an implementation contract creates a front-runnable proxy takeover vector that can establish unauthorized admin control before any user interaction — a one-transaction full account takeover with no code-quality mitigation available after deployment.
Measurement what to look for #
Determine whether the implementation contract does not call `_disableInitializers()` in its constructor, leaving re-initialization possible.