Skip to content
Back to skills

Solady Ownable Init Frontrun

ASecurity

Detect front-runnable ownership initialization in Solady Ownable / OwnableRoles. Solady's `_initializeOwner` is a guarded one-time setter (it reverts with `AlreadyInitialized` on a second call) but it is NOT access-controlled, so in constructor-less deployment paths (minimal-proxy clones, EIP-1167, factory `create`/`create2` without atomic init) an attacker can call the public initializer first and seize ownership. Activate on solady Ownable/OwnableRoles in clones, factories, or any non-atomi...

  • 36 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 4, 2026
blockchaingit

Security analysis

A100/100

Scanned October 4, 2026

npx -y skills add iktok90-design/ai-smart-contract-auditor --skill solady-ownable-init-frontrun --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Solady Ownable Init Frontrun?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Solady Ownable Init Frontrun
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/iktok90-design-solady-ownable-init-frontrun/badge)](https://www.skillsdirectory.com/skills/iktok90-design-solady-ownable-init-frontrun)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: solady-ownable-init-frontrun
description: Detect front-runnable ownership initialization in Solady Ownable / OwnableRoles. Solady's `_initializeOwner` is a guarded one-time setter (it reverts with `AlreadyInitialized` on a second call) but it is NOT access-controlled, so in constructor-less deployment paths (minimal-proxy clones, EIP-1167, factory `create`/`create2` without atomic init) an attacker can call the public initializer first and seize ownership. Activate on solady Ownable/OwnableRoles in clones, factories, or any non-atomic deploy+init.
---

# Solady Ownable initializer front-run detection

## When this applies

Trigger on any of:

- `import {Ownable} from "solady/auth/Ownable.sol";` or `OwnableRoles`
- A contract calling `_initializeOwner(...)` from a public/external `initialize()` rather than the constructor
- Minimal-proxy clones (`LibClone.clone` / EIP-1167) of an Ownable implementation
- Factory deployments where `create`/`create2` and `initialize()` are two separate transactions
- Implementation contracts behind proxies (UUPS / transparent) using Solady Ownable
- Any `initialize`/`init` that is not protected by an initializer guard or atomic deploy

## Detection patterns

### Public init, non-atomic deploy (HIGH)
```solidity
contract Vault is Ownable {
    function initialize(address owner) external {
        _initializeOwner(owner);   // reverts on 2nd call — but ANYONE can make the 1st
    }
}
// Factory:
address v = LibClone.clone(impl);
Vault(v).initialize(msg.sender);   // ← separate tx: front-runnable in the mempool
```
Between `clone` and `initialize`, a searcher front-runs `initialize(attacker)`. `_initializeOwner` succeeds for them; the legit call then reverts `AlreadyInitialized`.
**Signal:** `_initializeOwner` reachable from an unguarded external function and deploy/init are not in one transaction.

### Implementation left uninitialized (HIGH)
```solidity
contract Impl is Ownable {
    function initialize(address o) external { _initializeOwner(o); }
}
// Impl deployed standalone, never initialized → anyone claims it.
```
For UUPS, an attacker who owns the *implementation* can call `upgradeTo`/`selfdestruct`-style logic and brick or hijack all proxies pointing at it.
**Signal:** Solady Ownable implementation deployed but not initialized in the same tx, and the implementation itself is callable.

### Re-init via `_setOwner` exposure (MEDIUM-HIGH)
```solidity
function _setOwner(address o) internal { ... }   // Solady internal
function rescueOwner(address o) external { _setOwner(o); }  // ← bypasses init guard entirely
```
`_setOwner` has no `AlreadyInitialized` guard; exposing it publicly defeats the one-time protection.
**Signal:** `_setOwner` wrapped in an unprotected external function.

## Severity rubric

| Pattern | Severity | Notes |
|---|---|---|
| Clone/factory with non-atomic public initialize | **High** | Ownership theft, mempool front-run |
| Uninitialized implementation behind proxy | **High** | Impl hijack → proxy compromise |
| `_setOwner` exposed externally | **High** | Init guard bypassed entirely |
| Init gated to factory `msg.sender` / atomic deploy | **Info** | Correctly protected |

## Remediation patterns

1. **Atomic deploy+init** — initialize inside the same transaction as `clone`/`create`, or use `LibClone.cloneDeterministic` + immediate init in the factory call.
2. **Restrict the initializer** — `require(msg.sender == factory)` or pass owner via clone immutable args (`LibClone.clone(impl, immutableArgs)`).
3. **Lock the implementation** — call `_initializeOwner(deadAddress)` / `_disableInitializers`-equivalent in the implementation's constructor so the standalone impl can't be claimed.
4. **Never expose `_setOwner`** — only `transferOwnership` (owner-gated) and the guarded `_initializeOwner`.

## False-positive notes

- Constructor-based `_initializeOwner` (non-clone deploy) is not front-runnable — Info.
- Factory that deploys and initializes in one call (atomic) — not exploitable.
- Initializer gated on `msg.sender == factory` or consuming clone immutable args — safe.

## Related

- [[access-control]]
- [[delegatecall-risks]] — UUPS implementation hijack
- [[centralization-risk]]

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…