Secret Network / Axelar Bridge CW20-ICS20 Source-Channel Validation Omission / Unbacked saToken Mint (2026-06-10)

  • Name: Secret Network / Axelar bridge CW20-ICS20 source-channel validation omission / unbacked saToken mint
  • Date: 2026-06-10
  • Protocol: Secret Network / Axelar
  • Chain: Secret Network + Axelar + IBC
  • Category: bridge verification failure / deposit-signal-locked-value mismatch / message-source authentication failure / unbacked wrapped-asset mint
  • Loss: about $4.67M across saUSDT, saUSDC, saDAI, saWETH, saWBTC, saWBNB, and sawstETH
  • Summary: The Secret↔Axelar bridge did not fail because Axelar’s validator set, IBC light clients, or escrow accounts were directly compromised. It failed because the Secret-side ics20-for-axelar contract stopped proving that an inbound IBC packet belonged to Axelar’s canonical channel before minting Secret-wrapped Axelar assets. Two upstream CW20-ICS20 safety checks—parse_voucher_denom(&msg.denom, &packet.src) and reduce_channel_balance(...)—had been commented out in a forked Secret contract and never replaced. That left the bridge asking only is this bare denom allow-listed? instead of did this packet come from the right channel, and does that channel actually have escrowed backing for this redeemability domain?. The sharper design failure was that same bare denom name started to outrank same source channel and same escrow bucket, so packets from an attacker-created channel could mint the exact same saTokens that the real Axelar return lane later treated as fully backed inventory. The attacker therefore spun up a fake Cosmos chain, opened a fresh IBC channel to the Secret-side bridge contract, self-relayed forged deposits carrying allow-listed bare denoms, minted unbacked saTokens, and then redeemed those counterfeit balances back over the legitimate Axelar channel to release real escrowed assets.
  • Trigger / entry point:
    • Secret’s bridge-side contract secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83 was a modified fork of Secret’s CW20-ICS20 / SNIP-20 bridge logic, tailored for the Axelar connection.
    • The deployed code minted Secret-wrapped Axelar assets on inbound IBC receive.
    • In do_ibc_packet_receive, the fork no longer enforced the denom-path / source-channel consistency check and no longer reduced channel balance before minting.
    • The remaining logic allowed minting when the incoming denom was a bare allow-listed name such as uusdt or uusdc, even if the packet arrived over an attacker-controlled IBC channel rather than Axelar’s canonical channel-61 / channel-69 link.
  • Root cause:
    • The bridge’s authenticity boundary drifted from packet proven to come from Axelar's escrow-backed channel to packet carries an allow-listed bare denom string.
    • The removed parse_voucher_denom(...) check meant the contract no longer verified that the voucher denom trace matched the packet’s actual source endpoint.
    • The removed reduce_channel_balance(...) check meant the contract no longer capped minting to balances genuinely escrowed for that same channel.
    • Together, those omissions collapsed channel-local accounting into a denom-only identity rule: uusdt over attacker channel-227 and uusdt over Axelar's canonical channel-69/channel-61 route minted the same saUSDT even though only one path had real backing.
    • Because IBC channel creation is permissionless, the attacker did not need to counterfeit Axelar. They only needed a new chain and a new channel to Secret.
    • Once the Secret-side contract minted real-looking saTokens from those forged packets, the legitimate return path over the real Axelar channel faithfully released real assets from Axelar escrow.
  • Exploit path:
    1. The attacker spent June 10 preparing the route: rehearsing IBC setup, creating test light clients and channels, and probing the exit path.
    2. At 19:01 UTC, the attacker created live Secret client 07-tendermint-244 for fake chain ibc-dev-allowlist-denom-1.
    3. Between 19:02 and 19:05 UTC, they opened connection-241 and channel-227 from that fake chain to the Secret-side bridge contract secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83.
    4. Between 19:14 and 19:20 UTC, the attacker self-relayed seven forged inbound IBC packets over channel-227, each using a bare allow-listed denom and each sized to the current real backing of one saToken class.
    5. Because the contract only checked supported_denom and did not bind that denom to Axelar’s canonical source channel or escrow, it minted unbacked saUSDT, saUSDC, saDAI, saWETH, saWBTC, saWBNB, and sawstETH to attacker address secret154pdez8zazqh26wyuyu70nqraal3hkvf2f03vm.
    6. The forged mint transactions included approximately 1,121,270 USDT, 638,198 USDC, 893 WETH, 16 WBTC, 174,536 DAI, 170 WBNB, and 17 wstETH worth of counterfeit saTokens.
    7. Between 19:33 and 19:36 UTC, the attacker redeemed those freshly minted saTokens back over the legitimate Secret→Axelar channel to Axelar address axelar1hzra9z4zn8q0w8f3dj2wnw0xgetu8dfdhl6ad8.
    8. Axelar’s real escrow for channel-69 then unescrowed the matching genuine assets, because from Axelar’s perspective the saTokens arriving over the legitimate return path looked normal.
    9. From 19:38 to 19:56 UTC, the attacker moved the haul off Axelar in 18 packet-forwarded IBC transfers via Osmosis, then bridged onward to Ethereum.
    10. On Ethereum, the funds were consolidated in wallet 0x6c2eAB82bA2897A6E99FB6Af018020dA15123976, sold into ETH, split into roughly 30 transfers, and later deposited at exchanges before the bridge shortfall was discovered.
  • What the attacker needed to be true:
    • The Secret-side contract had to mint on inbound IBC receive without proving the packet came from Axelar’s canonical channel.
    • Bare denom allow-listing had to be treated as equivalent to authenticated source provenance.
    • Permissionless IBC channel creation had to remain available, so the attacker could open a brand-new channel from a chain they controlled.
    • The bridge had to keep one shared saToken identity per denom name rather than segregating same denom name over different channels into distinct redeemability domains.
    • The real Secret→Axelar redemption path had to accept the counterfeit saTokens as ordinary bridge inventory and release real escrow accordingly.
    • Monitoring had to miss the escrow depletion long enough for the attacker to finish cross-chain exits and exchange deposits.
  • Technical anatomy worth remembering:
    • This is a clean permissionless IBC is fine until an application-layer bridge forgets channel provenance incident. IBC itself lets anyone open new channels; the application contract still has to keep each channel’s accounting isolated.
    • The broken boundary was not did a packet exist?; it was did this packet arrive from the one channel whose escrow is allowed to back this voucher?.
    • Removing parse_voucher_denom(...) turned denom text into a weak authenticity signal. Removing reduce_channel_balance(...) removed the quantitative cap that would have limited minting to actually escrowed inventory for that same channel.
    • The attacker did not need to forge Axelar-origin packets under Axelar’s light client. They used a legitimate new IBC connection plus the bridge contract’s own underconstrained mint semantics.
    • Secret’s privacy model compounded detection delay. The bridge shortfall was only noticed on June 17, when a routine transfer tried to unescrow more WBTC than remained in Axelar’s escrow account.

