Skip to content
Security
Skill

/initialization

Detect initialization bugs in upgradeable contracts — missing `_disableInitializers()`, re-init attacks, parent-init not chained, constructor-vs-initializer confusion, public `initialize`. Activate on OZ Upgradeable, UUPS, Transparent proxy, Initializable, or any contract with

From plugin
rugproof
952 skills23 agents45 commands4 hooks
Install
$ npx -y skills add omermaksutii/RugProof --skill initialization --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/initialization

Context preview

The summary Claude sees to decide when to auto-load this skill.

Detect initialization bugs in upgradeable contracts — missing `_disableInitializers()`, re-init attacks, parent-init not chained, constructor-vs-initializer confusion, public `initialize`. Activate on OZ Upgradeable, UUPS, Transparent proxy, Initializable, or any contract with

SKILL.md

initialization.SKILL.md
name: initialization
description: Detect initialization bugs in upgradeable contracts — missing `_disableInitializers()`, re-init attacks, parent-init not chained, constructor-vs-initializer confusion, public `initialize`. Activate on OZ Upgradeable, UUPS, Transparent proxy, Initializable, or any contract with `initialize` / `__init`.

Initialization detection

When this applies

  • OZ `Initializable` / `*Upgradeable` contracts
  • Custom upgradeable contracts with `initialize()` functions
  • Beacon proxies, UUPS, Transparent — all flavors
  • Factory-deployed clones (EIP-1167 minimal proxies)
  • Any deploy script that uses `delegatecall` for setup

Detection patterns

Implementation contract not init-locked (CRITICAL)

contract Vault is Initializable, OwnableUpgradeable {
    function initialize() external initializer {
        __Ownable_init(msg.sender);
    }
    // ← no constructor disabling initializers
}

Anyone can call `initialize` on the *implementation* address directly. Fix:

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() { _disableInitializers(); }

Public initializer on deployed proxy (CRITICAL)

Proxy deployment script forgets to call `initialize` atomically → attacker calls it first → owns the contract. Fix: deploy + initialize in same tx via factory or initializer-on-deploy proxy.

Re-init via `reinitializer(N)` accessible to anyone (HIGH)

function reinitialize() external reinitializer(2) {   // ← no auth
    __Pausable_init();
}

Add `onlyOwner` or `onlyProxyAdmin`.

Parent init not called (HIGH)

function initialize() external initializer {
    // __Pausable_init() missing — pausable state defaults to wrong values
}

Verify every parent's `__X_init()` is chained.

Logic in constructor instead of initializer (HIGH for upgradeable)

constructor() OwnableUpgradeable() { owner = msg.sender; }   // ← runs on impl, not proxy

Upgradeable contracts must do setup in `initialize`, not constructor.

Constructor runs on proxy (incorrect mental model) (MEDIUM)

Devs sometimes write constructor logic expecting it to run on the proxy. It doesn't — constructor runs at deployment of the *implementation*, and the proxy never re-runs it.

Clones (minimal proxies) without per-clone init guard (HIGH)

EIP-1167 clones share implementation; each clone's `_initialized` slot starts at 0. If `initialize` has no auth, anyone can initialize a fresh clone before the deployer.

Re-init via storage-slot manipulation (HIGH)

OZ v4: `_initialized` is uint8; v5: `_initialized` is uint64. Custom upgradeable contracts that don't use OZ may have bypassable guards.

Severity rubric

| Pattern | Severity | |---|---| | Implementation init not disabled (anyone owns impl) | **Critical** | | Deployed proxy with public init un-called | **Critical** | | Re-initializer with no auth | **High** | | Clones with no atomic init | **High** | | Parent `__init` missing → broken inheritance | **High** | | Constructor used instead of initializer (upgradeable) | **High** | | Initializer parameter validation missing (e.g. zero-address admin) | **Medium** | | Initial state value seems wrong (defaults to 0) | **Medium** |

Remediation patterns

  • Implementation constructor: `_disableInitializers()`.
  • Deploy proxy + call initializer atomically (single factory tx).
  • Mark `initialize` as `initializer`, `reinitialize` as `reinitializer(N)` with auth.
  • Chain every parent's `__X_init()` in order matching inheritance.
  • For clones: use a factory that deploys-and-initializes in one call.
  • Test: from a fresh deploy, attempt `initialize()` twice and from a non-admin EOA — both should revert.

False-positive notes

  • Non-upgradeable contracts that just happen to use `initialize` as a name — not the same pattern.
  • `Initializable` used purely as a one-shot setup guard with no proxy involved — lower severity.

Related

  • [[storage-layout]]
  • [[access-control]]
  • [[delegatecall-risks]]
Read more
Ships withrugproof

Rugproof your code before someone else does. 🌐 Live site: omermaksutii.github.io/RugProof 📦 Latest: v1.0.0 — 45 commands · 23 agents · 45 skills · 13 MCP servers · tested, offline-first, with rule packs, a benchmark, non-EVM coverage, and post-deploy

Get the whole plugin

Other skills on rugproof.