crates/lint/docs/unprotected-initializer.md
Severity: High
ID: unprotected-initializer
Flags upgradeable contracts whose public or external initializer can still be called directly on an implementation contract that exposes a destructive entry point.
Reports initializer-like functions that:
initializer or reinitializer;_disableInitializers(); anddelegatecall, callcode, or selfdestruct path
that is not restricted to proxy calls with onlyProxy.An attacker can initialize the implementation directly, take ownership, and invoke its destructive entry point. Destroying or corrupting an implementation can disable every proxy that delegates to it.
contract Vault is Initializable {
address public owner;
function initialize(address owner_) public initializer {
owner = owner_;
}
function execute(address target, bytes calldata data) external {
(bool ok,) = target.delegatecall(data);
require(ok);
}
}
contract Vault is Initializable {
address public owner;
constructor() {
_disableInitializers();
}
function initialize(address owner_) public initializer {
owner = owner_;
}
}
The lint is intentionally local: it does not inspect deployment scripts to prove whether a proxy is initialized atomically. It focuses on implementation contracts that remain directly initializable and can reach code paths that may destroy or replace implementation state.
The onlyProxy exemption is a name-based heuristic for common UUPS implementations.