Three bridge-truth promotions this incident collapsed

  1. A packet carrying a familiar bare denom existed at all
    • The attacker did not need to forge IBC transport or counterfeit Axelar validators. They only needed a real packet on a fresh attacker-controlled channel whose payload used an allow-listed bare denom like uusdt or uusdc.
    • At this stage the strongest true fact was only some packet with familiar denom text arrived, not Axelar's canonical escrow-backed route authored a mintable deposit claim.
  2. That packet was promoted into canonical route-authenticated mint truth
    • This was the decisive collapse. Once parse_voucher_denom(&msg.denom, &packet.src) was gone, the contract stopped binding denom trace to the packet’s actual source channel; once reduce_channel_balance(...) was gone, it also stopped proving that the same route had escrow headroom to support the mint.
    • The bridge therefore confused an allow-listed denom string exists on some IBC route with the canonical Axelar channel proved this exact redeemability claim, so attacker channel channel-227 could mint the same saUSDT, saUSDC, saWETH, saWBTC, saWBNB, saDAI, and sawstETH that the real Axelar route used.
  3. That accepted mint truth still deserved immediate release authority against real Axelar escrow
    • Once the counterfeit saTokens existed, the legitimate Secret→Axelar redemption lane treated them as ordinary bridge inventory and unescrowed real assets from Axelar custody.
    • The route preserved too little separation between a wrong-channel packet already polluted shared saToken supply and that shared supply can now burn for real assets on the healthy channel, which is why one source-authentication failure on Secret could empty multiple live escrow buckets on Axelar.

