On 23 October 2026 at 14:59 UTC, an attacker used a flaw in the script cache of Initia’s MoveVM to credit itself with unbacked WETH, iUSD and USDC on the Initia L1. Within about seven minutes, real assets started leaving Ethereum-side bridge reserves. By the time validators halted the L1 and every rollup at 15:41:30 UTC, roughly $2.7M had been bridged out and swapped into 1,135.52 ETH.
No token contract on Ethereum was hacked, and no bridge signature was forged. The bridges did exactly what they were designed to do: they burned a token on Initia and released the matching asset on Ethereum. The problem is that the token on Initia was never backed. This post walks through what is confirmed, what is still unknown, and what bridge and L1 teams should take from it.
0x358ef013...41cac8, which still held 1,135.52 ETH and had never sent a transaction when we checked it on 27 October.Initia is a layer 1 built on the Cosmos SDK, with MoveVM as its smart contract engine and a family of rollups that settle to it. Most of the dollar and ether liquidity on the chain is not native. It arrives through bridges:
The security of every one of these copies reduces to a single invariant: the balances on Initia must never exceed what is locked behind them. If the destination chain can be convinced that someone owns tokens they never deposited, the reserve on the home chain will pay the difference to whoever burns them first.
Initia’s official statement on 24 October says an attacker “exploited a bug in Initia MoveVM’s script caching that allowed malicious code to bypass security checks.” It adds that the attacker “artificially spoofed the MoveVM state,” receiving unbacked WETH, iUSD and USDC “without changing the corresponding token’s total supply.” Co-founder Zon described it as a day-zero bug in Initia’s MoveVM on the Cosmos SDK.
That last detail matters for monitoring. The forged balances did not appear in each token’s recorded supply. A dashboard that watched only total_supply on Initia would have shown nothing unusual while millions of dollars in balances appeared.
Initia has not said which cache, which check, or which code path was involved. A fix and audit were promised “in a few days,” and the L1 and rollups were still halted at the time of writing.
Two independent on-chain write-ups, from D2 Finance (relayed by 0xCERT) and from Bitquery, line up on the shape of the attack.
Bitquery reports that the attacker’s Ethereum wallet, 0xdd922c84...f6edfa, received 0.098 ETH from Tornado Cash the same morning, sent small test transfers toward Osmosis, and bridged a little ETH to Initia. It then received 141 INIT for gas from an Osmosis wallet and uploaded its own program. The first attempt failed. The second worked.
D2 Finance pointed to a single mint transaction at 14:59:26 UTC that called one Move function to credit 2,000,000 AUSD0, with the same pattern for USDC and ETH. Bitquery counted four runs of the attacker’s program within 13 seconds, producing about $7.48M of iUSD, USDC, AUSD0 and WETH at face value. Both agree that nothing was locked anywhere to back it.
Face value overstates the damage. About 3M of that was iUSD, which exists only on Initia and had no bridge to drain. Only the copies of real assets could be redeemed.
We checked the Ethereum side ourselves against a public RPC node:
0x38b8b7ca...f200c844 transferred exactly 1,400,000 AUSD to the attacker.0x66a503a1...0babbc1bde released exactly 105 ETH.Each batch was swapped to ETH within minutes, mostly through 1inch according to Bitquery. Then, at 15:41:35 UTC, five seconds after the halt, the wallet sent 1,135.52 ETH to a fresh address, 0x358ef013...41cac8. That address had a transaction count of zero and the same balance when we read it on 27 October.
Bitquery found that three USDC withdrawals bounced because Noble’s reserve no longer held enough. A final 200,000 AUSD0 message stalled because LayerZero Labs’ own verifier did not sign it, and the AUSD adapter blocked it later that day. Roughly $1.55M of copies of real assets, plus the unsellable iUSD, were still on Initia when the chain stopped.
Ordinary users paid as well. After the attacker’s withdrawals, eleven normal USDC redemptions drained most of what remained in Noble’s reserve, and Bitquery counted 55 later withdrawals that failed before the halt.
Initia has not published the mechanism, so we will not guess at its code. The general failure class is well known to anyone who audits VMs, and it is worth stating plainly.
A Move VM does expensive work before it runs a script: it deserializes the bytecode, runs the bytecode verifier, and checks type and resource-safety rules. Those rules are what stop a script from creating a Coin or FungibleAsset value out of thin air or writing to storage it does not own. Caching the result is a reasonable optimisation. It becomes dangerous when the cache key does not fully bind what was verified. If the key misses the exact bytes, the dependency set, or the context the check depended on, a later execution can reuse a “verified” result for code that was never verified.
In a resource-oriented VM, skipping verification is close to the worst case. Resource safety is the mint authority. Bypass it, and a balance can exist without the module that controls supply ever being asked. That is consistent with Initia’s description of balances that appeared while recorded supply did not move.
The lesson for auditors: in VM and runtime reviews, every cache in front of a security check is itself a security boundary. Review what the key contains, when entries are invalidated, and whether a cache hit can ever skip a check that a miss would run.
The VM bug created the forged balances. The bridges turned them into $2.7M. A bridge cannot tell a forged balance from a real one if the source chain’s state says it is real, so the last line of defense is how much it will release, and how fast.
Here is a simplified, illustrative Solidity sketch of an outflow limit on a lockbox adapter. It is not LayerZero’s or Initia’s code:
// Simplified illustration only. Not production code.
contract RateLimitedLockbox {
uint256 public constant WINDOW = 1 hours;
uint256 public maxOutflowPerWindow; // e.g. 10% of locked reserves
uint256 public windowStart;
uint256 public outflowInWindow;
function _release(address to, uint256 amount) internal {
if (block.timestamp >= windowStart + WINDOW) {
windowStart = block.timestamp;
outflowInWindow = 0;
}
outflowInWindow += amount;
require(outflowInWindow <= maxOutflowPerWindow, "outflow limit");
token.transfer(to, amount);
}
}
With a cap of, say, 10% of reserves per hour, a 1.4M AUSD release out of a larger pool, plus a 105 ETH release out of a reserve that ended 96% drained, would have hit the limit and paused. That would have bought the 42 minutes Initia needed to halt the chain.
It is worth noting what did work. LayerZero’s verifier set includes a LayerZero Labs verifier, and the final AUSD message stalled when it did not sign. Noble’s reserve could only pay what it held, which capped the USDC loss. Earlier we covered a negative fee that drained the WAX bridge and a Sui bridge claim bot that paid the Cetus attacker. The bugs differ, but each raises the same audit question about a bridge: how much can leave before anyone is able to react?
Any cache that stores a verification, type-check or permission result must be keyed by everything the result depended on: the full code hash, dependency versions, and the execution context. Add tests that try to reach execution through a cache hit with code the verifier would reject.
Initia’s forged balances left recorded supply untouched. An invariant check that sums balances for bridged assets, or reconciles each bridged token against its remote lockbox, would have flagged the attack in its first block.
Per-asset, per-window outflow caps on lockboxes and mint limits on the destination side turn an unlimited drain into a bounded one. Pair them with an automated pause when a cap is hit.
The one AUSD message that stalled was the one that lacked a signature from an independent verifier. Bridges should require more than one independent party to approve a release, and those parties should be allowed to refuse when the amount looks wrong.
Initia stopped the chain 42 minutes after the first forged balance appeared. That is fast for a full L1 halt, and still long enough to lose $2.7M. Chains with bridge exposure should know exactly who can pause which bridge, and practice doing it.
For a broader pre-launch list, see our smart contract security checklist.
0xdd922c841d85e12dac2dc2eef5a5e29cfff6edfa0x358ef0130f7a5aa516dcd81bdc80b18cde41cac80x034c1076...be17f3b60x5cd422fd...0887d7e90x60f15b7e...1043e060x38b8b7ca28e672018a299b20565aca8cf200c8440x66a503a1060ab3f2b1aaabed613fe30babbc1bde67423E84815CEC63632BD4676CA22D33567F3E02C3187281C9B6BFD593CBA7AFThe Initia exploit is a reminder that a bridge is only as sound as the state of the chain it trusts. A single cache bug in the VM was enough to fake balances, and bridges with no outflow limits converted them into real ETH within minutes. When the full post-mortem lands, we will be watching for exactly what the cache key missed.
Building on Move, running a bridge, or launching an appchain? Contact Panda Security for a review of your VM integration and bridge controls, or see our audit pricing.
A zero-quorum DAO vote gave an attacker Yapeswap's feeToSetter role, and a fake Yearn registry then routed every pair's farmed reserves into attacker vaults.
Flash-loaned X tokens bought two of three X100 Senate seats, and one transaction later the Freezer treasury sold 400,000 X for about 36 USDT on Ethereum.
A missing sign check on max_fee in WAX's eth.token withdraw action let an attacker bridge out ~$90K in USDC, USDT, DAI, WETH and WBTC for free.
A stale sell counter in NemTokenV2 let an attacker burn the PancakeSwap pair's own NEM 42 times and sell into a pumped price for $822K on BNB Chain.