<?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: Carlos | Zealynx</title>
    <description>The latest articles on DEV Community by Carlos | Zealynx (@bloqarl).</description>
    <link>https://dev.to/bloqarl</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%2F4170572%2F70c56d5a-9679-44df-9711-054229f1b848.png</url>
      <title>DEV Community: Carlos | Zealynx</title>
      <link>https://dev.to/bloqarl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bloqarl"/>
    <language>en</language>
    <item>
      <title>Your agent's payment approval can pass at any price</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Thu, 05 Nov 2026 14:16:47 +0000</pubDate>
      <link>https://dev.to/bloqarl/your-agents-payment-approval-can-pass-at-any-price-40ap</link>
      <guid>https://dev.to/bloqarl/your-agents-payment-approval-can-pass-at-any-price-40ap</guid>
      <description>&lt;p&gt;On Saturday morning I was reading a draft standard for human approvals in MCP, the Human Gateway SEP from the &lt;a href="https://github.com/probita-ai/hatp" rel="noopener noreferrer"&gt;HATP&lt;/a&gt; project. The idea is a good one. Before an agent runs a tool that matters, the right person signs off, and the tool server can check that signature offline without trusting the agent or the client.&lt;/p&gt;

&lt;p&gt;The approval is a signature over a digest of the action: the server, the tool and the arguments. The example in the spec is a &lt;code&gt;charge_card&lt;/code&gt; call, and it works because the amount and the payee are arguments. Bob approved this payment, to this payee, for this amount.&lt;/p&gt;

&lt;p&gt;Then I tried it with a paid tool. In x402 over MCP the price isn't an argument. The agent calls the tool, the server answers with an error result carrying its terms (amount in atomic units, token contract, network, &lt;code&gt;payTo&lt;/code&gt;), and the agent calls again with the same arguments plus a signed payment in &lt;code&gt;params._meta["x402/payment"]&lt;/code&gt;. The digest is the same for both calls. So the approval passes at any price and any payee, and the person approving never sees either.&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://github.com/probita-ai/hatp/issues/38" rel="noopener noreferrer"&gt;opened an issue&lt;/a&gt; on 31 October and the maintainer shipped a fix in release 0.1.4 three days later. The same gap is easy to have anywhere a human approval sits in front of an agent payment, so this is what I'd compare in yours.&lt;/p&gt;

&lt;p&gt;Last week I wrote about &lt;a href="https://dev.to/bloqarl/ai-agent-payments-usually-get-the-flow-right-and-the-trust-wrong-1f4c"&gt;where agent payment designs trust the wrong thing&lt;/a&gt; and put up a &lt;a href="https://dev.to/bloqarl/an-agent-payment-security-checklist-from-the-payout-address-to-the-signer-5eee"&gt;full checklist&lt;/a&gt;. This one is only about the approval step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key signs more than the person approves
&lt;/h2&gt;

&lt;p&gt;On EVM chains the x402 &lt;code&gt;exact&lt;/code&gt; scheme pays with an EIP-3009 &lt;code&gt;transferWithAuthorization&lt;/code&gt;. The agent signs typed data with &lt;code&gt;from&lt;/code&gt;, &lt;code&gt;to&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;validAfter&lt;/code&gt;, &lt;code&gt;validBefore&lt;/code&gt; and a nonce, and the EIP-712 domain carries the chain ID and the token contract. So the payment signature is bound to the payee, the amount, the token, the chain and a time window.&lt;/p&gt;

&lt;p&gt;The approval in front of it usually covers less, and it's the only part a person looks at. So put the two next to each other, field by field. Anything the key signs that the approval doesn't cover is something the server or the agent gets to pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amount and payee have to be in what gets approved
&lt;/h2&gt;

&lt;p&gt;The fix adds an optional &lt;code&gt;payment&lt;/code&gt; member to the approved action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eip155:8453"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2500000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x9a3f…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's inside the digest and it's shown in the ask, so the person sees 2.50 USDC on Base to that address instead of "premium_report, ticker ACME".&lt;/p&gt;

&lt;p&gt;I'd copy two details from it. The amount is in atomic units of a named asset. A &lt;code&gt;{ "value": 2.5, "currency": "USD" }&lt;/code&gt; field can't tell USDC on Base from a token with the same symbol on another chain, and a &lt;a href="https://chainagnostic.org/CAIPs/caip-19" rel="noopener noreferrer"&gt;CAIP-19&lt;/a&gt; asset ID can. And it's a maximum, not an exact price, so "up to $5" is the same check with a bigger number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payee can't be the one checking
&lt;/h2&gt;

&lt;p&gt;In the first version the gated server verified the approval. That makes sense when the agent and the client are the parties being supervised. For a paid tool, though, the server is also the payee, and a check that only the payee runs doesn't protect the payer.&lt;/p&gt;

&lt;p&gt;So in 0.1.4 the payer's client or wallet compares the server's terms with the approved &lt;code&gt;payment&lt;/code&gt; before it signs: same network, same asset, same &lt;code&gt;payTo&lt;/code&gt;, and an amount no higher than &lt;code&gt;maxAmount&lt;/code&gt;. If anything differs, it doesn't sign and asks for a new approval with the new terms. The server still checks too, but that catches honest mismatches, not a server that wants more.&lt;/p&gt;

&lt;p&gt;The server side has its mirror image. An x402 server should check the payment against its own price list, not against the copy of the terms the client sends back in &lt;code&gt;accepted&lt;/code&gt;. The stock x402 server compares the two. A hand-rolled one that forwards the client's &lt;code&gt;accepted&lt;/code&gt; to the facilitator lets the client set its own price, and the facilitator will happily confirm it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The window is the field I rarely see on an approval screen
&lt;/h2&gt;

&lt;p&gt;The x402 client doesn't choose how long its payment signature stays valid. The stock clients set &lt;code&gt;validBefore&lt;/code&gt; to now plus the &lt;code&gt;maxTimeoutSeconds&lt;/code&gt; from the server's terms (I checked the TypeScript, Python and Go clients in the x402 repo). A server that asks for 604800 gets a signature it can settle for a week.&lt;/p&gt;

&lt;p&gt;The approval has its own expiry, HATP's is &lt;code&gt;expiresAt&lt;/code&gt;, but that's when the approval stops being accepted. It says nothing about how long the payment it led to can be settled. The amount doesn't change, but the server can hold the signed payment and settle it days after the person who approved it changed their mind.&lt;/p&gt;

&lt;p&gt;HATP's &lt;code&gt;payment&lt;/code&gt; member doesn't carry a window yet, so this is the one field from the signature the approval still doesn't cover. A maximum window, checked next to &lt;code&gt;maxAmount&lt;/code&gt; before signing, would close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A limit per payment isn't a limit in total
&lt;/h2&gt;

&lt;p&gt;Standing approvals ("let the agent pay for this tool this week") are what people ask for next, and a per-payment cap doesn't bound them. 2.50 USDC a call is 250 USDC after a hundred calls, and an agent in a retry loop can make a hundred calls in a minute.&lt;/p&gt;

&lt;p&gt;The approval needs a running total, kept where the payments are signed. The SEP now says that for standing approvals, and I'd ask the same of any wallet guard: does it count, or does it only look at one payment at a time?&lt;/p&gt;

&lt;h2&gt;
  
  
  The fee is part of the price
&lt;/h2&gt;

&lt;p&gt;This one isn't x402. Earlier this week I looked at an ERC-4337 smart account where the owner sets per-call caps and a budget, and an agent holds a session key. The caps covered the calls the key could make. But the session key also signs the gas fields of the user operation (&lt;code&gt;maxFeePerGas&lt;/code&gt;, &lt;code&gt;maxPriorityFeePerGas&lt;/code&gt;, &lt;code&gt;preVerificationGas&lt;/code&gt;), and the fee goes to whoever bundles it. Someone holding a leaked key can bundle one allowed operation themselves at an absurd gas price and take most of the account's ETH as the fee, inside every cap.&lt;/p&gt;

&lt;p&gt;A cap on the gas fields in validation closes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approve once, pay once
&lt;/h2&gt;

&lt;p&gt;x402 turns a paid call into two &lt;code&gt;tools/call&lt;/code&gt; requests with the same arguments. HATP approvals carry a single-use nonce, and the first version didn't say when the server records it. If it records it on the first call, the one it answers with its price, the paid retry comes back as a replay and gets refused. That's not a hole, but it's the kind of bug that gets a check switched off in a hurry. The fix says a nonce is consumed only when the tool actually runs.&lt;/p&gt;

&lt;p&gt;The opposite mistake is worse: a nonce that's never consumed lets one approval pay for many calls. Test both. The retry goes through once, and a third call with the same approval doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field by field
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the key signs&lt;/th&gt;
&lt;th&gt;What the approval should cover&lt;/th&gt;
&lt;th&gt;Checked where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;to&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;payTo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;payer's client or wallet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;maxAmount&lt;/code&gt; in atomic units, plus a running total for standing approvals&lt;/td&gt;
&lt;td&gt;payer's client or wallet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;token contract (EIP-712 domain)&lt;/td&gt;
&lt;td&gt;CAIP-19 asset&lt;/td&gt;
&lt;td&gt;payer's client or wallet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chain ID (EIP-712 domain)&lt;/td&gt;
&lt;td&gt;CAIP-2 network&lt;/td&gt;
&lt;td&gt;payer's client or wallet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;validBefore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a maximum window&lt;/td&gt;
&lt;td&gt;payer's client (not in HATP yet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gas fields (ERC-4337)&lt;/td&gt;
&lt;td&gt;a gas cap&lt;/td&gt;
&lt;td&gt;the account's validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The quickest test I know for your own setup: approve a call, then have your test server raise its price on the retry. If the payment still gets signed, the approval doesn't cover the price.&lt;/p&gt;

&lt;p&gt;I first wrote a short version of this &lt;a href="https://x.com/TheBlockChainer/status/2116581868711870710" rel="noopener noreferrer"&gt;on X&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the review experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>web3</category>
    </item>
    <item>
      <title>Still building? The design questions worth settling before anyone scopes an audit</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Tue, 03 Nov 2026 14:22:01 +0000</pubDate>
      <link>https://dev.to/bloqarl/still-building-the-design-questions-worth-settling-before-anyone-scopes-an-audit-4026</link>
      <guid>https://dev.to/bloqarl/still-building-the-design-questions-worth-settling-before-anyone-scopes-an-audit-4026</guid>
      <description>&lt;p&gt;A lot of teams write to an auditor while the code still changes every few days. The honest answer then is that nobody can give you a real scope yet. An audit reviews one commit, inside a scope, for a fixed number of days, and none of those three are stable while you're still building.&lt;/p&gt;

&lt;p&gt;But that doesn't mean there's nothing to do until the freeze. Many of the serious findings in an audit aren't typos in the code. They're decisions: an admin key that can do more than anyone intended, a pause that also blocks withdrawals, an external call whose return value nobody thought about. Those get decided early, often in a few minutes, and by the time an auditor reads the code they're already in the storage layout and the tests.&lt;/p&gt;

&lt;p&gt;This month I answered a few builders on public forums who were asking exactly these questions before shipping. Three of the threads show it well.&lt;/p&gt;

&lt;h2&gt;
  
  
  A chain deciding who it trusts, before there's a token
&lt;/h2&gt;