The review habit this incident punishes is simple: do not stop at the packet was valid IBC. First ask whether the packet proved the exact channel-local backing object that was allowed to mint a shared wrapper, then ask whether that wrapper should have inherited immediate release authority against the canonical escrow lane.

  • Representative transactions, contracts, and addresses:
    • Exploited Secret contract: secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83
    • Original deployed code ID: 872
    • Migrated code ID at exploit time: 2446
    • Migration transaction to code ID 2446: A049548A7DEE3F9BF38A97F70EF8051E680A95F1088EBFE3D3C30BFEACADB8B5
    • Attacker Secret address: secret154pdez8zazqh26wyuyu70nqraal3hkvf2f03vm
    • Fake source-chain sender string used in forged packets: cosmos1lquy33kxj7ywgzwvc662sqv4y0hyns4a8na5x2
    • Attacker Axelar address: axelar1hzra9z4zn8q0w8f3dj2wnw0xgetu8dfdhl6ad8
    • Main Ethereum consolidation wallet: 0x6c2eAB82bA2897A6E99FB6Af018020dA15123976
    • Live attacker client creation on Secret: 562C87C05E5523A07E5F5221CC996EE5C4FF3DE80E2E7A6CC5D2030C52E0E386
    • Live attacker channel open init (channel-227): D404B1BF6611344D2C1C72444989AFCD7C2C264FA2F83855ED5A0CD514D4970E
    • Live attacker channel open ack: C0D8B4B4322C56F895E4C182817755B263337C3FEDC3D50A267E048C944293DD
    • Representative forged mint transactions on Secret:
      • saUSDT: E17184D49A12B594CEB66696F380598E83C2E716F5F10002291B8DA1C2B1BFA1
      • saUSDC: DC4529F30175A7FAEF332FEEE86A5FE1F4D9DE56B144A549CCE620E64039BB42
      • saWETH: 4A0CC8681376AEA9E52C5812E661F880B207061799E6CF84D50B7FDD4C9283A0
      • saWBTC: AE894827FEA3421DCA4F593765D5C7B7602C8CBF5E2994894F031BD13C4BC351
    • Representative Secret→Axelar redemption transactions:
      • USDC redemption: 842CCB7514F4F7527E10C590C32526DDF4CFBC3CCA099B3B5EE14901AB5B3A18
      • WETH redemption: 45C2E2853A3DEBC558DFDB5E0316F79B6D3122A2EB0455059FD4744F7E857B64
      • WBTC redemption: D0BF6C27028D574591DFC25E4D68E0889BE41727265489752F35DBBBF702C3C2
  • Response / mitigation:
    • Axelar disclosed the incident publicly after the June 17 failed transfer exposed the escrow shortfall.
    • Axelar’s Emergency Committee disabled the Secret↔Axelar connection.
    • Squid removed Secret from its frontend.
    • Secret said the impact was confined to the Axelar-linked saToken set on this connection and that other IBC channels, assets, and Axelar core protocol components were not affected.
    • Secret and Axelar coordinated incident response, notified law enforcement, and began migration / remediation work on the affected Secret-side contract.
  • Comparable exploit pattern:

Pattern tags

  • deposit-signal-locked-value-mismatch
  • message-source-authentication-failure
  • bridge-verification-failure
  • Secret’s forked ics20-for-axelar contract let same bare denom name outrank same source channel and same escrow bucket, so packets from attacker channel channel-227 minted the same saTokens as the real Axelar channel-69 / channel-61 route
  • Two commented-out guards — parse_voucher_denom(&msg.denom, &packet.src) and reduce_channel_balance(...) — removed both provenance binding and route-local backing limits, turning a permissionless new IBC channel into mint authority over Axelar-backed inventory
  • The legitimate Secret→Axelar redemption path did not need its own bug; once wrong-channel packets minted ordinary-looking saUSDT, saUSDC, saWETH, saWBTC, saWBNB, saDAI, and sawstETH, the real return lane honored them as if they had come from the canonical escrow-backed route

