EXTCODESIZE Bypass
Detects access control checks using EXTCODESIZE that can be bypassed by calling from a contract's constructor, where code size is zero.
EXTCODESIZE Bypass
Overview
Remediation Guide: How to Fix EXTCODESIZE Bypass
The EXTCODESIZE bypass detector identifies contracts that use EXTCODESIZE (Solidity’s address.code.length) to distinguish between externally owned accounts (EOAs) and contracts. During contract construction, EXTCODESIZE returns 0 for the deploying contract, allowing attackers to bypass “no contract” checks by executing their attack from within a constructor.
Sigvex locates EXTCODESIZE opcodes followed by zero-comparison checks in require/revert patterns, then determines whether the check is used as an access control gate for critical operations.
Why This Is an Issue
Many contracts use EXTCODESIZE checks to prevent bots or enforce that only human users (EOAs) interact with certain functions. This protection is fundamentally flawed: an attacker deploys a contract whose constructor calls the victim, and during that constructor execution, EXTCODESIZE returns 0 for the attacker’s address.
This bypass has been exploited in NFT minting bots (bypassing anti-bot measures), airdrop claims (claiming from contract wallets despite “no contract” restrictions), and DeFi protocols that assumed only EOAs would interact with certain functions.
How to Resolve
// Before: Vulnerable — EXTCODESIZE check can be bypassed
function claim() external {
require(msg.sender.code.length == 0, "No contracts");
// Critical logic...
}
// After: Fixed — use tx.origin comparison
function claim() external {
require(tx.origin == msg.sender, "Only EOA");
// Critical logic...
}
Note: While tx.origin == msg.sender is generally discouraged for authorization, it is the correct pattern for EOA-only enforcement. For most use cases, removing the restriction entirely and adding a reentrancy guard is preferable.
Examples
Vulnerable Code
contract VulnerableMint {
mapping(address => bool) public hasClaimed;
function claim() external {
// BYPASS: attacker calls from constructor where code.length == 0
require(msg.sender.code.length == 0, "No contracts allowed");
require(!hasClaimed[msg.sender], "Already claimed");
hasClaimed[msg.sender] = true;
_mint(msg.sender, 1);
}
}
Fixed Code
contract SafeMint {
mapping(address => bool) public hasClaimed;
function claim() external {
// tx.origin check cannot be bypassed from a constructor
require(tx.origin == msg.sender, "Only EOA");
require(!hasClaimed[msg.sender], "Already claimed");
hasClaimed[msg.sender] = true;
_mint(msg.sender, 1);
}
}
Sample Sigvex Output
{
"detector_id": "extcodesize-bypass",
"severity": "critical",
"confidence": 0.85,
"description": "Function claim() uses EXTCODESIZE to check if the caller is a contract. This check returns 0 during contract construction, allowing bypass by calling from a constructor.",
"location": { "function": "claim()", "offset": 42 }
}
Detection Methodology
- EXTCODESIZE identification: Locates the
EXTCODESIZEopcode (0x3B) in function bytecode. - Zero comparison pattern: Checks for
ISZEROorEQwith zero following theEXTCODESIZEresult. - Access control context: Determines whether the comparison feeds into a
JUMPIused as arequire/revertgate. - Missing constructor guard: Verifies there is no additional
tx.origin == msg.sendercheck on the same execution path. - Confidence adjustment: Higher confidence when the check gates a state-modifying operation; lower for view-only paths.
Limitations
False positives:
- Contracts that use
EXTCODESIZEfor informational purposes (not access control) may be flagged. - Functions that additionally validate via other means (e.g., signature verification) may be flagged despite having sufficient protection.
False negatives:
EXTCODESIZEchecks performed in a separate helper function (not inlined) may not be detected.- Custom assembly-level implementations may use non-standard patterns.
Related Detectors
- Access Control — detects missing authorization on privileged functions
- TX Origin — detects tx.origin usage for authentication (a related but different anti-pattern)