<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 0xrivet</title>
    <description>The latest articles on DEV Community by 0xrivet (@0xrivet).</description>
    <link>https://dev.to/0xrivet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4085937%2F177102d0-7e28-4ee8-bf88-b7cdf309d169.jpg</url>
      <title>DEV Community: 0xrivet</title>
      <link>https://dev.to/0xrivet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0xrivet"/>
    <language>en</language>
    <item>
      <title>What Your Multisig Threshold Actually Protects</title>
      <dc:creator>0xrivet</dc:creator>
      <pubDate>Fri, 21 Aug 2026 18:45:11 +0000</pubDate>
      <link>https://dev.to/0xrivet/what-your-multisig-threshold-actually-protects-4jd</link>
      <guid>https://dev.to/0xrivet/what-your-multisig-threshold-actually-protects-4jd</guid>
      <description>&lt;p&gt;I've been digging into multisig configurations for bridge and protocol security reviews. The threshold gets all the attention — 3-of-5, 4-of-7, whatever. But after checking a few dozen Safes on mainnet, the threshold is rarely the weakest link. There are five other things that determine whether a Gnosis Safe actually protects funds, and most people only check the first one.&lt;/p&gt;

&lt;p&gt;This post walks through all of them, with &lt;code&gt;cast&lt;/code&gt; commands you can run yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the threshold does
&lt;/h2&gt;

&lt;p&gt;The threshold sets the minimum number of owner signatures required to execute a transaction through &lt;code&gt;execTransaction()&lt;/code&gt;. If threshold is 3 and you have 2 signatures, the call reverts. Simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# check threshold and owners&lt;/span&gt;
cast call &amp;lt;SAFE&amp;gt; &lt;span class="s2"&gt;"getThreshold()(uint256)"&lt;/span&gt;
cast call &amp;lt;SAFE&amp;gt; &lt;span class="s2"&gt;"getOwners()(address[])"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part everyone understlse.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the threshold does NOT protect
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Modules
&lt;/h3&gt;

&lt;p&gt;This is the biggest blind spot in multisig security.&lt;/p&gt;

&lt;p&gt;Safe modules are contracts authorFromModule()`. A module can execute*any transaction from the safe without a single owner signature*. The threshold is irrelevant. The module has its own authority.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`bash&lt;/p&gt;

&lt;h1&gt;
  
  
  if this returns anything other than an empty array, investigate
&lt;/h1&gt;

&lt;p&gt;cast call  "getModulesPagin[],address)" \&lt;br&gt;
  0x0000000000000000000000000000000000000001 10&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Modules are legitimate — timelockation. But a malicious or compromisedmodule is a full bypass of every threshold. Your 7-of-10 means nothing if a module can move funds independently.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Guard
&lt;/h3&gt;

&lt;p&gt;A guard contract implements &lt;code&gt;checerExecution()&lt;/code&gt;. It adds validation &lt;em&gt;on top of&lt;/em&gt; the threshold — restricting destinations, limiting values, blocking certain operations.&lt;/p&gt;

&lt;p&gt;The guard address lives at a specific storage slot. If it's &lt;code&gt;0x00&lt;/code&gt;, there's no guard. No additional checks beyond threshold + signatu&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`bash&lt;/p&gt;
&lt;h1&gt;
  
  
  guard storage slot (keccak256("guard_manager.guard.address"))
&lt;/h1&gt;

&lt;p&gt;cast storage  \&lt;br&gt;
  0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8&lt;/p&gt;
&lt;h1&gt;
  
  
  0x000...000 = no guard installe
&lt;/h1&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A guard can enforce things like "no transfers above X without delay" or "no delegatecalls ever."&lt;br&gt;
Without one, the threshold alone lly isn't enough.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Fallback handler
&lt;/h3&gt;

&lt;p&gt;The fallback handler processes annize — &lt;code&gt;EIP-1271&lt;/code&gt; signaturevalidation, token callbacks (&lt;code&gt;onERC721Received&lt;/code&gt;, &lt;code&gt;onERC1155Received&lt;/code&gt;), anything routed through the &lt;code&gt;fallback()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`bash&lt;/p&gt;
&lt;h1&gt;
  
  
  fallback handler slot (keccak256("fallback_manager.handler.address"))
&lt;/h1&gt;

&lt;p&gt;cast storage  \&lt;br&gt;
  0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A compromised fallback handler caion, manipulate token reception logic, or introduce re-entrancy vectors. It runs in the context of calls &lt;em&gt;to&lt;/em&gt; the safe, not calls &lt;em&gt;from&lt;/em&gt; it — a different attack surface terous.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Delegatecall (operation=1)
&lt;/h3&gt;