Why the exploit worked

  • Common Prefix’s reconstruction makes the decisive break unusually explicit: in do_ibc_packet_receive, the deployed Secret fork had commented out both parse_voucher_denom(&msg.denom, &packet.src) and reduce_channel_balance(...), leaving only a bare-denom allow-list check for inbound minting.
  • That changed the bridge’s source-authentication question from did this packet come from Axelar's canonical channel with matching denom trace and escrow? to the weaker does this packet carry an allow-listed bare denom like uusdt or uusdc?.
  • Because IBC channel creation is permissionless, the attacker did not need to counterfeit Axelar or subvert IBC light clients. They only needed a new chain, a new Secret-side client, and a fresh channel whose packets the contract was still willing to treat as fungible with the Axelar lane.
  • The missing reduce_channel_balance(...) check mattered as much as the missing source-channel check. Even after wrong-channel packets minted saTokens, a healthy route-local balance cap could have blocked those counterfeit balances from expanding to the full escrowed backing of the real channel.
  • The exit path then behaved exactly as designed: once the attacker held ordinary-looking saTokens, redeeming them over the legitimate Secret→Axelar channel caused Axelar’s real escrow accounts to unescrow matching genuine assets. The bridge’s lie had already been authored on the Secret-side mint path.
  • The incident is therefore best preserved not as IBC was permissionless or merely allow-list bug, but as route-local redeemability collapsed. The bridge forgot that the same bare denom name over two different channels is not the same liability unless channel identity, denom trace, and escrow bucket are all proven to line up.

Detection and audit pivots

  • Audit IBC bridge forks for any path where parse_voucher_denom, channel-trace validation, or equivalent source-endpoint checks were removed, bypassed, or replaced with denom-only allow-listing.
  • Treat channel-local escrow accounting as part of authenticity, not just bookkeeping. If reduce_channel_balance(...)-style caps disappear, one wrong-channel mint can often scale directly to the full backing of the canonical route.
  • Ask whether the bridge keeps same asset name over different channels route-local all the way through minting, holding, and redemption, or whether wrapper names like saUSDT silently collapse multiple channels into one redeemability domain.
  • Test the bridge the way the attacker did: stand up a disposable chain, open a fresh permissionless IBC channel, and try to send allow-listed bare denoms or path variants that should be non-fungible with the production route. If the resulting wrapped asset can redeem against live escrow elsewhere, the bridge is already broken.
  • Reconcile wrapped supply against source-channel backing at route granularity, not only at the aggregate token-symbol level. Total saUSDT supply can look reasonable while channel-227 minted supply is entirely unbacked.
  • Review migrations and long-lived forks as if they were verifier upgrades. Common Prefix’s timeline makes clear that the vulnerable checks had been absent since the repo’s early 2023 history and survived the March 2026 migration to code ID 2446; unchanged for years is not evidence that route-local authenticity was safe.
  • In privacy-preserving wrappers, require explicit reserve and escrow observability outside the hidden-balance layer. The exploit itself took minutes, but the shortfall was only surfaced when later unescrow attempts exceeded real Axelar backing.

Why this incident matters

  • It preserves a valuable bridge-security lesson distinct from classic proof forgery: a bridge can fail even when transport-layer proofs are valid if the application contract forgets which channel is allowed to mint which asset.
  • It is a strong exemplar for deposit signal / locked-value mismatch in IBC form. The forged packets were real packets, but they still did not prove the locked value Axelar later released.
  • It also preserves the sharper deposit-truth lesson that same denom text must not collapse different source channels into one redeemability domain unless the bridge has actually proven those channels share the same backing bucket. In healthy IBC-style bridge semantics, same asset name is never enough on its own.
  • It also sharpens the separate blast-radius question preserved in bridge bounded release design: once bare denom allow-listing became mint authority, one forged channel still had immediate line-of-sight to the full live escrow buckets behind saUSDT, saUSDC, saWETH, saWBTC, saWBNB, saDAI, and sawstETH rather than first hitting tighter per-channel or per-asset release ceilings.
  • It also shows why privacy-chain operations need explicit reserve monitoring. The exploit itself took minutes; the detection lag took a week.

