Skip to main content
Sigvex

EXTCODESIZE Bypass Remediation

Why code-size checks do not reliably distinguish EOAs from contracts, and what to do instead when a function must be restricted to externally owned accounts.

EXTCODESIZE Bypass Remediation

Overview

EXTCODESIZE (Solidity’s address.code.length) returns 0 for an address whose contract is still being constructed. A contract calling out from inside its own constructor therefore reports zero code size, so any “reject contracts” gate built on code.length == 0 is bypassed by an attacker who runs the call from a constructor. The real fix is usually to stop depending on the EOA-vs-contract distinction at all; where an EOA-only rule is genuinely required, enforce it with tx.origin == msg.sender, which a constructor cannot fake.

Related Detector: EXTCODESIZE Bypass

Before (Vulnerable)

function claim() external {
    require(msg.sender.code.length == 0, "No contracts");
    _claim(msg.sender);
}

After (Fixed)

function claim() external {
    // tx.origin is the EOA that started the transaction; during a
    // constructor call, tx.origin != msg.sender, so the bypass fails
    require(tx.origin == msg.sender, "EOA only");
    _claim(msg.sender);
}

Alternative Mitigations

Design the rule away. Most “EOA only” checks exist to deter bots or block flash-loan-style interactions. Those goals are better served directly: add a per-address rate limit or cooldown, require a small stake, or add a reentrancy guard plus value invariants so contract callers gain no advantage. This avoids the tx.origin caveat entirely.

Account abstraction awareness. Treat “is the caller an EOA” as an unreliable signal going forward. Smart-contract wallets are legitimate users, and excluding all contracts may lock out real participants while only briefly inconveniencing attackers.

Common Mistakes

Using tx.origin for authorization instead of EOA-gatingrequire(tx.origin == owner) is a phishing vulnerability (a malicious contract the owner calls can act as the owner). tx.origin == msg.sender is acceptable; tx.origin == <privileged address> is not.

Assuming code.length > 0 proves a contract — the inverse check is equally unreliable during construction, so neither direction is a sound security boundary.

References