&lt;p&gt;This is how Bybit lost $1.46B.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;execTransaction&lt;/code&gt; takes an &lt;code&gt;operaular call. &lt;/code&gt;1` is a delegatecall — the target contract's code executes &lt;em&gt;in the safe's storage context&lt;/em&gt;. The target can rewrite ownership, change the threshold, drain everyn.&lt;/p&gt;

&lt;p&gt;The signers approved what appearehe transaction payload contained&lt;code&gt;operation: 1&lt;/code&gt;. The target contract ran &lt;code&gt;DELEGATECALL&lt;/code&gt; in the safe's context, replacing the implementation with an attacker-chold was met. Every signature wasvalid. It just didn't matter — the signers didn't understand what they were signing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# execTransaction signature — noteter
# execTransaction(address to, uint256 value, bytes data, uint8 operation,
#   uint256 safeTxGas, uint256 ba
#   address gasToken, address refundReceiver, bytes signatures)
#
# operation: 0 = Call, 1 = DelegateCall
# if your guard doesn't block openg
# set of signers can delegatecall arbitrary code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a guard that explicitly bold-meeting quorum of signers —whether compromised, socially engineered, or just not reading the payload — can execute a delegatecall that rewrites the en&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Organizational independence
&lt;/h3&gt;

&lt;p&gt;Not on-chain, but worth more than&lt;/p&gt;

&lt;p&gt;Ronin Bridge ran 5-of-9. Four keyfth was an allowlisted key from agas-free RPC arrangement in November 2021 — never revoked. One compromised developer machine yielded 5 of 9 signatures. The threshold s not.&lt;/p&gt;

&lt;p&gt;You can't verify this with &lt;code&gt;cast&lt;/code&gt;wner addresses share a common fundingsource, were deployed by the same address, or interact with the same contracts. Same deployer funding multiple "independent" si&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# check if owners share a funding origin&lt;/span&gt;
cast call &amp;lt;SAFE&amp;gt; &lt;span class="s2"&gt;"getOwners()(add
# for each owner, trace the first inbound ETH transfer
# common source → likely same org
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Safe Safe Checklist
&lt;/h2&gt;

&lt;p&gt;Five things to verify beyond the threshold. I run these on every safe I audit.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Red flag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;modules&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getModulesPaginated()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;any unknown contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;guard&lt;/td&gt;
&lt;td&gt;storage slot read&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;fallback&lt;/td&gt;
&lt;td&gt;storage slot read&lt;/td&gt;
&lt;td&gt;unverified contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;delegatecall&lt;/td&gt;
&lt;td&gt;guard configll restriction&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;independence&lt;/td&gt;
&lt;td&gt;owner funding trace&lt;/td&gt;
&lt;td&gt;common deployer/funder&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full check in one shot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SAFE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;SAFE_ADDRESS&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# threshold + owners&lt;/span&gt;
cast call &lt;span class="nv"&gt;$SAFE&lt;/span&gt; &lt;span class="s2"&gt;"getThreshold()(uint256)"&lt;/span&gt;
cast call &lt;span class="nv"&gt;$SAFE&lt;/span&gt; &lt;span class="s2"&gt;"getOwners()(address[])"&lt;/span&gt;

&lt;span class="c"&gt;# modules (bypass threshold entirely)&lt;/span&gt;
cast call &lt;span class="nv"&gt;$SAFE&lt;/span&gt; &lt;span class="s2"&gt;"getModulesPaginated(address,uint256)(address[],address)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  0x0000000000000000000000000000000000000001 10

&lt;span class="c"&gt;# guard (additional validation layer)&lt;/span&gt;
cast storage &lt;span class="nv"&gt;$SAFE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8

&lt;span class="c"&gt;# fallback handler (processes unknown calls)&lt;/span&gt;
cast storage &lt;span class="nv"&gt;$SAFE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The threshold is the door lock. Modules are the window you forgot to close. The guard is the deadbolt you never installed. The fallback handler is the mail slot big enough to reach through. And delegatecall is the landlord's master key — works even when every lock is engaged.&lt;/p&gt;

&lt;p&gt;Check all five. Or don't, and hope nobody else does either.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;More posts and tooling on &lt;a href="https://0xrivet.xyz" rel="noopener noreferrer"&gt;0xrivet.xyz&lt;/a&gt;. The cast snippets above and some other safe-audit scripts are on &lt;a href="https://github.com/0xrivet" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If you're auditing bridge multisigs or building guard contracts, feel free to reach out — always happy to compare notes.&lt;/em&gt;&lt;br&gt;
cast call $SAFE "getOwners()(addr&lt;/p&gt;

&lt;h1&gt;
  
  
  modules (bypass threshold entir
&lt;/h1&gt;

&lt;p&gt;cast call $SAFE "getModulesPaginated(address,uint256)(address[],address)" \&lt;br&gt;
  0x00000000000000000000000000000&lt;/p&gt;

&lt;h1&gt;
  
  
  guard (additional validation la
&lt;/h1&gt;

&lt;p&gt;cast storage $SAFE \&lt;br&gt;
  0x4a204f620c8c5ccdca3fd54d003ba3c34c8&lt;/p&gt;

&lt;h1&gt;
  
  
  fallback handler (processes unk
&lt;/h1&gt;

&lt;p&gt;cast storage $SAFE \&lt;br&gt;
  0x6c9a6c4a39284e37ed1cf53d337579918d5&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

---                                                                                          
The threshold is the door lock. Modules are the window you forgot to close. The guard is the deadbolt you never installed. The slot big enough to reach through. And delegatecall is the landlord's master key — works even when every lock is engaged.

Check all five. Or don't, and hope nobody else does either.

---

*More posts and tooling on [0xrivet.xyz](https://0xrivet.xyz). The cast snippets above and some other safe-audit scripts are on [rivet). If you're auditing bridgemultisigs or building guard contracts, feel free to reach out — always happy to compare notes.*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>web3</category>
    </item>
    <item>
      <title>Your Bridge's Security Page Is Lying to You</title>
      <dc:creator>0xrivet</dc:creator>
      <pubDate>Thu, 20 Aug 2026 19:41:14 +0000</pubDate>
      <link>https://dev.to/0xrivet/your-bridges-security-page-is-lying-to-you-40pk</link>
      <guid>https://dev.to/0xrivet/your-bridges-security-page-is-lying-to-you-40pk</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;(Originally published at &lt;a href="https://0xrivet.xyz/bridge-trust-score" rel="noopener noreferrer"&gt;https://0xrivet.xyz/bridge-trust-score&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  Bridge Trust Score
&lt;/h2&gt;

&lt;p&gt;Five layers, each scored 0-4. The composite score is the minimum — because attackers go for the weakest one, not the strongest.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it protects&lt;/th&gt;
&lt;th&gt;Where it failed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1 Verification&lt;/td&gt;
&lt;td&gt;Message authenticity&lt;/td&gt;
&lt;td&gt;Wormhole $326M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 Independence&lt;/td&gt;
&lt;td&gt;Validator diversity&lt;/td&gt;
&lt;td&gt;Ronin $625M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3 Upgrade&lt;/td&gt;
&lt;td&gt;Contract mutation&lt;/td&gt;
&lt;td&gt;Nomad $190M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L4 Timelock&lt;/td&gt;
&lt;td&gt;Reaction window&lt;/td&gt;
&lt;td&gt;Harmony $100M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L5 Circuit Breaker&lt;/td&gt;
&lt;td&gt;Drain velocity&lt;/td&gt;
&lt;td&gt;Bybit $1.46B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Security here is multiplicative. One zero makes the whole thing zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  L1 — Verification
&lt;/h2&gt;

&lt;p&gt;How does the destination chain know a message is actually real?&lt;/p&gt;

&lt;p&gt;0 = single signer, 1 = multisig under 5, 2 = 5-12 independent validators, 3 = 13+ or multi-method ISM, 4 = ZK light client.&lt;/p&gt;

&lt;p&gt;The thing nobody talks about — LayerZero V2 DVN configuration is &lt;em&gt;per pathway&lt;/em&gt;. 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:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
cast call &amp;lt;SEND_LIBRARY&amp;gt; \
  "getUlnConfig(address,uint32)((uint64,uint8,uint8,uint8,address[],address[]))" \
  &amp;lt;OAPP_ADDRESS&amp;gt; &amp;lt;DST_EID&amp;gt;

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 &amp;lt;PROXY&amp;gt; \
  0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103

# Step 2: is it an EOA or contract?
cast code &amp;lt;ADMIN&amp;gt; | 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 &amp;lt;TIMELOCK&amp;gt; "getMinDelay()(uint256)"
# 86400 = 24 hours. Looks good.

# But wait — can the admin set it to zero?
cast call &amp;lt;TIMELOCK&amp;gt; "hasRole(bytes32,address)(bool)" \
  $(cast keccak "TIMELOCK_ADMIN_ROLE") &amp;lt;ADMIN&amp;gt;
# 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 &amp;lt;SAFE&amp;gt; "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="&amp;lt;BRIDGE_PROXY&amp;gt;"

# 1 — Upgrade admin
cast storage $B 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103

# 2 — EOA or contract?
cast code &amp;lt;ADMIN&amp;gt; | head -c 10

# 3 — Multisig threshold
cast call &amp;lt;ADMIN&amp;gt; "getThreshold()(uint256)" 2&amp;gt;/dev/null

# 4 — Timelock delay
cast call &amp;lt;ADMIN&amp;gt; "getMinDelay()(uint256)" 2&amp;gt;/dev/null

# 5 — Can it be paused?
cast call $B "paused()(bool)" 2&amp;gt;/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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