Failure-prevention checklist

  • Keep channel identity + denom trace + escrow bucket + wrapped representation fused as one redeemability object from inbound packet validation through mint, holding, burn, and release. Bare denom allow-listed should never be enough to mint a shared wrapped asset.
  • Enforce source-path validation at the application layer even when transport proofs are valid. In this case, parse_voucher_denom(&msg.denom, &packet.src) or an equivalent invariant needed to remain a hard mint precondition, not an optional convenience check.
  • Preserve route-local balance accounting like reduce_channel_balance(...) as part of authenticity, not mere bookkeeping. A healthy bridge should fail closed when the inbound route cannot prove it has the escrow headroom it is trying to mint against.
  • Default new channels and route additions to isolated liability buckets, fresh wrappers, or low-cap quarantine until operators explicitly prove they are entitled to share an existing saToken redeemability domain.
  • Key monitoring, reconciliation, and pause controls by channel / route as well as by token symbol. Operators should be able to answer which channel authored this saUSDT supply? before any large redemption is honored.
  • Require route-local or per-asset release caps on the canonical exit path so one wrong-channel mint cannot immediately redeem the full backing of the healthy channel.
  • Review long-lived forks and migrations like verifier upgrades. If a fork has removed route-binding checks, carrying that code unchanged into a later migration is still preserving an authenticity break.
  • In privacy-preserving wrappers, publish enough reserve-side telemetry or reconciliation proofs that wrong-channel liabilities cannot hide inside opaque token balances until an escrow account runs dry.

Audit prompts

  • What exact tuple defines redeemability here: bare denom, full denom trace, source port/channel, escrow bucket, wrapped-token contract, or some stricter canonical object?
  • Can a new permissionless IBC channel or peer mint into an existing wrapped representation without first proving it shares the same backing and unwind path as the canonical route?
  • Where is same asset name on a different channel is a different liability unless proven otherwise enforced, and is that enforcement shared across mint, burn, and release logic?
  • If parse_voucher_denom(...)-style checks disappeared tomorrow, what independent control would still prevent wrong-channel packets from minting spendable inventory?
  • Does route-local accounting survive all code paths, or can a packet mint against aggregate token identity without consuming balance from the specific channel whose escrow is supposed to back it?
  • Can operators reconcile wrapped supply against escrow per channel, or do they only see aggregate saUSDT / saUSDC totals that hide route contamination until redemption fails?
  • If one wrong-channel mint lands anyway, what per-route pause, cap, delay, or quarantine keeps the legitimate return lane from immediately honoring the counterfeit inventory?
  • During migrations, forks, or wrapper customizations, which tests prove the deployed code still binds voucher denom trace, packet source, channel-local balance, and redeemability domain together?

Lessons that should already be standard

  • Permissionless transport is not the bug. The bug is letting a permissionless new route inherit the reserve semantics of an existing canonical route just because metadata looks familiar.
  • In channel-based bridges, route identity is asset identity. uusdt over attacker channel-227 and uusdt over Axelar channel-69/channel-61 are not the same bridge liability unless the bridge proves they share one backing object.
  • Route-local accounting is part of authentication. Losing the quantitative cap that ties minting to a specific channel’s escrow can be as fatal as losing the source-channel check itself.
  • Shared wrappers are dangerous when they flatten failure domains. One user-facing token like saUSDT can quietly become a balance-sheet merger between healthy and malicious routes unless the bridge resists that aliasing explicitly.
  • Privacy features increase the need for explicit reserve observability. Hidden balances do not excuse weak route-local reconciliation when the wrapped asset can still redeem against visible escrow elsewhere.
  • Long-lived forks deserve the same suspicion as fresh changes. A missing invariant that survived from 2023 into a 2026 migration was still a live bridge-authenticity defect, not legacy harmlessness.
  • Bounded release is a separate layer. Even after route authenticity fails once, the healthy bridge question is why the legitimate exit lane was still allowed to release nearly the full live backing of seven assets in minutes.

Internal linkages

Control surface