The bridge didn’t need a sophisticated zero-day. It didn’t need broken cryptography or a stolen admin key.
It just needed one zero.
On August 1, 2022, the Nomad cross-chain bridge was drained of roughly $190 million in liquidity. The number was staggering, but the mechanics were stranger still: once the exploit went public, more than 300 addresses joined the feeding frenzy.
Most of them weren’t writing custom exploits. They were copying a working transaction’s input data, pasting their own wallet address into the recipient field, and resubmitting it. It became one of the most chaotic copy-paste heists in Web3 history.
At the center was one value doing two jobs: 0x00 meant “no proof recorded” in one place and “trusted” in another.
The Architecture: How Nomad Decided What Was Real
To understand how the flaw worked, you first need to understand how Nomad handles cross-chain messages.
Nomad is an optimistic interoperability protocol. When you send assets across chains, the message is committed into a Merkle tree on the source chain. An off-chain Updater signs each new root, the root is relayed to the destination chain, and it only becomes usable once a challenge window has passed. On the destination chain, a contract named Replica decides whether a message is valid and allowed to execute.
The fundamental security boundary is simple:
An inbound message must only execute after the protocol proves it belongs to a valid, accepted Merkle root.
If Replica accepts an unproven message, everything downstream of it (the routers, the liquidity pools, the token vaults) is exposed. The cryptography doesn’t fail here. The access-control gate simply opens the door.
The Assumption: What Happens When a Mapping Is Empty?
In Solidity, storage mappings don’t throw errors for keys that haven’t been set. Query a key that doesn’t exist and the Ethereum Virtual Machine (EVM) returns the default zero value for that type.
After its June 2022 upgrade, Replica tracked trust with two mappings:
// message hash => the root it was proven under
mapping(bytes32 => bytes32) public messages;
// root => timestamp after which the root is acceptable
mapping(bytes32 => uint256) public confirmAt;
For an unseen, fake or unproven message, messages[unknownMessage] naturally returns:
0x0000000000000000000000000000000000000000000000000000000000000000
Under normal circumstances, that’s fine. Zero means “nothing has been proven yet.” Zero represents absence.
The Collision: When “Nothing” Became “Everything”
The vulnerability became exploitable after a smart contract upgrade on June 21, 2022. The state that made it possible had been set two months earlier.
When Nomad deployed the Replica proxy on April 21, 2022, its initial committed root was set to bytes32(0). The initializer pre-approves whatever root it is given:
committedRoot = _committedRoot; // _committedRoot was bytes32(0)
// pre-approve the committed root.
confirmAt[_committedRoot] = 1; // so confirmAt[0x00] = 1
confirmAt stores a timestamp: the moment a root becomes acceptable. A value of 1 means one second past midnight on January 1, 1970, so as far as the contract was concerned, the zero root had been trusted for more than half a century.
Before June, that entry sat unused, because the old process() checked a message status rather than looking up a root. The June 21 upgrade changed how process() verified messages. It now fetched the root a message was proven under and asked whether that root was acceptable:
bytes32 _messageHash = _m.keccak();
require(acceptableRoot(messages[_messageHash]), "!proven");
Inside acceptableRoot(), the decision came down to that timestamp:
uint256 _time = confirmAt[_root];
if (_time == 0) {
return false;
}
return block.timestamp >= _time;
An unproven message now resolved to bytes32(0), the same value that had already been marked as trusted. That created a semantic collision:
Interpretation A (storage):
0x00= “This message has no recorded proof.”Interpretation B (validation):
0x00= “This root is pre-approved and trusted.”
Imagine a venue where security checks every visitor against an approval database. If a visitor has no record, the lookup returns a blank entry. But during setup, the system had marked that blank entry itself as approved.
Now someone with no valid pass gets waved through, not because their identity was verified, but because the system mistakes “no record” for “approved.”
That is exactly what process() did with an unproven message. It looked up the message’s root (0x00), found that confirmAt[0x00] equaled 1, saw that the current time was later than 1970, and decided the check had passed.
Absence of proof became evidence of validity.
The Check Was There. The Meaning Was Wrong.
This wasn’t a case of developers forgetting to write a check. The protocol did perform validation.
The flaw was that two distinct layers assigned contradictory meanings to the same state value:
Layer 1 treated zero as an uninitialized placeholder.
Layer 2 treated zero as a trusted root.
When system components agree on the syntax but disagree on the semantics, security invariants collapse.
The Copycat Wave: The Economics of an On-Chain Template
The bug was probed before it was looted. On July 31, an address later involved in the exploit sent small test transactions straight to process(), and they went through without proofs.
Active exploitation began at 21:32 UTC on August 1, in Ethereum block 15259101. The first two exploit transactions, each taking 100 WBTC, were routed privately through Flashbots via helper contracts, which kept the payload out of the public mempool. A third exploiter in the same block skipped the helper and called the contract directly, publishing the raw payload for anyone watching.
Because Ethereum transactions are entirely transparent, observers quickly realized what was happening:
Copy the raw payload of a working exploit transaction.
Replace the original attacker’s address with their own wallet address.
Send the modified transaction.
That’s what made it feel like a crowdsourced loot box. The barrier dropped from “discover, understand, and construct” to “observe, modify, and submit.” Coinbase’s analysis noted that attackers drained the bridge in identical chunks when they could have emptied it in a single transaction, a sign they were replaying payloads rather than writing their own.
Blockchain security firm PeckShield counted more than 300 addresses taking funds, with roughly 41 of them taking about $152 million, around 80% of the total. Bot operators, MEV searchers, at least one address tied to an earlier DeFi exploit, and white hats trying to get funds out of harm’s way all squeezed into the same window. By 05:49 UTC on August 2, the bridge was effectively empty.
The aftermath wasn’t purely theft, either. Nomad offered a 10% bounty to anyone who returned at least 90% of what they took, and by mid-August reported that more than $37 million, about 20% of the stolen funds, had come back.
The Regulator’s Read
In December 2025, the US Federal Trade Commission announced a proposed settlement with Illusory Systems, the company behind Nomad. The FTC’s complaint alleged that the June 2022 upgrade introduced inadequately tested code containing a significant vulnerability, and that weak security and incident response kept Nomad from stopping a loss of $186 million. The proposed order requires a comprehensive information security program and the return of recovered funds to affected users.
These are allegations resolved by a proposed consent order, not a court finding. The signal for builders is still plain: an upgrade that breaks a state invariant has now been described by a regulator as a failure of reasonable security, not bad luck.
The Fix Was Three Lines
Nomad’s patched Replica, published in its public repository, closes the hole at every point where the zero value could reach a trust decision:
// initialize(): never pre-approve the zero root
if (_committedRoot != bytes32(0)) confirmAt[_committedRoot] = 1;
// acceptableRoot(): "no record" is never acceptable
if (_root == LEGACY_STATUS_PROCESSED || _root == LEGACY_STATUS_NONE)
return false;
// setConfirmation(): governance cannot re-trust zero
require(_root != bytes32(0) || _confirmAt == 0, "can't set zero root");
LEGACY_STATUS_NONE is simply bytes32(0). Each line encodes the same rule: zero means absence, and absence must never reach a trust gate.
The Real Lesson: State Invariants Across Upgrades
The vulnerability wasn’t baked into the initial code. It was introduced by an upgrade.
When upgrading smart contracts, teams can focus on new logic while overlooking how it interacts with state and assumptions established earlier. Storage survives an upgrade. The meaning attached to it may not.
For teams shipping upgradeable contracts, that turns into a short checklist:
Reserve zero for “unset” and reject it explicitly at every trust or authorization gate.
Validate initializer parameters, and treat values set at deploy time as part of the attack surface.
Review upgrades against live storage, not just the code diff. Ask what every existing value will mean to the new logic.
Run invariant tests against a fork of the post-upgrade chain state, so the values that actually went live are the ones being tested.
Build a way to stop. Pauses, rate limits and withdrawal caps can turn an eight-hour drain into a contained incident.
About Soulbound Security
This research comes from Soulbound Security’s 31337 security research team, which focuses on securing blockchain infrastructure.
Soulbound Security is a blockchain technology company focused on protocol-level security infrastructure. We design and ship systems where privacy, compliance, and auditability are architectural properties, not policy promises.
Soulbound Security is an approved organization under Anthropic’s Cyber Verification Program.
Got questions or insights on cross-chain security, or want to collaborate on a research piece? Reach out to Gakarot at gakarot@soulboundsecurity.io.
Sources
Coinbase, Nomad Bridge incident analysis (August 2022)
Mandiant, Decentralized Robbery: Dissecting the Nomad Bridge Hack and Following the Money (November 2022)
The Block, Nomad’s $190 million bridge exploit drew hacking feeding frenzy of 300 addresses (PeckShield figures, August 2022)
nft now, Nomad Offers Hackers a Chance at Redemption (reporting Nomad’s August 18, 2022 recovery update)
US Federal Trade Commission, FTC Will Require Illusory Systems to Return Money Stolen by Hackers and Implement Information Security Program (December 2025)
Nomad, Replica.sol (patched implementation)