&lt;p&gt;On the Polkadot forum, the builder of MADAR Network asked how to keep "one person, one vote" on their Polkadot SDK chain without collecting IDs. A 2-of-3 committee approves operators and runtime upgrades, and they were weighing a bond per operator.&lt;/p&gt;

&lt;p&gt;Another forum member pointed out it's a Sybil and proof-of-personhood problem. I added that a bond can be slashed for equivocation, since double voting is provable onchain, but an operator who just stops voting has signed nothing you can slash. The unbonding delay also has to be longer than the window to report equivocation. And two of the three committee keys could admit their own operators and push an upgrade, so separate keys plus a delay on upgrades closes most of it.&lt;/p&gt;

&lt;p&gt;Their answer: they're still on public testnet with no token, so this goes into the design before mainnet instead of being added later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A private payments vault that changed its redeem function
&lt;/h2&gt;

&lt;p&gt;On the Celo forum, a solo developer started building Envelope in public: tokens locked in a vault, a transferable NFT as the claim ticket, and a zero-knowledge redeem with a nullifier.&lt;/p&gt;

&lt;p&gt;I asked who is really allowed to redeem, because a design that needs both the NFT and a secret lets the two drift apart once the NFT is sold, and whether the recipient and the relayer fee were bound into the proof, so nobody can copy a valid proof from the mempool. A wallet team asked what happens when a user loses the note data.&lt;/p&gt;

&lt;p&gt;The planned contract changed. Redeem will burn the NFT in the same transaction as the proof, a holder's signature will let a relayer submit it so a fresh address needs no gas, and losing the note means losing the funds, so the app has to say that up front. Each of those touches a function signature or a storage layout. Cheap in month one of a four-month build.&lt;/p&gt;

&lt;h2&gt;
  
  
  An approval flow that couldn't say who approved
&lt;/h2&gt;

&lt;p&gt;On the LangChain forum, a developer building an approval flow on LangGraph noticed that the record shows someone resumed the paused graph, not who.&lt;/p&gt;

&lt;p&gt;Besides the identity part, I pointed at a second gap: the paused step runs again from the top on resume, so if the state changes in between, what executes can differ from what the approver saw. Put a hash of the proposed action in the pause and refuse to execute on a mismatch.&lt;/p&gt;

&lt;p&gt;For any agent that can spend or call tools, "who approved what" is a design decision. It's much easier to make before the flow has users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to write down once the design settles
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What it contains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System overview&lt;/td&gt;
&lt;td&gt;One page: what it does, main flows, contracts or components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assets and trust boundaries&lt;/td&gt;
&lt;td&gt;What holds value, and every place your code trusts something it doesn't control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privileged actions&lt;/td&gt;
&lt;td&gt;Every role, what it can do, what it can't, who holds it, which delays apply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assumptions and dependencies&lt;/td&gt;
&lt;td&gt;External contracts with addresses and versions, compiler and library versions, evidence for each assumption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test history&lt;/td&gt;
&lt;td&gt;What's covered, which invariants are checked, what's known to be untested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open questions&lt;/td&gt;
&lt;td&gt;The ones you know are still open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch window&lt;/td&gt;
&lt;td&gt;When, and what's fixed about that date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frozen commit&lt;/td&gt;
&lt;td&gt;Last, when the code stops moving&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Feedback is not an audit
&lt;/h2&gt;

&lt;p&gt;A forum thread or a review call can change a design, and that's worth a lot. It doesn't read the code you deploy and it certifies nothing. Your tests prove what they test. Audit readiness means a reviewer can start on a fixed commit. An independent audit tells you what qualified people found in that commit, within that scope and time, and nothing about later commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing to do today
&lt;/h2&gt;

&lt;p&gt;Pick the one assumption your protocol depends on most that no test checks today. "The committee will never collude." "The lending market never pauses withdrawals." "The agent can't sign above the cap." Write it down, and next to it write what evidence would show it holds.&lt;/p&gt;

&lt;p&gt;If you can't think of the evidence, that's the design question to settle while you're still building.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a piece our team published on &lt;a href="https://www.zealynx.io/research/audit-ops/still-building-before-audit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=still-building-before-audit" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;, which also has three hypothetical worked examples (a vault, an external integration and an agent that spends) and the forum links.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the review experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>ai</category>
    </item>
    <item>
      <title>An agent payment security checklist, from the payout address to the signer</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:30:23 +0000</pubDate>
      <link>https://dev.to/bloqarl/an-agent-payment-security-checklist-from-the-payout-address-to-the-signer-5eee</link>
      <guid>https://dev.to/bloqarl/an-agent-payment-security-checklist-from-the-payout-address-to-the-signer-5eee</guid>
      <description>&lt;p&gt;Yesterday I &lt;a href="https://dev.to/bloqarl/ai-agent-payments-usually-get-the-flow-right-and-the-trust-wrong-1f4c"&gt;wrote up five AI agent payment designs&lt;/a&gt; I'd looked at in October: an x402 facilitator, a swap API for agents, an x402 test suite, a wallet guard and a banking agent. The payments worked in all five. The gap was always in something the payment path accepted without checking.&lt;/p&gt;

&lt;p&gt;The day before that post went out, Binance lost about $2.5 billion from one hot wallet in under four minutes, through an EIP-7702 delegation signed by that wallet's own key.&lt;/p&gt;

&lt;p&gt;So this is the full list I'd go through now before an agent is allowed to move money. Some of it repeats that post, most of it is new.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened to Binance 14
&lt;/h2&gt;

