Cross-chain bridge attack — message-validation bypass (Wormhole class), validator key compromise (Ronin / Harmony class), reentrancy on token-bridge claim, Merkle-proof forgery on optimistic bridges, signature replay across chains. Bridges have lost >$2B; understand why.
67
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Bridges hold large pooled assets and execute messages from a separate trust layer. Architecturally fragile.
| Type | Trust model | Past exploits |
|---|---|---|
| Lock-and-mint | Trusted validators sign mint events | Wormhole ($325M), Ronin ($600M), Harmony ($100M) |
| Burn-and-redeem | Same | Multichain/Anyswap ($126M) |
| Optimistic | Fraud proof window | Nomad ($190M) |
| Light-client / zk | Cryptographic | (Fewer hacks, but newer) |
| Liquidity-network (Hop, Connext) | Routers + relayers | Smaller individual exploits |
The Solana-side verify_signature function trusted the caller to provide a "verified" sigset account. Attacker passed a sysvar that wasn't a real sigset → unauthorized mint of 120k wETH.
Lesson: any verify function whose input is a struct fetched by address is bypassable if address-trust is missing.
9-of-9 validators, 5 of which were operated by one entity. Sky Mavis got phished → attacker controlled 5 keys → arbitrary mint.
Lesson: validator decentralization isn't math, it's operational. Count distinct legal entities and key-storage methods, not just keys.
After an upgrade, the zero-hash was set as a "valid root" — any proof against root = 0 passed. The first attacker withdrew real funds; then thousands of others copy-pasted the same tx with their own address. ~$190M total.
Lesson: check initialize / upgrade scripts for accidental defaults — bytes32(0), address(0), 1 (often the most-tested value) becoming the trusted value.
Bridge tx signed for chain A is replayed on chain B because chainId wasn't bound into the signed message:
function claim(bytes32 nonce, uint256 amount, bytes calldata sig) external {
bytes32 hash = keccak256(abi.encode(nonce, msg.sender, amount)); // ⚠ no chainId
require(ecrecover(hash, ...) == validator);
token.transfer(msg.sender, amount);
}
// On chain A: legit claim
// On chain B (same validator, same token): replay the same sigClaim transfers tokens THEN updates nonce-used mapping:
function claim(...) external {
token.transfer(msg.sender, amount); // attacker token w/ callback
used[nonce] = true; // updated AFTER
}
// ERC777 / ERC1363 / malicious ERC20 callback → re-enter claim same nonceBridges using a quote-and-relay model price slippage via oracle. Manipulate the oracle → arbitrage drains liquidity:
Step 1: Borrow large position on chain A
Step 2: Manipulate underlying spot price oracle that bridge fee uses
Step 3: Initiate cross-chain transfer at the manipulated rate
Step 4: Repay loan, profit = bridge fee discount on a large transferverify_signature trust chain: from where does the verifier load its trust roots? Can the caller spoof them?address(0), bytes32(0), 1 in initialize that becomes a trusted value?# Foundry test against a fork — simulate the exact bridge flow
forge test --fork-url $RPC --match-contract Bridge --fork-block-number 17000000
# Slither — finds reentrancy + signature-replay heuristics
slither <project>
# Echidna — fuzz the bridge claim function with arbitrary calldata
echidna-test test/Bridge.t.sol --config echidna.yaml
# Tenderly — live simulation against deployed bridge contractse34afba
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.