Skip to content
Last updated: February 06, 2026

SCWE-098: Initialization Front-Running in Upgradeable Contracts

Stable Version v1.0

This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).

Send Feedback

Relationships

Description

Proxy-based contracts ship without constructors; if the initializer is callable by anyone, an attacker can front-run deployment and seize ownership, set trusted roles, or lock upgrades. This permanently compromises the implementation behind the proxy.

Remediation

  • Protect initializer functions with access control and initializer/reinitializer guards.
  • Invoke initialization atomically during deployment or via a deploy-time call data payload.
  • Use disableInitializers() on the implementation contract to prevent accidental takeover.

Examples

Vulnerable

pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract UpgradeableVault is Initializable {
    address public owner;

    function initialize(address _owner) public initializer {
        owner = _owner; // anyone can front-run and become owner
    }
}

Fixed

pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract UpgradeableVault is Initializable {
    address public owner;

    function initialize(address _owner, address deployer) public initializer {
        require(msg.sender == deployer, "only deployer");
        owner = _owner;
    }
}
Deployer (or factory) deploys the proxy and calls initialize(owner, address(this)) in the same transaction — no window for front-running.