&lt;p&gt;At 20:20:59 UTC an attacker sent one transaction carrying 50 EIP-7702 authorizations. Each was signed by the wallet Etherscan labels "Binance 14", each for a different consecutive nonce, all pointing the wallet at a sweeper contract. Ethereum used the one matching the wallet's next nonce. At 20:24 the first sweep took about 167,464 ETH and roughly 1.6 billion USDT. The wallet stayed delegated until 00:10, and deposits Binance kept routing into it were swept again at 21:04 and 21:44 (&lt;a href="https://www.coindesk.com/tech/2026/10/30/here-s-how-one-ethereum-permission-let-an-attacker-drain-usd2-5-billion-from-binance" rel="noopener noreferrer"&gt;CoinDesk&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Binance says it found no evidence that private keys leaked. How the attacker got the 50 signatures was still under investigation when I checked on 31 October. That part shows up in checks 10, 11, 14 and 15.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where the money goes (4 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The strongest control sets the payout first.&lt;/strong&gt; In the x402 facilitator, a merchant had to sign to change where their vault pays, but the operator deployed the vault and its first payout came from a dashboard setting. A leaked operator key could create vaults that pay someone else before any merchant signs. If the payout is written once at creation, a fix in the factory only covers new contracts, so go back through the old ones too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every contract address is pinned per chain in your config.&lt;/strong&gt; The swap API added recipient and minimum-output checks, but took the bridge's deposit contract address from the same bridge API it was checking. A compromised API could list its own contract and every check would still pass. Alert when the API and your config disagree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calldata gets decoded before anyone signs it.&lt;/strong&gt; If the agent signs calldata an API returned, that API is part of the agent's wallet. Check the target is on an allowlist, the recipient is the user and the minimum output matches the quote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A smart wallet address means nothing on another chain.&lt;/strong&gt; In 2022, 20 million OP sent to Wintermute's address on Optimism went to someone else, because their Safe existed on mainnet but not yet on Optimism and an attacker deployed one there first. Verify ERC-1271 signatures and smart wallet payouts on the chain where the money lands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. What a payment is bound to (4 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Each payment is bound to amount, recipient, resource, token contract and network.&lt;/strong&gt; A server that only checks that a settled payment exists serves whatever route the proof is presented to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Substitution tests run in CI.&lt;/strong&gt; A valid payment for a cheaper route, to another recipient, in another token or on a testnet has to fail against your most expensive mainnet route. The x402 test suite added all of these as checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One payment buys one response.&lt;/strong&gt; Mark it as used atomically before the response goes out, or the same signed payment sent 20 times at once buys 20 responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client spend limits pin the token and the network, not only the number.&lt;/strong&gt; In x402 the 402 response tells the client which token contract and network to pay on, so an endpoint can keep the number the same and change the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. What the agent's key can sign (4 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You have a list of every signature the key can produce.&lt;/strong&gt; Transactions, EIP-712 typed data, Permit and Permit2, plain messages and EIP-7702 authorizations. The wallet guard held unlimited approvals and Permits by default and still approved a type 4 transaction that delegated the whole account. Its author fixed it within three hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keys that should never delegate refuse the authorization payload.&lt;/strong&gt; An authorization is a signature over &lt;code&gt;keccak256(0x05 || rlp([chain_id, address, nonce]))&lt;/code&gt;, with no recipient and no amount in it, and &lt;code&gt;chain_id&lt;/code&gt; 0 makes it valid everywhere. A hot wallet has no reason to sign one. A rule that only blocks type 4 transactions &lt;em&gt;sent from&lt;/em&gt; your hot wallet wouldn't have fired at Binance, because the attacker sent the transaction and only the signatures came from Binance 14's key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The signer decodes what it signs.&lt;/strong&gt; An MPC policy engine or HSM that signs whatever 32-byte hash it's handed can't apply any policy. Have it build the hash itself from the decoded request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The guard is the only way to the key.&lt;/strong&gt; A client-side guard only sees what goes through the client it wraps. If the agent or another service can reach the raw key, the guard never sees the signature.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Watching the account (3 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The wallet reads its own code before each send.&lt;/strong&gt; A delegated account's &lt;code&gt;eth_getCode&lt;/code&gt; returns &lt;code&gt;0xef0100&lt;/code&gt; followed by the delegate's address. Stop if it points somewhere you don't expect. The wallet guard shipped this two days after I suggested it, with a list of expected delegates so teams that delegate on purpose don't have to turn it off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funds stop flowing into a wallet whose code changed.&lt;/strong&gt; Whatever watches your hot wallets' balances can call &lt;code&gt;eth_getCode&lt;/code&gt; every block too. It wouldn't have stopped Binance 14's first sweep, but it could have stopped deposits going in before 21:04 and 21:44.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The revoke is ready, and so is the nonce math.&lt;/strong&gt; An authorization to &lt;code&gt;address(0)&lt;/code&gt; clears a delegation. But authorizations signed for later nonces stay valid until the account's nonce passes them, and the Binance 14 transaction carried 50 of them. After a revoke, push the nonce past the highest one an attacker could hold, and if the signer itself is compromised, move the funds and retire the key.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. What the model and the human see (2 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The model writes no free text that leaves your system.&lt;/strong&gt; Transfer notes, memos, email bodies, metadata. The banking agent kept the payee and amount out of the model's hands, but the model still wrote the transfer note, which lands with someone outside the bank. Let the customer fill it on the confirm screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The human approves what gets signed, rendered by code.&lt;/strong&gt; Token contract, network, amount, recipient and expiry, built from the payload, not the agent's summary, on a screen outside the chat.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Operators, tools and logs (3 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Operator keys that can set payouts are treated as treasury keys.&lt;/strong&gt; Separate, monitored, rotated, and every payout they set raises an alert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only is enforced by the database role.&lt;/strong&gt; dbhub's read-only mode (&lt;a href="https://github.com/advisories/GHSA-mwwr-p57h-56pf" rel="noopener noreferrer"&gt;GHSA-mwwr-p57h-56pf&lt;/a&gt;) never reached the connection, so a &lt;code&gt;SELECT&lt;/code&gt; calling a function with side effects could still write. And a read tool next to a tool that sends anything out can still leak what it reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every payment decision is logged with its inputs.&lt;/strong&gt; Which requirements, which quote, which upstream answer, which approval, which signature.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before you ship
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Draw the path from the agent's decision to the money landing, and mark every value that comes from outside your code.&lt;/li&gt;
&lt;li&gt;Run the substitution tests from check 6 against your real routes on a testnet.&lt;/li&gt;
&lt;li&gt;Send your signer one EIP-7702 authorization request and see whether it signs.&lt;/li&gt;
&lt;li&gt;Write down who can revoke a delegation, rotate a key and stop routing funds, and how long each takes.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Adapted from a checklist our team published on &lt;a href="https://www.zealynx.io/research/adversarial-security/agent-payment-security-checklist?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-payment-security-checklist" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the review experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>One EIP-7702 signature hands over a whole wallet. What to check if you build on it</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Fri, 30 Oct 2026 14:14:05 +0000</pubDate>
      <link>https://dev.to/bloqarl/one-eip-7702-signature-hands-over-a-whole-wallet-what-to-check-if-you-build-on-it-3998</link>
      <guid>https://dev.to/bloqarl/one-eip-7702-signature-hands-over-a-whole-wallet-what-to-check-if-you-build-on-it-3998</guid>
      <description>&lt;p&gt;This week I was reading an open-source guard for agent wallets. It checked unlimited approvals, Permit signatures and long expiries, the usual suspects. It had no idea EIP-7702 authorizations existed. The author fixed it within a few hours, but it stuck with me, because that signature is the most powerful one an Ethereum key can produce now, and some tooling still treats it as an edge case.&lt;/p&gt;

&lt;p&gt;Since Pectra went live on mainnet on May 7, 2025, any EOA can point its address at a contract with one signature. After that, every call to the address runs that contract's code with the EOA's balance and storage. The wallet keeps its address and its key, and it also becomes a smart account.&lt;/p&gt;

&lt;p&gt;That's a great feature. It also moved the wallet attack surface overnight. Here's what I'd check if you write a delegate contract, a wallet, or a protocol that takes calls from EOAs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the delegation works
&lt;/h2&gt;

&lt;p&gt;EIP-7702 adds transaction type &lt;code&gt;0x04&lt;/code&gt;. It carries an &lt;code&gt;authorization_list&lt;/code&gt;, and each entry is a tuple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;chain_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y_parity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signature covers &lt;code&gt;keccak256(0x05 || rlp([chain_id, address, nonce]))&lt;/code&gt;. Whoever signed it is the account being delegated. That signer doesn't have to be the one sending the transaction, which is what makes sponsored transactions possible. It's also what lets a drainer submit an authorization a user signed on a phishing site five minutes ago.&lt;/p&gt;

&lt;p&gt;Once a tuple is processed, the account's code becomes 23 bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xef0100 || address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the delegation designator. Calls to the account (and a transaction sent to it) load the code at &lt;code&gt;address&lt;/code&gt; and run it in the account's context. Code-reading opcodes don't follow the pointer: &lt;code&gt;EXTCODESIZE&lt;/code&gt; returns 23 and you read the designator itself, which is how you tell a delegated account apart.&lt;/p&gt;

&lt;p&gt;The delegation stays until the owner signs a new one. Delegating to &lt;code&gt;address(0)&lt;/code&gt; clears the code and the account is a plain EOA again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signature is the deployment
&lt;/h2&gt;

&lt;p&gt;With a normal smart account you deploy a contract and then use it. Here the signature is the whole deployment. The tuple has a chain id, a target and a nonce. No scope, no expiry, no value cap. Whoever signs it hands their address to whatever code lives at &lt;code&gt;address&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It went the way you'd expect. In May 2025 Scam Sniffer flagged a user who lost about $146,000 to malicious 7702 batch transactions tied to Inferno Drainer: the account got delegated and the tokens moved out in one go. On August 24, 2025, a similar batch took $1.54 million in wstETH, cbBTC and NFTs from a single victim.&lt;/p&gt;

&lt;p&gt;There's a second pattern that often gets mixed with this one. A month after Pectra, Wintermute found that over 97% of all 7702 delegations on mainnet pointed to copies of the same sweeper contract. Those were accounts whose keys had already leaked. 7702 didn't steal the keys, it just made sweeping every incoming deposit cheaper than running a frontrunning bot. Different problem, same signature.&lt;/p&gt;

&lt;p&gt;If you build a wallet, the authorization deserves its own signing screen, with the target contract named in plain words. It's a bigger grant than any approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  The delegate contract is the wallet's whole security
&lt;/h2&gt;

&lt;p&gt;Even when a user delegates on purpose to the contract they meant to, that contract is now the only thing standing between the account and anyone who calls it. The simplest failure is a function with no access control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract VulnerableDelegate {
    function execute(address to, uint256 value, bytes calldata data) external payable {
        (bool ok, ) = to.call{value: value}(data);
        require(ok);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone who knows a delegated address can call &lt;code&gt;execute&lt;/code&gt; and send its funds anywhere. The user authorized the contract, not each call, so every call has to be authenticated inside it.&lt;/p&gt;

&lt;p&gt;The spec's "Secure delegation" section spells out what the account's signature over a call has to cover, and it's a good review checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A nonce&lt;/strong&gt;, or the same signature replays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;value&lt;/code&gt;&lt;/strong&gt;, or a sponsor can change how much ETH goes out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gas&lt;/code&gt;&lt;/strong&gt;, or a sponsor can starve the call so it fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;target&lt;/code&gt; and &lt;code&gt;calldata&lt;/code&gt;&lt;/strong&gt;, or a sponsor can route the call anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there's initialization. A delegation runs no constructor and no initcode, so delegates use an &lt;code&gt;initialize&lt;/code&gt; function, the same as proxies. If the delegation and the &lt;code&gt;initialize&lt;/code&gt; call land in separate transactions, someone can call &lt;code&gt;initialize&lt;/code&gt; first with their own owner. Batch them in the same transaction, or make &lt;code&gt;initialize&lt;/code&gt; check a signature from the account's own key.&lt;/p&gt;

&lt;p&gt;And storage. The delegation can be swapped, the account's storage can't. If the user moves from a delegate that keeps a &lt;code&gt;bool&lt;/code&gt; in slot 0 to one that keeps a &lt;code&gt;uint256&lt;/code&gt; there, the new one starts with garbage. Use ERC-7201 namespaced storage and assume you won't be the last delegate this account ever has. It's the same discipline as upgradeable proxies.&lt;/p&gt;

&lt;h2&gt;
  
  
  chain_id = 0
&lt;/h2&gt;

&lt;p&gt;An authorization with &lt;code&gt;chain_id = 0&lt;/code&gt; is valid on every EVM chain. The spec allows it on purpose, so a wallet can ask for one signature that delegates to the same contract address everywhere.&lt;/p&gt;

&lt;p&gt;The catch is that the user signs once and the attacker gets to choose where it lands. The nonce in the tuple has to match the account's nonce on that chain, so it doesn't apply everywhere at once. But it stays valid on any chain where the nonce matches, today or whenever the account's nonce gets there. One phished signature is a takeover waiting on every chain the user touches, as long as the same contract address holds attacker code there.&lt;/p&gt;

&lt;p&gt;My view: wallets should refuse to sign &lt;code&gt;chain_id = 0&lt;/code&gt; by default, and delegates shouldn't ask for it unless their code really behaves the same on every chain they might be deployed to.&lt;/p&gt;

&lt;h2&gt;
  
  
  If your contract takes calls from EOAs
&lt;/h2&gt;

&lt;p&gt;This is the part that affects people who never touch a delegate.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;require(msg.sender == tx.origin)&lt;/code&gt; used to mean "this is the top frame of the transaction". The spec says plainly that 7702 breaks that. A delegated account can be &lt;code&gt;tx.origin&lt;/code&gt; and still run arbitrary code, so if you used the check as a reentrancy guard or an anti-bot gate, it no longer holds for those users. Use a real reentrancy lock (&lt;code&gt;ReentrancyGuard&lt;/code&gt; or a transient-storage mutex) for the first, and accept that the second is gone.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;code.length&lt;/code&gt; checks flip the other way. A delegated account has 23 bytes of code, so anything that treats &lt;code&gt;code.length &amp;gt; 0&lt;/code&gt; as "this is a contract" now sees your user as a contract. Two places I'd look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ETH pushed to users.&lt;/strong&gt; A plain &lt;code&gt;call{value: x}&lt;/code&gt; to a delegated account runs its delegate's &lt;code&gt;receive&lt;/code&gt; or &lt;code&gt;fallback&lt;/code&gt;. That can revert, burn gas or re-enter. If a loop pays a list of users, one delegated user can now break the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;safeTransferFrom&lt;/code&gt; and &lt;code&gt;_safeMint&lt;/code&gt;.&lt;/strong&gt; OpenZeppelin's ERC-721 calls &lt;code&gt;onERC721Received&lt;/code&gt; whenever the recipient has code. If the user's delegate doesn't implement it, minting to that user reverts. Delegate authors should implement the token receiver hooks, and protocols should know some users will fail here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you really need to know whether a caller is delegated, check the designator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function isDelegated(address account) internal view returns (bool) {
    bytes memory code = account.code;
    return code.length == 23
        &amp;amp;&amp;amp; code[0] == bytes1(0xef)
        &amp;amp;&amp;amp; code[1] == bytes1(0x01)
        &amp;amp;&amp;amp; code[2] == bytes1(0x00);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since London no contract can be deployed with code starting with &lt;code&gt;0xef&lt;/code&gt; (EIP-3541), so the prefix is unambiguous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sponsors and paymasters
&lt;/h2&gt;

&lt;p&gt;7702 was designed to work with ERC-4337, so a delegated EOA can be the sender of a UserOperation and validation runs its delegate's code. If you run a paymaster that sponsors "accounts using our wallet", remember the user can swap the delegate with one signature at any time. Checking the implementation once at onboarding isn't enough. Read the account's code (&lt;code&gt;0xef0100&lt;/code&gt; plus the delegate address) when you sponsor, and only pay for delegates you know.&lt;/p&gt;

&lt;p&gt;The same goes for any guard that signs on a user's behalf. A delegated account's code tells you its current delegate. If it points somewhere you don't expect, stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Writing a delegate&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is every call authenticated, with the signature covering nonce, value, gas, target and calldata?&lt;/li&gt;
&lt;li&gt;Is &lt;code&gt;initialize&lt;/code&gt; batched with the delegation or signed by the account's key?&lt;/li&gt;
&lt;li&gt;Is storage namespaced (ERC-7201)?&lt;/li&gt;
&lt;li&gt;Do you implement the token receiver hooks?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Building a wallet&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the 7702 authorization get its own screen, with the target named?&lt;/li&gt;
&lt;li&gt;Do you refuse &lt;code&gt;chain_id = 0&lt;/code&gt; by default?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Writing a protocol&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anything relying on &lt;code&gt;tx.origin == msg.sender&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Anything that treats &lt;code&gt;code.length &amp;gt; 0&lt;/code&gt; as "contract", including ETH pushes and safe transfers to users?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've shipped a delegate already, I'm curious what you did about initialization: batched with the delegation, or a signature check?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a piece our team published on &lt;a href="https://www.zealynx.io/research/smart-contracts/eip-7702-wallet-security?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=eip-7702-wallet-security" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ethereum</category>
      <category>solidity</category>
      <category>web3</category>
    </item>
    <item>
      <title>AI agent payments usually get the flow right and the trust wrong</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Fri, 30 Oct 2026 07:52:52 +0000</pubDate>
      <link>https://dev.to/bloqarl/ai-agent-payments-usually-get-the-flow-right-and-the-trust-wrong-1f4c</link>
      <guid>https://dev.to/bloqarl/ai-agent-payments-usually-get-the-flow-right-and-the-trust-wrong-1f4c</guid>
      <description>&lt;p&gt;Over the last two weeks I kept running into AI agent payment projects: a pay-per-call facilitator, a swap API built for agents, a test suite for x402 payments, a guard that sits between an agent and its wallet, and a banking agent.&lt;/p&gt;

&lt;p&gt;The payments worked in all five. The gap was somewhere else, in something the payment path trusted without checking, and it was never the model.&lt;/p&gt;

&lt;p&gt;Every team took the feedback well and most shipped a fix within days, so this isn't about anyone being careless. It's a blind spot that's easy to have when the model gets all the attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who decides where the money goes
&lt;/h2&gt;

&lt;p&gt;An x402 facilitator live on mainnet gives each merchant their own vault contract, which pulls the payment and splits it in one transaction, the net to the merchant and the fee to the protocol.&lt;/p&gt;

&lt;p&gt;To change where a vault pays, the merchant has to sign. That's the strong control. But the operator deploys the vault, and the first payout address comes from what was saved in the dashboard. The docs call that setting a preference, not an authority. You only see that it decides the first payments in the vault code, not in the docs.&lt;/p&gt;

&lt;p&gt;So the weaker path picks where the money goes, and the merchant's signature only matters after that. If the operator key leaks, every merchant who hasn't been paid yet can get a vault that pays someone else.&lt;/p&gt;

&lt;p&gt;The dev agreed and is fixing it so operator-created vaults always pay the merchant. If you ever fix something like this, remember the payout is written once, when the vault is created. A fix in the factory only covers new vaults. The existing ones keep whatever they were given, so you go through the creation events and ask those merchants to confirm.&lt;/p&gt;

&lt;h2&gt;
  
  
  If the address comes from the API, it's not a check
&lt;/h2&gt;

&lt;p&gt;Then there was a free swap API and MCP server for agents. It asks several aggregators and bridges for quotes, ranks the routes and returns calldata, and the agent signs whatever comes back.&lt;/p&gt;

&lt;p&gt;That makes the upstream APIs part of the agent's wallet. If one of them is compromised, it can hand back calldata that sends the swap to someone else, and every agent using the API signs it.&lt;/p&gt;

&lt;p&gt;I suggested decoding the calldata on the server first: the router is on an allowlist, the recipient is the user, and the minimum output matches the quote. They shipped it within a day.&lt;/p&gt;

&lt;p&gt;Then they hit the harder case, a bridge where the recipient is bound off chain. Their fix was good: fetch the order, recompute its id, check the recipient and the minimum amount, and require the deposit to go to the bridge's deposit contract. But the deposit contract address came from the same bridge API at runtime. A compromised API could list its own contract, and every order check would still pass, because the funds never reach the real one.&lt;/p&gt;

&lt;p&gt;Pin addresses like that per chain in your config, and alert when the API disagrees.&lt;/p&gt;

&lt;h2&gt;
  
  
  "A payment exists" is not "this payment paid for this"
&lt;/h2&gt;

&lt;p&gt;This one is an open-source CI suite that injects faults into x402 settlement on Solana, so a server can test how it handles bad payments. The author asked which checks were missing.&lt;/p&gt;

&lt;p&gt;I'd start with a valid payment made for a cheaper route, or to a different recipient, replayed against a pricier route. A server that only checks that a settled payment exists will serve it.&lt;/p&gt;

&lt;p&gt;They took it as tests for amount, recipient and resource substitution. Then I asked about the same amount paid in a different token, or on devnet, replayed against a mainnet route. Checks that only compare amount and recipient let that through. They shipped both as new checks in their next release.&lt;/p&gt;

&lt;p&gt;The same thing comes up on the client side. In x402 the 402 response tells the agent which token contract and which network to pay on. So a spend limit that pins the recipient but not the asset and the network can be walked around by an endpoint that keeps the number the same and changes the token. And when a human approves a payment, they should see the exact requirements that get signed, not the agent's summary of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signature your guard doesn't know about
&lt;/h2&gt;

&lt;p&gt;An open-source guard for agent wallets holds unlimited approvals, Permit signatures and long expiries by default, which are the ones that usually bite.&lt;/p&gt;

&lt;p&gt;The one missing was EIP-7702. An authorization is a signature over a chain id, an address and a nonce. It isn't typed data and it isn't a normal transaction, but it delegates the whole account to a contract, which is worse than any unlimited approval. And a chain id of 0 makes it valid on every chain.&lt;/p&gt;

&lt;p&gt;The guard approved a type 4 transaction even when its authorization list delegated the account, and the framework plugin didn't look at the authorization list at all. The author fixed both and published within three hours.&lt;/p&gt;

&lt;p&gt;He was also honest about the limit: the guard only sees what goes through the wrapped client. If the agent can reach the raw key, nothing on the client side sees the authorization. One thing a guard can still do on its own is read its account's code before each send. A delegated account returns 0xef0100 followed by the delegate's address. If that points somewhere you don't expect, stop signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The text the model still writes
&lt;/h2&gt;

&lt;p&gt;The banking agent was already well built. The customer types the payee and the amount, and the confirm screen sits outside the chat, so the model can't misdescribe the transfer.&lt;/p&gt;

&lt;p&gt;But the transfer note is still written by the model, and it leaves the bank and lands with someone outside. A model that read a malicious message can put the customer's balance or another account number in the note of a small transfer. It's the same problem as email tools where the recipient is pinned but the model writes the body.&lt;/p&gt;

&lt;p&gt;The simplest fix I can think of is a note field on the confirm screen that the customer fills in. Then the model never writes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I check now
&lt;/h2&gt;

&lt;p&gt;These are the questions I ask about an agent payment design now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Who can set where the money goes, and in what order?&lt;/strong&gt; If a weaker control decides first and the strong one only applies later, the weak one is the real control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does every address come from?&lt;/strong&gt; If it comes from the same API you're checking, it's not a check. Pin it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is the payment bound to?&lt;/strong&gt; Amount, recipient, resource, token and network. Not just "a settled payment exists".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which signatures can the agent's key produce?&lt;/strong&gt; Transactions, typed data, permits and EIP-7702 authorizations. A guard that only knows some of them isn't a guard for the others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What free text does the model still write that reaches someone outside?&lt;/strong&gt; Notes, memos, email bodies, metadata. That's a way to send data out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of the fixes needed a smarter model.&lt;/p&gt;

&lt;p&gt;If you're building agent payments, I'm curious which address in your flow still comes from an API you don't control.&lt;/p&gt;

&lt;p&gt;I first wrote a short version of this &lt;a href="https://x.com/TheBlockChainer/status/2114410387328102707" rel="noopener noreferrer"&gt;on X&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the review experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Six ways ERC-4337 smart accounts fail in audits</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Tue, 27 Oct 2026 14:11:34 +0000</pubDate>
      <link>https://dev.to/bloqarl/six-ways-erc-4337-smart-accounts-fail-in-audits-1gac</link>
      <guid>https://dev.to/bloqarl/six-ways-erc-4337-smart-accounts-fail-in-audits-1gac</guid>
      <description>&lt;p&gt;A normal Ethereum wallet has one rule: whoever holds the key controls the account. Blunt, but predictable.&lt;/p&gt;

&lt;p&gt;ERC-4337 swaps that rule for a function you write, &lt;code&gt;validateUserOp&lt;/code&gt;. Whatever logic sits in there is now the account's whole security. The EntryPoint will call it, the bundler will forward whatever passes it, and if it's wrong, nothing downstream saves you. Something that used to be protected by cryptography is now protected by code, and code has bugs.&lt;/p&gt;

&lt;p&gt;So when I review a smart account, that's where I start. Trail of Bits published their own list of six common mistakes in 4337 accounts in March, after auditing dozens of them, and a lot of it matches what we run into. These are the six I'd check first, with what they look like in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The signature doesn't cover the whole UserOperation
&lt;/h2&gt;

&lt;p&gt;The classic one: a team ports an EOA-style signature check into &lt;code&gt;validateUserOp&lt;/code&gt; and only signs part of the operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Vulnerable: the signature only covers callData
function validateUserOp(
    UserOperation calldata userOp,
    bytes32 userOpHash,
    uint256 missingAccountFunds
) external returns (uint256 validationData) {
    bytes32 hash = keccak256(abi.encode(userOp.callData));
    address recovered = ECDSA.recover(hash, userOp.signature);
    if (recovered != owner) return SIG_VALIDATION_FAILED;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EntryPoint checks and increments the nonce in the UserOperation, but this signature doesn't cover the nonce. So anyone who sees a valid operation can resubmit the same signature with the next nonce and run the same call again, on the same chain. The gas fields aren't covered either, so they can also set the gas price as high as they like and the account pays it from its deposit. And if the account sits at the same address on another chain (common with &lt;code&gt;CREATE2&lt;/code&gt;), the operation works there too.&lt;/p&gt;

&lt;p&gt;The fix is to verify the signature against &lt;code&gt;userOpHash&lt;/code&gt;, which the EntryPoint already gives you. It covers every field of the operation except the signature, plus the EntryPoint address and the chain id.&lt;/p&gt;

&lt;p&gt;Same idea for &lt;code&gt;isValidSignature&lt;/code&gt; (ERC-1271): if two accounts share an owner key, a signature made for one can pass on the other unless the hash includes the account address and the chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A paymaster that pays for anything
&lt;/h2&gt;

&lt;p&gt;Paymasters pay gas for other people. That sentence alone should start a threat-modeling session.&lt;/p&gt;

&lt;p&gt;The common failure is a paymaster that decides to sponsor in &lt;code&gt;validatePaymasterUserOp&lt;/code&gt; based only on who the sender is. "Is this user on our allowlist?" Yes, so it pays, whatever the &lt;code&gt;callData&lt;/code&gt; does and however much gas it asks for. An allowlisted user (or anyone who controls an allowlisted account) can then burn through the paymaster's deposit in the EntryPoint.&lt;/p&gt;

&lt;p&gt;What I'd check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does validation look at the target, the function selector and the gas limits, not just the sender?&lt;/li&gt;
&lt;li&gt;If you charge the user in &lt;code&gt;postOp&lt;/code&gt; (an ERC-20 paymaster pulling tokens, for example), what happens when &lt;code&gt;postOp&lt;/code&gt; fails? The user can make it fail, for example by revoking the token allowance inside the same operation. The execution reverts, and the paymaster has still paid the gas (since EntryPoint v0.7 there's no second &lt;code&gt;postOp&lt;/code&gt; call to clean up). Getting paid shouldn't depend only on &lt;code&gt;postOp&lt;/code&gt; succeeding.&lt;/li&gt;
&lt;li&gt;What does it cost an attacker to drain the deposit? If the answer is "less than the deposit", you need more limits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. The packed return value
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;validateUserOp&lt;/code&gt; doesn't return a simple true or false. It returns a &lt;code&gt;uint256&lt;/code&gt; with three things packed in it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The low 20 bytes: &lt;code&gt;0&lt;/code&gt; means the signature is valid, &lt;code&gt;1&lt;/code&gt; means it failed, and anything else is read as the address of an aggregator.&lt;/li&gt;
&lt;li&gt;The next 6 bytes: &lt;code&gt;validUntil&lt;/code&gt;, a timestamp, where &lt;code&gt;0&lt;/code&gt; means it never expires.&lt;/li&gt;
&lt;li&gt;The next 6 bytes: &lt;code&gt;validAfter&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistakes I see around this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reverting on a wrong signature.&lt;/strong&gt; The spec says to return &lt;code&gt;1&lt;/code&gt; (&lt;code&gt;SIG_VALIDATION_FAILED&lt;/code&gt;) on a signature mismatch and revert only on other errors. Reverting breaks gas estimation for your users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packing by hand and swapping &lt;code&gt;validUntil&lt;/code&gt; and &lt;code&gt;validAfter&lt;/code&gt;.&lt;/strong&gt; The window you meant to allow is not the window the EntryPoint enforces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting that &lt;code&gt;validUntil = 0&lt;/code&gt; means forever.&lt;/strong&gt; A session key whose expiry never got set is a key that never expires.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the &lt;code&gt;_packValidationData&lt;/code&gt; helper from the reference implementation, and write a test that runs your return values through the real EntryPoint, not a mock.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Modules anyone can install
&lt;/h2&gt;

&lt;p&gt;More and more accounts use modules (validation modules, execution modules, fallback handlers, ERC-7579 style). Composable, yes, but the account becomes only as safe as the weakest module installed on it.&lt;/p&gt;

&lt;p&gt;The specific failure: module installation without access control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Vulnerable: no access control on module installation
function installModule(address module, bytes calldata initData) external {
    IModule(module).onInstall(initData);
    _modules[module] = true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anyone can call that, an attacker installs a validation module that says yes to every signature, and the account is theirs. Installing a module should go through the same validation as any other privileged action, and every module you allow deserves the same review as your own &lt;code&gt;validateUserOp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Validation that reads storage it shouldn't
&lt;/h2&gt;

&lt;p&gt;Bundlers simulate your validation before including an operation, and they enforce rules (now in ERC-7562) on what validation can touch. Roughly: the account's own storage, and slots in other contracts that are tied to the account's address, like a token balance keyed by the account. The rules exist so an operation can't pass simulation and then fail on chain because someone changed shared state in between.&lt;/p&gt;

&lt;p&gt;Read a shared price oracle or a third-party registry during validation and compliant bundlers will drop your operations. The annoying part is that it all passes locally, and teams find out in production when operations quietly never land. Run your operations through a real bundler on testnet before mainnet, and call &lt;code&gt;eth_estimateUserOperationGas&lt;/code&gt; against it during testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Social recovery that can be griefed
&lt;/h2&gt;

&lt;p&gt;Social recovery is one of the best things about smart accounts, and one of the most often broken. Usually it's not theft, it's a way to lock the owner out.&lt;/p&gt;

&lt;p&gt;The pattern: any single guardian can start a recovery, the threshold is needed to finish it, and starting a new one resets the timer. A single bad guardian can keep restarting the clock and block a real recovery forever, for the price of gas.&lt;/p&gt;

&lt;p&gt;What I'd check: can one guardian restart or block the process on their own? Is there a delay on ownership changes, so the real owner has time to cancel? What happens if a recovery starts while another one is still open?&lt;/p&gt;

&lt;h2&gt;
  
  
  What the bundler and EntryPoint don't do for you
&lt;/h2&gt;

&lt;p&gt;A misconception I hear a lot: "the bundler and the EntryPoint are a security layer". They're not. They enforce the 4337 protocol rules, not your business logic. An operation that passes your &lt;code&gt;validateUserOp&lt;/code&gt; will be executed even if it drains the account, installs a malicious module or hands ownership to someone else. Protocol conformance is their job. The rest is yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent problem
&lt;/h2&gt;

&lt;p&gt;More and more AI agents use a 4337 account as their wallet: the agent builds UserOperations, the account validates and executes them. That changes the threat model. If the agent can be prompt-injected, the attacker doesn't need the key. They only need to change what the agent wants to do, and the agent will sign it correctly.&lt;/p&gt;

&lt;p&gt;So "the signature is valid, so the action is authorized" stops being true. For agent-controlled accounts, the limits have to live in the account: spending caps per period, allowed targets and functions, rate limits, in &lt;code&gt;validateUserOp&lt;/code&gt; or in a dedicated module. Not in the agent's system prompt. I think a lot of review time in the next year goes exactly here.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the signature checked against &lt;code&gt;userOpHash&lt;/code&gt;, and nothing narrower?&lt;/li&gt;
&lt;li&gt;Do you return &lt;code&gt;1&lt;/code&gt; on a bad signature instead of reverting?&lt;/li&gt;
&lt;li&gt;Are &lt;code&gt;validUntil&lt;/code&gt; and &lt;code&gt;validAfter&lt;/code&gt; packed with the helper and tested against the real EntryPoint?&lt;/li&gt;
&lt;li&gt;Does validation read only storage the bundler rules allow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Paymaster&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is sponsorship tied to targets, selectors and gas limits, not just the sender?&lt;/li&gt;
&lt;li&gt;Does the paymaster still come out fine if &lt;code&gt;postOp&lt;/code&gt; fails?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Modules&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does installing a module need the owner's authorization?&lt;/li&gt;
&lt;li&gt;Can a module override core validation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recovery&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can one guardian grief the process?&lt;/li&gt;
&lt;li&gt;Is there a delay on ownership changes that the owner can use to cancel?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've built on 4337 or ERC-7579 modules, which part was hardest to get right for you? I'm curious whether it's the paymaster or the validation rules that bite people most.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a piece I wrote on &lt;a href="https://www.zealynx.io/research/smart-contracts/erc-4337-smart-accounts-six-failure-modes-we?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=erc-4337-failure-modes" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ethereum</category>
      <category>solidity</category>
      <category>web3</category>
    </item>
    <item>
      <title>Three things that break when your vault takes cross-chain deposits</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:12:20 +0000</pubDate>
      <link>https://dev.to/bloqarl/three-things-that-break-when-your-vault-takes-cross-chain-deposits-4218</link>
      <guid>https://dev.to/bloqarl/three-things-that-break-when-your-vault-takes-cross-chain-deposits-4218</guid>
      <description>&lt;p&gt;If your ERC-4626 vault is about to accept deposits from other chains, this one is for you.&lt;/p&gt;

&lt;p&gt;On October 8, Chainlink launched &lt;a href="https://chain.link/blog/introducing-ccip-vault-adapters" rel="noopener noreferrer"&gt;CCIP Vault Adapters&lt;/a&gt;. A standard ERC-4626 vault on one chain can now accept deposits from more than 80 others, while the accounting, governance and risk parameters stay on its home chain. Aave, Lombard, Veda and Venus are among the first users.&lt;/p&gt;

&lt;p&gt;Four days earlier, S&amp;amp;P Global Ratings launched a &lt;a href="https://press.spglobal.com/2026-10-04-S-P-Global-Ratings-launches-Vault-Risk-Assessment-for-digital-asset-markets" rel="noopener noreferrer"&gt;Vault Risk Assessment&lt;/a&gt;. It scores a lending vault on six factors, and two of them are about code: protocol risk, and vault security and governance. "A strong score in one factor does not offset a material weakness in another," one of S&amp;amp;P's analysts &lt;a href="https://cointelegraph.com/news/sp-global-launches-risk-assessments-for-10b-crypto-lending-vault-market" rel="noopener noreferrer"&gt;told Cointelegraph&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And the money is there. Deposits in DeFi lending vaults passed $10 billion, up from about $4 billion at the start of 2025, according to the Financial Times, as &lt;a href="https://www.coindesk.com/markets/2026/10/19/crypto-vault-deposits-top-usd10-billion-as-lending-risks-draw-regulatory-scrutiny" rel="noopener noreferrer"&gt;reported by CoinDesk&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My read
&lt;/h2&gt;

&lt;p&gt;A vault is becoming something an outside party scores, and part of that score is your code. I think that's a good thing. A depositor reading a risk score asks better questions than a depositor reading an APY.&lt;/p&gt;

&lt;p&gt;One-click deposits are good for users too. But they move the hardest question in cross-chain finance out of the user's wallet and into your vault: did the full amount actually arrive? Before, the user bridged, waited, and deposited whatever landed. Now your contract mints shares for a deposit that started on another chain. Chainlink is clear about where its part ends. Its own disclaimer says it "does not deploy, control or operate any vault or vault adapter." The transport is theirs. The accounting is yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks when a vault goes cross-chain
&lt;/h2&gt;

&lt;p&gt;My team reviewed this kind of system in September. Yieldcoin v2 is a five-chain stablecoin vault built on Chainlink CCIP and CRE: a parent vault keeps the books, and child vaults on other chains put the money to work in Aave and Compound. It built its own CCIP flows before the new adapters existed, so none of this is about Chainlink's product. These are the three findings I'd check in any cross-chain vault:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A successful send is not a delivery.&lt;/strong&gt; CCIP token pools can charge a transfer fee. The code minted shares from the amount sent and treated a successful &lt;code&gt;ccipSend&lt;/code&gt; as proof that the full value arrived. With a fee on, the destination received less, every call still succeeded, and the shares were backed by less than the books said. Nothing reverted, so nothing alerted. The fix: reconcile shares to the amount that actually arrived.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A signed command has to name its receiver.&lt;/strong&gt; The signatures covered the report, but not which contract received it. A valid withdrawal meant for one child vault could be replayed through another. That was the Critical. The fix: sign the target chain and the router too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A retry only helps if the failure can change.&lt;/strong&gt; Netting could shrink a deposit to one base unit of USDC. Aave v4 rounds that to zero shares and rejects it. The vault stored it for a retry, and the retry could never work, so later epochs and rebalancing stayed blocked. The fix: preview the conversion first, and keep amounts that can't be deposited tracked instead of retried.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The adapters do come with failed-message handling, designed to give stuck funds a path back to the owner. Asynchronous vaults and cooldowns are next on Chainlink's list, and that's where these state machines get really hard. If you're heading there, ERC-7540 (the request-and-claim pattern for async vaults) is the standard to read first.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're opening your vault to other chains in the next three months
&lt;/h2&gt;

&lt;p&gt;Three things I'd have done before the first cross-chain deposit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test with the fee switched on.&lt;/strong&gt; Run your deposit flow on a fork against a token pool that charges a fee, and assert that shares match what arrived, not what was sent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down every failure and its exit.&lt;/strong&gt; For each revert you catch, ask: can waiting fix this? If not, does it abort, reconcile or reroute? A stored retry for a condition that never clears is a frozen vault with extra steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get the single-chain basics right first.&lt;/strong&gt; The first-depositor inflation attack is still the first thing I'd check in any ERC-4626 vault. Virtual shares or dead shares at deploy, and an invariant that a deposit above zero never mints zero shares.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;S&amp;amp;P hasn't scored a single vault yet. The first assessments come "in future announcements." When they do, protocol risk and vault security sit on the list next to curator and credit risk, and a strong score on one doesn't cover a weak one on another.&lt;/p&gt;

&lt;p&gt;If you're building one of these, I'm curious: how are you testing the cross-chain leg today, forks, mocks, or testnet?&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://chain.link/blog/introducing-ccip-vault-adapters" rel="noopener noreferrer"&gt;Chainlink CCIP Vault Adapters&lt;/a&gt;, &lt;a href="https://press.spglobal.com/2026-10-04-S-P-Global-Ratings-launches-Vault-Risk-Assessment-for-digital-asset-markets" rel="noopener noreferrer"&gt;S&amp;amp;P Global Ratings press release&lt;/a&gt;, &lt;a href="https://cointelegraph.com/news/sp-global-launches-risk-assessments-for-10b-crypto-lending-vault-market" rel="noopener noreferrer"&gt;Cointelegraph&lt;/a&gt;, &lt;a href="https://www.coindesk.com/markets/2026/10/19/crypto-vault-deposits-top-usd10-billion-as-lending-risks-draw-regulatory-scrutiny" rel="noopener noreferrer"&gt;CoinDesk&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published in my newsletter, &lt;a href="https://defiprotocolbycode.substack.com/p/defi-vaults-graded-cross-chain-deposits?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=edition_4" rel="noopener noreferrer"&gt;DeFi Protocol by code&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>defi</category>
      <category>solidity</category>
      <category>security</category>
      <category>web3</category>
    </item>
    <item>
      <title>An MCP security checklist for developers wiring agents to real tools</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Thu, 22 Oct 2026 13:12:40 +0000</pubDate>
      <link>https://dev.to/bloqarl/an-mcp-security-checklist-for-developers-wiring-agents-to-real-tools-l71</link>
      <guid>https://dev.to/bloqarl/an-mcp-security-checklist-for-developers-wiring-agents-to-real-tools-l71</guid>
      <description>&lt;p&gt;Connecting an agent to a tool through MCP takes about ten minutes. You add a server to a config file, restart, and now the model can read files, run git, query a database or send email.&lt;/p&gt;

&lt;p&gt;What you also did in those ten minutes is give a process that takes instructions from text it reads the same permissions as you. Every file the agent opens, every web page, every tool result is input that can steer it. And the MCP server on the other side usually runs with your user, your keys and your network.&lt;/p&gt;

&lt;p&gt;Most of the MCP problems I've looked at are not exotic. They're the old bugs (path traversal, shell injection, hardcoded keys) in a place where the input now comes from a model that can be talked into anything. Plus a few new ones that only exist because of how agents read tool output.&lt;/p&gt;

&lt;p&gt;So this is the list I'd go through before letting an agent use an MCP server on anything real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, briefly
&lt;/h2&gt;

&lt;p&gt;These are from public research in 2025, so treat them as a snapshot, not a census:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;82%&lt;/strong&gt; of MCP servers studied use file operations prone to path traversal (&lt;a href="https://www.endorlabs.com/learn/classic-vulnerabilities-meet-ai-infrastructure-why-mcp-needs-appsec" rel="noopener noreferrer"&gt;Endor Labs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;53%&lt;/strong&gt; rely on static, long-lived credentials like hardcoded API keys (&lt;a href="https://astrix.security/learn/blog/state-of-mcp-server-security-2025/" rel="noopener noreferrer"&gt;Astrix Security&lt;/a&gt;, 5,200+ servers analyzed).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;43%&lt;/strong&gt; of servers tested had command injection (&lt;a href="https://equixly.com/blog/2025/03/29/mcp-server-new-security-nightmare/" rel="noopener noreferrer"&gt;Equixly&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the incidents that show the pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool poisoning, April 2025.&lt;/strong&gt; Invariant Labs showed a malicious MCP server, sitting next to a trusted WhatsApp server in the same agent, quietly exfiltrating chat history through instructions hidden in its tool description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Inspector RCE, June 2025.&lt;/strong&gt; &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-49596" rel="noopener noreferrer"&gt;CVE-2025-49596&lt;/a&gt;, CVSS 9.4. Unauthenticated code execution on developer machines through Anthropic's official debugging tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mcp-remote, July 2025.&lt;/strong&gt; &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2025-6514" rel="noopener noreferrer"&gt;CVE-2025-6514&lt;/a&gt;, CVSS 9.6. A malicious &lt;code&gt;authorization_endpoint&lt;/code&gt; during the OAuth flow turned into OS command execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark MCP, September 2025.&lt;/strong&gt; A trojanized package on npm BCC'd every outgoing email to the attacker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these needed a clever model attack. They needed one trust assumption nobody wrote down.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The server itself (4 checks)
&lt;/h2&gt;

&lt;p&gt;These are the ones that end in code execution or file access on your machine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Path traversal.&lt;/strong&gt; Every path argument is resolved and checked against the allowed root, after following symlinks. &lt;code&gt;../&lt;/code&gt; is the obvious case, an absolute path or a symlink inside the allowed folder are the ones that slip through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command injection.&lt;/strong&gt; No shell strings built from tool arguments. Pass arguments as an array, and watch for arguments that start with &lt;code&gt;-&lt;/code&gt;, because &lt;code&gt;git log --grep="$input"&lt;/code&gt; stops being safe the moment the input becomes a flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute primitives in "safe" tools.&lt;/strong&gt; Git is the classic one. If the agent can write &lt;code&gt;.git/config&lt;/code&gt; or pass &lt;code&gt;-c&lt;/code&gt; options, it can register a hook, an alias or a filter that runs on the next commit or checkout. A reader pointed out on one of my earlier posts that git worktrees share hooks and config with the main repo, so a separate worktree doesn't help here either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write boundaries.&lt;/strong&gt; Writes are limited to the folders the task needs. Config files (&lt;code&gt;.git/hooks&lt;/code&gt;, &lt;code&gt;.husky&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; scripts, CI files, editor settings) are persistence, so treat writes there as executions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Tool poisoning and injection (4 checks)
&lt;/h2&gt;

&lt;p&gt;The part that ordinary input validation doesn't catch.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Injected instructions in tool output.&lt;/strong&gt; A git log, a README, an issue comment or a web page can contain "ignore previous instructions". Assume it will. The defense is not a better prompt, it's that the agent can't do much damage when it follows one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hidden content.&lt;/strong&gt; Invisible Unicode, HTML comments and text the user never sees but the model does. Strip or flag it before it reaches the context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exfiltration through normal-looking actions.&lt;/strong&gt; A poisoned agent doesn't need a "send secrets" tool. A URL with a query string, an image link in markdown, a commit message, a search query. Check which of your tools can carry data out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool descriptions are input too.&lt;/strong&gt; A server can put instructions in its own tool descriptions, or name a tool so it shadows one from a trusted server. Read the descriptions of every server you add, and re-read them when the package updates.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Credentials and access (3 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No hardcoded credentials.&lt;/strong&gt; Use proper OAuth or short-lived tokens where the API supports it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped keys.&lt;/strong&gt; A key per server, with the smallest scope that works, rotated. Not your personal admin token in an env var that every server and every subprocess can read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege for the process.&lt;/strong&gt; Don't run servers as root. Ideally not even as your main user: a container or a separate user with access only to what the task needs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Several servers in one agent (3 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Combinations are the attack surface.&lt;/strong&gt; A browser server plus a git server means web content can steer git operations. Filesystem plus network means read plus send. Look at what each pair can do together, not each one alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared context.&lt;/strong&gt; All servers write into the same model context, so one bad server can influence how the agent uses all the others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down the combinations.&lt;/strong&gt; A short table of which servers are connected where, and what each combination could do if one of them went bad.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. Supply chain (3 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin versions&lt;/strong&gt; and verify what you install. An MCP server is a dependency with your permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for typosquats and impersonation.&lt;/strong&gt; The Postmark case was a package that looked like the real thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer a short list of servers you've reviewed&lt;/strong&gt; over whatever a search turns up, and review updates like you'd review a PR.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Prompt and context (3 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context pollution.&lt;/strong&gt; Large or adversarial tool output can push your instructions out of the window or bury them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep instructions and data apart.&lt;/strong&gt; Mark tool output as data in the way you build the context, and don't let it land in the system prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size limits&lt;/strong&gt; on what a tool can return.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  7. Network and monitoring (4 checks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SSRF.&lt;/strong&gt; Any tool that fetches a URL can be pointed at &lt;code&gt;169.254.169.254&lt;/code&gt;, localhost admin panels or your internal network. Block private ranges by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks and callbacks&lt;/strong&gt; are authenticated and can't be pointed anywhere by the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate and spend limits&lt;/strong&gt; that live outside the agent, so a loop or a poisoned plan can't run up a bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs with arguments.&lt;/strong&gt; Which tool was called, with what, by which agent session. When something goes wrong, "the agent did something" is not a useful incident report.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before you ship it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Go through the &lt;a href="https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/" rel="noopener noreferrer"&gt;OWASP Top 10 for Agentic Applications&lt;/a&gt; once with your setup in mind.&lt;/li&gt;
&lt;li&gt;If the agent uses more than one server, review them together.&lt;/li&gt;
&lt;li&gt;Test prompt injection against your actual model and tools, not a generic demo. Put an instruction in a file the agent will read and see what it does.&lt;/li&gt;
&lt;li&gt;Decide what you'll do when a server or a key is compromised: how you revoke it, and how you find out what it touched.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;If I could only pick three: no shell strings built from model input, no long-lived broad keys in the server's environment, and treat every tool result as something an attacker wrote. Most of the incidents above fail at least one of those.&lt;/p&gt;

&lt;p&gt;What does your MCP setup look like? I'm curious how many people run servers in a container versus straight on their laptop.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a guide our team published on &lt;a href="https://www.zealynx.io/research/adversarial-security/mcp-security-checklist?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=mcp-security-checklist" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>mcp</category>
      <category>devops</category>
    </item>
    <item>
      <title>Can't afford an audit yet? What I'd do instead</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:07:11 +0000</pubDate>
      <link>https://dev.to/bloqarl/cant-afford-an-audit-yet-what-id-do-instead-52h1</link>
      <guid>https://dev.to/bloqarl/cant-afford-an-audit-yet-what-id-do-instead-52h1</guid>
      <description>&lt;p&gt;A serious smart contract audit costs somewhere between $15k and $100k+, depending on scope and complexity. For a team before its first raise, that can be more than a month of runway. So a lot of founders look at the quote, close the tab and ship anyway.&lt;/p&gt;

&lt;p&gt;I run a small audit firm, so weigh my bias. But "audit or nothing" is the wrong way to frame it. The useful question is: &lt;strong&gt;what is the most risk I can remove per dollar, at the stage I'm actually at?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how I'd answer it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The options, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Rough cost&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;th&gt;What it misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ship unaudited&lt;/td&gt;
&lt;td&gt;$0 today&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static analysis (Slither, Aderyn)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Known bug patterns&lt;/td&gt;
&lt;td&gt;Your logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI-assisted review&lt;/td&gt;
&lt;td&gt;$0 to $200 a month&lt;/td&gt;
&lt;td&gt;Broad pattern coverage&lt;/td&gt;
&lt;td&gt;Accountability, economic logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public audit contest&lt;/td&gt;
&lt;td&gt;$20k+ prize pool&lt;/td&gt;
&lt;td&gt;Many eyes, variable depth&lt;/td&gt;
&lt;td&gt;Early teams' budgets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug bounty&lt;/td&gt;
&lt;td&gt;Pay per finding&lt;/td&gt;
&lt;td&gt;Bugs found after deploy&lt;/td&gt;
&lt;td&gt;Everything before deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time-boxed senior review&lt;/td&gt;
&lt;td&gt;A few hundred to a few thousand dollars&lt;/td&gt;
&lt;td&gt;Your highest-risk contracts&lt;/td&gt;
&lt;td&gt;Full coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full audit&lt;/td&gt;
&lt;td&gt;$15k to $100k+&lt;/td&gt;
&lt;td&gt;Full scope, re-checks&lt;/td&gt;
&lt;td&gt;Nothing in scope, if done well&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What each one is actually good for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Shipping unaudited&lt;/strong&gt; is fine on testnet with no user funds and no token. The trap isn't getting hacked on day one. It's that value arrives slowly, and by the time the protocol is worth attacking, the team is used to shipping without review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static analysis&lt;/strong&gt; should run in your CI no matter what. It catches the embarrassing bugs: reentrancy shapes, unchecked calls, common access control mistakes. It does not catch the expensive ones. No analyzer knows your withdrawal formula rounds the wrong way or that liquidations break when the oracle goes stale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI review&lt;/strong&gt; finds real bugs now. I build AI auditing tooling myself, so I've seen both sides: without a human who audits for a living checking the output, it gives you false confidence in both directions. It flags things that aren't exploitable and stays quiet on economic logic it doesn't understand. A strong complement, a weak replacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit contests&lt;/strong&gt; can go deep, but competitive prize pools start around $20k and quality depends on how attractive your pool is to good researchers that week. They suit funded teams near mainnet with code stable enough to freeze.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug bounties&lt;/strong&gt; aren't a review at all. They're an incentive for after you deploy, so the bug gets found once user funds are already behind it. Run one alongside everything else once you're live, never instead of reviewing before launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A time-boxed senior review&lt;/strong&gt; is the category built for exactly this gap: a senior auditor spends one to three days on the contracts where the money moves. It's not line by line over everything, and it's not an audit. Anyone who sells you a two-day review as "audited" is setting your users up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd pick at each stage
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Testnet, nothing at risk:&lt;/strong&gt; static analysis in CI plus AI review. Spend $0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real users or funds arriving, budget under about $5k:&lt;/strong&gt; a time-boxed senior review of the contracts that hold the money, plus the free tools. This is the most risk removed per dollar you can get.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funded and heading to serious TVL:&lt;/strong&gt; a full audit, maybe preceded by a contest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live at any stage:&lt;/strong&gt; add a bug bounty on top.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Three questions before you pay anyone
&lt;/h2&gt;

&lt;p&gt;Whatever you choose, ask these first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who exactly reads the code,&lt;/strong&gt; and what's their track record?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What do I get in writing?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What am I allowed to claim publicly afterwards?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answers are vague, or a cheap review comes with an "audited by" badge, walk away.&lt;/p&gt;

&lt;p&gt;What did you do for security before you could afford a full audit? Curious what worked for other teams, and what you'd skip next time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a guide our team published on &lt;a href="https://www.zealynx.io/research/audit-ops/cant-afford-smart-contract-audit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cant-afford-smart-contract-audit" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>startup</category>
    </item>
    <item>
      <title>Ethereum's next upgrade can break your contracts</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Fri, 16 Oct 2026 13:10:08 +0000</pubDate>
      <link>https://dev.to/bloqarl/ethereums-next-upgrade-can-break-your-contracts-18ld</link>
      <guid>https://dev.to/bloqarl/ethereums-next-upgrade-can-break-your-contracts-18ld</guid>
      <description>&lt;p&gt;If you have a &lt;code&gt;transfer()&lt;/code&gt;, a &lt;code&gt;call{gas: 50000}&lt;/code&gt; or an &lt;code&gt;if (gasleft() &amp;lt; X)&lt;/code&gt; somewhere in a contract on mainnet, this one is for you.&lt;/p&gt;

&lt;p&gt;On October 6, Ethereum's next upgrade, Glamsterdam, went live on the Sepolia testnet. Most of the coverage is about throughput and block building. The part that matters for deployed contracts is quieter: Glamsterdam reprices state.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a new account goes from 25,000 gas to 183,600.&lt;/li&gt;
&lt;li&gt;A new storage slot goes from 20,000 to 97,920.&lt;/li&gt;
&lt;li&gt;Every byte of deployed code goes from 200 to 1,530.&lt;/li&gt;
&lt;li&gt;Changing an existing storage slot goes from 2,900 to 10,100.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Ethereum Foundation replayed historical mainnet transactions under the new numbers. Most contracts are fine. A small set "may lead to these contracts breaking or degrading without preventative updates." The testnet announcement says it plainly: "Contracts that rely on fixed gas stipends, hardcoded gas limits, or assumptions about remaining gas may need changes." Mainnet has no date yet, and the target is this quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  My read
&lt;/h2&gt;

&lt;p&gt;Hard forks rarely break your code. This one can, but only where you wrote down a number. Every &lt;code&gt;transfer()&lt;/code&gt;, every hardcoded gas value, every branch on &lt;code&gt;gasleft()&lt;/code&gt; is an assumption about what the EVM charges. If your contracts were audited, the audit checked that assumption against today's gas schedule, not the next one. Every hard fork is an audit decay event for someone.&lt;/p&gt;

&lt;p&gt;This has happened before. In 2019, Istanbul raised the cost of reading storage from 200 to 800 gas, and the EIP itself warned that fallback functions reading storage "may in some cases require more than" the 2,300 gas a &lt;code&gt;transfer()&lt;/code&gt; forwards. That's when the industry started telling developers to stop using &lt;code&gt;transfer()&lt;/code&gt;. The difference now is months of warning, a public testnet, and a tool that tells you if your contract is on the list. I don't think this is a reason to panic. But "most contracts are fine" is not a property of your contracts. You have to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the new numbers bite
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded gas in calls.&lt;/strong&gt; &lt;code&gt;transfer()&lt;/code&gt;, &lt;code&gt;send()&lt;/code&gt; and &lt;code&gt;call{gas: N}&lt;/code&gt;. The 2,300 stipend stays the same, but what fits inside it changes, because touching state now costs more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic that branches on &lt;code&gt;gasleft()&lt;/code&gt;.&lt;/strong&gt; Keepers, relayers, meta-transaction forwarders, batch loops that stop at a gas threshold. EIP-8037 even notes that metering by subtracting two gas readings can now go negative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything that creates accounts or contracts.&lt;/strong&gt; Factories, a clone per user, smart accounts deployed on first use. Account creation and code deposit both get several times more expensive, so re-check your gas budgets and your fee math.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fixed gas limits off-chain.&lt;/strong&gt; Presigned transactions, bots and frontends that send a hardcoded limit. A plain ETH transfer to an existing account still costs 21,000, but a transfer to a fresh address no longer does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexers.&lt;/strong&gt; Every ETH transfer will emit a log with the same topic as an ERC-20 &lt;code&gt;Transfer&lt;/code&gt;, from a system address. If your backend listens for that topic without checking the emitter, check what it does with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's good news too: the contract size limit goes from 24 KiB to 64 KiB, so fewer teams will need to split code across libraries just to deploy.&lt;/p&gt;

&lt;p&gt;If one of your contracts is flagged and it's immutable, the fix is a migration, not a patch. If it's behind a proxy, the fix is an upgrade, and upgrades have their own ways to go wrong, so treat that upgrade like any other change that needs review.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you have contracts on mainnet, before the fork date is set
&lt;/h2&gt;

&lt;p&gt;Three things I'd do this month:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Paste your addresses into the Ethereum Foundation's &lt;a href="https://ethereum.github.io/repricing-impact/" rel="noopener noreferrer"&gt;repricing impact dashboard&lt;/a&gt;.&lt;/strong&gt; It's free. It replays real mainnet transactions under the new schedule and tells you if yours still work, need a higher gas limit, or might be broken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grep for written-down gas.&lt;/strong&gt; Search your contracts for &lt;code&gt;.transfer(&lt;/code&gt;, &lt;code&gt;.send(&lt;/code&gt;, &lt;code&gt;gas:&lt;/code&gt; and &lt;code&gt;gasleft()&lt;/code&gt;, then do the same in your frontend, bots and deploy scripts for hardcoded gas limits. Every hit is a question to answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run your critical flows on Sepolia.&lt;/strong&gt; Deposits, withdrawals, liquidations, anything a keeper triggers. A fix costs a conversation now. After the fork it costs an incident.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're deploying new contracts before the fork, the simplest rule is not to write gas down at all. Send ETH with a plain &lt;code&gt;call&lt;/code&gt; behind a reentrancy guard, check the return value, and let your frontend estimate gas against the network it's actually talking to instead of shipping a constant.&lt;/p&gt;

&lt;p&gt;One more date: the repricing EIPs are in Last Call until Sunday, November 1. If the dashboard shows your contracts breaking, now is the time to say it in the EIP-8037 and EIP-8038 threads on Ethereum Magicians, not after mainnet.&lt;/p&gt;

&lt;p&gt;Have you run your contracts through the dashboard yet? Curious if anyone found something flagged.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://blog.ethereum.org/2026/09/17/glamsterdam-testnet-announcement" rel="noopener noreferrer"&gt;EF testnet announcement&lt;/a&gt;, &lt;a href="https://blog.ethereum.org/2026/08/24/glamsterdam-repricing-testing" rel="noopener noreferrer"&gt;EF repricing testing post&lt;/a&gt;, &lt;a href="https://eips.ethereum.org/EIPS/eip-8037" rel="noopener noreferrer"&gt;EIP-8037&lt;/a&gt;, &lt;a href="https://eips.ethereum.org/EIPS/eip-8038" rel="noopener noreferrer"&gt;EIP-8038&lt;/a&gt;, &lt;a href="https://eips.ethereum.org/EIPS/eip-7954" rel="noopener noreferrer"&gt;EIP-7954&lt;/a&gt;, &lt;a href="https://eips.ethereum.org/EIPS/eip-7708" rel="noopener noreferrer"&gt;EIP-7708&lt;/a&gt;, &lt;a href="https://eips.ethereum.org/EIPS/eip-1884" rel="noopener noreferrer"&gt;EIP-1884&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published in my newsletter, &lt;a href="https://defiprotocolbycode.substack.com/p/glamsterdam-gas-repricing-what-builders-should-check?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=glamsterdam-gas" rel="noopener noreferrer"&gt;DeFi Protocol by code&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>solidity</category>
      <category>security</category>
      <category>web3</category>
    </item>
    <item>
      <title>AI helped find most of curl's 24 new CVEs. What made them real was the human check.</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Wed, 14 Oct 2026 11:57:43 +0000</pubDate>
      <link>https://dev.to/bloqarl/ai-helped-find-most-of-curls-24-new-cves-what-made-them-real-was-the-human-check-a4a</link>
      <guid>https://dev.to/bloqarl/ai-helped-find-most-of-curls-24-new-cves-what-made-them-real-was-the-human-check-a4a</guid>
      <description>&lt;p&gt;In January, curl shut down its bug bounty. Daniel Stenberg said the goal was to remove the incentive for people to send "crap and non-well researched reports to us. AI generated or not." The last week of the program brought seven reports and none of them described a vulnerability.&lt;/p&gt;

&lt;p&gt;Today, curl 8.23.0 shipped with 24 security fixes. AISLE says its researchers found 18 of them. The only one rated High, a use-after-free in the HTTP/2 upload path, was reported by an Anthropic researcher using Anthropic's new open source scanner. The curl team confirmed it within hours and cut the release cycle from eight weeks to six to ship the fix.&lt;/p&gt;

&lt;p&gt;Same project, same kind of tools, nine months apart. One produced so much noise the bounty closed. The other produced real CVEs. I don't think the difference was the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed between January and October
&lt;/h2&gt;

&lt;p&gt;Look at how the October reports got to the curl team.&lt;/p&gt;

&lt;p&gt;AISLE says a security researcher reviews the complete report before submission. Then curl's own security team decides whether the behavior is real, whether it breaks curl's security policy, how severe it is and whether it gets a CVE. Stenberg describes AISLE's work as "detailed and accurate findings backed up by a team of skilled humans."&lt;/p&gt;

&lt;p&gt;So there were two human checks before anything became a CVE. One on the side that ran the tools, one on the side that owns the code.&lt;/p&gt;

&lt;p&gt;The January reports skipped the first check. Someone pointed a tool at curl, got something that sounded like a vulnerability and sent it. The cost of verifying moved from the reporter to a small maintainer team. Stenberg's rule was simple: never report a bug unless you actually understand it and can reproduce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same thing happens in smart contract audits
&lt;/h2&gt;

&lt;p&gt;I audit smart contracts, and AI is now a big part of how my team works. Last week I wrote that without prior experience auditing manually, I don't think AI auditing can be done properly. The curl story is the same point at a much bigger scale.&lt;/p&gt;

&lt;p&gt;In my last sweep on a competition codebase I ran six agents in parallel. Five came back with reasoned negatives. The sixth produced passing PoCs, but they only rediscovered a finding we already had. None of that output could be taken at face value.&lt;/p&gt;

&lt;p&gt;The two failure modes are the same ones curl lived through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agents invent findings.&lt;/strong&gt; Ask a model to find a vulnerability and it will try hard to give you one. It sounds plausible, it has a title, sometimes a severity. That's what filled curl's inbox in January.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents declare things safe.&lt;/strong&gt; A reasoned negative is useful to decide where not to spend more time. It doesn't mean the area was checked completely, and it definitely doesn't mean the audit is done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You need someone who knows the code well enough to catch both. That's the part that didn't get cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  A raw firehose is coming too
&lt;/h2&gt;

&lt;p&gt;On October 8 Anthropic opened an opt-in scanning service for open source maintainers. They say plainly that its output "will be fully model-generated, without human review or triage." In their own six months of scanning they got over 29,000 candidate vulnerabilities and manually reviewed about 6,000.&lt;/p&gt;

&lt;p&gt;I don't think that's bad. It's free, it's opt-in, and maintainers know what they're getting. But it moves the triage to you. If you enroll, the first check from the curl story is your job now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd take from this if you ship code
&lt;/h2&gt;

&lt;p&gt;Whether you maintain a library or you're about to launch a protocol, a few things I'd keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A finding without a reproduction is a lead, not a finding.&lt;/strong&gt; In audits a PoC is what turns "this looks wrong" into "this is broken". Ask for it, or write it yourself before you spend a week on a fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget time for triage, not just for scanning.&lt;/strong&gt; Running the tool is the cheap part now. Reading 200 results and throwing most of them away is where the hours go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down why you discarded something.&lt;/strong&gt; "Not exploitable, only the owner can call it" is a trust assumption. If nobody writes it down, nobody notices when the assumption changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat "nothing found" with suspicion too.&lt;/strong&gt; Ask what the tool actually covered. A clean scan of one path says nothing about the others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the human who knows the code in the loop.&lt;/strong&gt; curl's team could confirm the High one in hours because they know their code. That's the bottleneck now, and it's the part worth protecting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;curl didn't stop taking AI-assisted reports. It stopped rewarding unverified ones. Now Stenberg says the reviewed work is helping them fix vulnerabilities "at a higher rate than ever before." I think that's the right model for the rest of us.&lt;/p&gt;

&lt;p&gt;How are you handling AI scanner output on your own projects? Do you read everything, or do you have a way to rank it first?&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://daniel.haxx.se/blog/2026/10/14/curl-8-23-0/" rel="noopener noreferrer"&gt;curl 8.23.0 release&lt;/a&gt;, &lt;a href="https://daniel.haxx.se/blog/2026/10/14/a-uaf-turned-rce/" rel="noopener noreferrer"&gt;the HTTP/2 use-after-free write-up&lt;/a&gt;, &lt;a href="https://aisle.com/blog/one-aisle-curl-audit-produced-24-cves-18-ship-today" rel="noopener noreferrer"&gt;AISLE's write-up&lt;/a&gt;, &lt;a href="https://www.anthropic.com/research/launching-opt-in-vuln-finding-service-for-open-source" rel="noopener noreferrer"&gt;Anthropic's opt-in scanner&lt;/a&gt;, &lt;a href="https://www.theregister.com/2026/01/21/curl_ends_bug_bounty/" rel="noopener noreferrer"&gt;The Register on the bounty shutdown&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>ai</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>What to have ready before your first smart contract audit</title>
      <dc:creator>Carlos | Zealynx</dc:creator>
      <pubDate>Tue, 13 Oct 2026 13:11:44 +0000</pubDate>
      <link>https://dev.to/bloqarl/what-to-have-ready-before-your-first-smart-contract-audit-580b</link>
      <guid>https://dev.to/bloqarl/what-to-have-ready-before-your-first-smart-contract-audit-580b</guid>
      <description>&lt;p&gt;An audit is usually the biggest single check an early team writes. And a good part of the first days of most audits goes to things that have nothing to do with finding bugs: figuring out what the protocol is supposed to do, which files are in scope, why the tests don't run, which commit we're even looking at.&lt;/p&gt;

&lt;p&gt;You pay for those days either way. The difference is whether they go into understanding your setup or into attacking your logic.&lt;/p&gt;

&lt;p&gt;So this is the list I'd go through before the audit starts. None of it needs a big budget. Most of it is a few evenings of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Write down what the code is supposed to do
&lt;/h2&gt;

&lt;p&gt;If the auditor has to reverse engineer your intent from the code, part of the audit is spent guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NatSpec on every public and external function.&lt;/strong&gt; &lt;code&gt;@notice&lt;/code&gt;, &lt;code&gt;@param&lt;/code&gt;, &lt;code&gt;@return&lt;/code&gt;. It sounds boring, but it's one of the best bug finders there is. If the comment says "takes a 1% fee" and the code computes 0.1%, that mismatch jumps out the moment someone reads both. Without the comment, the code is just the code, and a wrong number looks like a design choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A short theory of operation.&lt;/strong&gt; How the money moves, who can do what, and what must never happen. A call graph and a state machine diagram help a lot if you have lifecycles (governance, vesting, auctions, liquidations).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A one page threat model.&lt;/strong&gt; Who is trusted (owner, keepers, oracles), what each of them could break if they went rogue, and the single assumption you're most nervous about. This is the page I'd want first. A finding is only real relative to your trust assumptions, and without them every report turns into a debate about what counts as "in scope for an attacker".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 20 to 30 minute video walkthrough.&lt;/strong&gt; The lead dev screen-sharing the architecture and the flow of funds. It's the fastest way to get someone from zero to useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Clean the house
&lt;/h2&gt;

&lt;p&gt;Noise hides bugs. It also costs reading time, because every line has to be read before anyone can say it doesn't matter.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run a linter&lt;/strong&gt; (&lt;a href="https://github.com/protofire/solhint" rel="noopener noreferrer"&gt;Solhint&lt;/a&gt; or &lt;a href="https://github.com/prettier-solidity/prettier-plugin-solidity" rel="noopener noreferrer"&gt;prettier-plugin-solidity&lt;/a&gt;). Style comments in an audit report are money spent on things a free tool does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete dead code.&lt;/strong&gt; Unused functions, commented out blocks, leftover test helpers in production contracts. Dead code isn't always dead: an internal helper that later gets exposed by a refactor is a classic way for old assumptions to come back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin your compiler and your dependencies.&lt;/strong&gt; &lt;code&gt;pragma solidity 0.8.x;&lt;/code&gt; with an exact version, not &lt;code&gt;^0.8.0&lt;/code&gt;, and locked dependency versions. The bytecode you deploy should come from exactly the code that was reviewed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Test the sad paths
&lt;/h2&gt;

&lt;p&gt;Low test coverage is the clearest sign a codebase isn't ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branch coverage, not line coverage.&lt;/strong&gt; Line coverage tells you the code ran. Branch coverage tells you that every &lt;code&gt;require&lt;/code&gt; and every &lt;code&gt;if&lt;/code&gt; was hit both ways. Most of the interesting bugs live in the paths that are supposed to revert and don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run static analysis and deal with every warning.&lt;/strong&gt; &lt;a href="https://github.com/crytic/slither" rel="noopener noreferrer"&gt;Slither&lt;/a&gt; takes seconds. Fix the warnings or write down why each one is a false positive. Handing over 50 untriaged warnings means someone you're paying will triage them for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run an AI pass too.&lt;/strong&gt; Finding the obvious stuff got very cheap this year, for you and for anyone who looks at your code after deploy. Do it before the audit, fix what's real, and you'll get more out of the humans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write invariants and fuzz them.&lt;/strong&gt; Unit tests check the cases you thought of. Invariant tests check the cases you didn't. With &lt;a href="https://book.getfoundry.sh/" rel="noopener noreferrer"&gt;Foundry&lt;/a&gt; or &lt;a href="https://github.com/crytic/echidna" rel="noopener noreferrer"&gt;Echidna&lt;/a&gt;, write down the properties that must always hold (something like "the sum of user balances never exceeds what the contract holds") and let the fuzzer try to break them. Even a handful of good invariants tells the auditor a lot about how you think about your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Freeze the code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tag the commit&lt;/strong&gt; (&lt;code&gt;git tag audit-v1&lt;/code&gt;) and don't touch it. Pushing "small fixes" during the audit moves line numbers, invalidates notes, and turns into a diff review you pay for on top. If you find something yourself mid-audit, write it down and fix it in the remediation round. Better yet, tell the auditors, so they look at the area around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write the scope down.&lt;/strong&gt; A &lt;code&gt;scope.txt&lt;/code&gt; with the exact files. Exclude mocks, deploy scripts and unmodified libraries like OpenZeppelin. If you modified a library, say so explicitly, because that's exactly the kind of thing that hides in "we just use OZ".&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Ready when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;NatSpec on every public function, a theory of operation, a one page threat model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;Linter clean, no dead code, compiler and dependencies pinned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;High branch coverage, sad paths tested, a few invariants fuzzed for a long run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;Slither and an AI pass run, every warning fixed or explained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops&lt;/td&gt;
&lt;td&gt;Commit tagged and frozen, scope file written, walkthrough recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  One more thing
&lt;/h2&gt;

&lt;p&gt;An audit covers a commit. Not your deploy scripts, not your config, not the three PRs you merge the week after. So plan the launch too: caps you can raise later, a pause you've actually tested, alerts that reach a human.&lt;/p&gt;

&lt;p&gt;If you only do one thing this week, run Slither on your core contracts tonight. If it flags things you've never looked at, that's your answer on whether you're ready.&lt;/p&gt;

&lt;p&gt;What's on your own pre-audit list that I didn't cover? Curious what other teams do before they book.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a checklist our team published on &lt;a href="https://www.zealynx.io/research/audit-ops/pre-audit-checklist?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=pre-audit-checklist" rel="noopener noreferrer"&gt;zealynx.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.&lt;/em&gt;&lt;/p&gt;

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