I spent the last few months pulling apart bridge security pages and comparing what they claim to what the contracts actually enforce on-chain. It's not pretty.
This post is the scoring framework I ended up building. Everything here you can verify yourself with cast and an RPC endpoint — no trust required.
(Originally published at https://0xrivet.xyz/bridge-trust-score)
Bridge Trust Score
Five layers, each scored 0-4. The composite score is the minimum — because attackers go for the weakest one, not the strongest.
| Layer | What it protects | Where it failed |
|---|---|---|
| L1 Verification | Message authenticity | Wormhole $326M |
| L2 Independence | Validator diversity | Ronin $625M |
| L3 Upgrade | Contract mutation | Nomad $190M |
| L4 Timelock | Reaction window | Harmony $100M |
| L5 Circuit Breaker | Drain velocity | Bybit $1.46B |
Security here is multiplicative. One zero makes the whole thing zero.
L1 — Verification
How does the destination chain know a message is actually real?
0 = single signer, 1 = multisig under 5, 2 = 5-12 independent validators, 3 = 13+ or multi-method ISM, 4 = ZK light client.
The thing nobody talks about — LayerZero V2 DVN configuration is per pathway. I've seen bridges running 2-of-3 DVN on their main Ethereum→Arbitrum route and 1-of-1 on a smaller chain. The docs never break this down at the pathway level. You have to check:
bash
cast call <SEND_LIBRARY> \
"getUlnConfig(address,uint32)((uint64,uint8,uint8,uint8,address[],address[]))" \
<OAPP_ADDRESS> <DST_EID>
If requiredDVNCount comes back as 1 with 0 optionals — that's a single point of failure right there.
Wormhole has 19 guardians, 13-of-19 quorum. Sounds solid. But in January 2024 a researcher found that the genesis guardian set — one key — was never expired on Wormchain. One key bypassing the entire quorum. It was allegedly destroyed before discovery. "Allegedly"
isn't a security model. They fixed it within 48h, but it had been sitting there for years.
L2 — Independence
Everyone cites Ronin's 5-of-9 but nobody explains the actual mechanism. Four of the nine validators were Sky Mavis infrastructure. The fifth was a signing allowlist from November
2021 that was created for a transaction surge, expired in December, and was never revoked. Lazarus compromised one developer laptop and got 5 of 9 keys.
L3 — Upgrade Authority
Most people check "is it a multisig?" and stop there. Don't.
You need to trace the whole chain:
Proxy → EIP-1967 admin slot → ProxyAdmin → owner()
→ Timelock? → proposer role → Multisig → threshold/owners
# Step 1: who controls upgrades?
cast storage <PROXY> \
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
# Step 2: is it an EOA or contract?
cast code <ADMIN> | head -c 10
# "0x" means EOA. That's a score of 0 right there.
L4 — Timelock
Here's the thing — the delay value isn't what matters. What matters is who can change it.
cast call <TIMELOCK> "getMinDelay()(uint256)"
# 86400 = 24 hours. Looks good.
# But wait — can the admin set it to zero?
cast call <TIMELOCK> "hasRole(bytes32,address)(bool)" \
$(cast keccak "TIMELOCK_ADMIN_ROLE") <ADMIN>
# If true, your timelock is theater.
A timelock whose delay can be zeroed without its own delay is the same as no timelock.
L5 — Circuit Breaker
Bybit lost $1.46B in a single batch. No rate limit triggered. No pause. Nothing.
Most bridges I've checked score 0 here. It's the least deployed layer and probably the one that would've mattered most.
---
Patterns I Keep Seeing
Asymmetric pathways. The flagship route is well-secured. The secondary chain nobody audited runs 1-of-1 DVN. Guess which one the attacker uses.
Timelock illusion. There's a 48h timelock. The admin holds
TIMELOCK_ADMIN_ROLE. One call to updateDelay(0) and it's gone.
Orphaned guardians. Validator set gets rotated on mainnet. Old set never expires on some alt-L1. The Wormchain bug was exactly this.
The Safe blind spot. Threshold and owners get checked. Modules don't. A module can execute transactions without meeting the threshold at all.
cast call <SAFE> "getModules()(address[])"
# If this returns anything — investigate immediately.
---
The 60-Second Version
Five commands. Run these and you'll know more than the security page tells you.
B="<BRIDGE_PROXY>"
# 1 — Upgrade admin
cast storage $B 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
# 2 — EOA or contract?
cast code <ADMIN> | head -c 10
# 3 — Multisig threshold
cast call <ADMIN> "getThreshold()(uint256)" 2>/dev/null
# 4 — Timelock delay
cast call <ADMIN> "getMinDelay()(uint256)" 2>/dev/null
# 5 — Can it be paused?
cast call $B "paused()(bool)" 2>/dev/null
If the admin is an EOA, you already know more than the docs told you.
---
The toolkit that automates these checks is open source:
bridge-security-toolkit.
PRs welcome.
— 0xrivet / 0xrivet.xyz
Top comments (0)