SquidRouterModule express path and forged sourceAddress
The express route checked a sourceAddress string supplied by the caller before it moved into the deeper execution flow. That is weak ground for a security boundary. If the source metadata can be forged, the check becomes a formality rather than a control.
In the observed path, the call used expressExecuteWithToken with commandId set to zero, an empty source-chain string, tokenSymbol set to WETH, and amount set to 0. The module still continued into the post-bridge path. A bridge execution that never bridged anything should not be able to drive Safe actions, but this one did.
A zero-amount call still reached the post-bridge path
The amount field was not acting as a meaningful gate. The module accepted the express call and processed it even though no bridged value was settled. That left the execution path open to local asset movement without a legitimate Axelar or Squid message behind it.
A zero-amount express call is not harmless padding. It can be enough to reach code that assumes the bridge has already done the hard part.
The forged sourceAddress cleared the SquidRouter check
The source address check compared against a value the caller could shape. That made the control depend on untrusted input before any stronger verification had happened. Once the forged source address matched what the module expected, the path moved on as if the message were genuine.
Payload bytes turned the Safe, delegate, and permissions into attacker input
The payload bytes were doing more than carrying instructions. They carried the module address, the victim Safe, a delegate identity, and the action parameters used later in execution. Decoding those fields directly from attacker-controlled bytes gave the caller control over the context used for permission checks.
That is the real fault line here. The module did not derive the delegate from a trusted execution context. It read one from the payload and then trusted it.
Decoded module and Safe values drove the module call
The payload was decoded into (address module, address safe, address delegate, ActionsExecutionParams memory params). The module check only required the decoded module to match the contract address. The Safe address then fed into the next stage of execution.
Once the victim Safe address was embedded in the payload, the module could act on that Safe through execTransactionFromModule. The attacker did not need to compromise the Safe itself. The module was enough.
Fake delegate identity satisfied Safe permission checks
The delegate field from the payload was used in permission checks through hasPermission. That meant the attacker could supply a delegate identity that already carried the needed rights on paper. The permission system then answered the question it had been asked, not the one it should have asked.
In this case the forged delegate satisfied permissions for APPROVE, SWAP, and WRAP. That opened the door to module actions that looked authorised from inside the contract, even though the identity came from attacker input.
Safe assets were spent through Permit2 and Uniswap after the bypass
Once the permission check passed, the module could instruct the victim Safe to approve routes and execute swaps. Permit2 approvals and the Uniswap Universal Router then became the path out. The module had enough authority to move value once it was handed a trusted-looking delegate and a matching Safe.
The result was not subtle. Wrapped ETH and token swaps moved value from the victim Safe into small outputs. The path included Permit2 allowances, wrapping, and swaps through Uniswap V3 pools.
Approved routes opened the path to token movement
The Safe first approved Permit2 and the Uniswap Universal Router. After that, the module could route asset movement through those approvals without another meaningful check at the Safe boundary. A single bad express execution can turn a one-off bypass into a clean transfer path.
The approval step matters because it is not the end of the exploit. It is the point where the module gets enough room to keep spending.
Wraps and swaps drained value from the victim Safe
The execution path used ETH wrapping and Uniswap swaps to pull value from the victim Safe. Reported assets included WBTC, wTAO, WETH, and u, with the final outputs reduced to small amounts. The exact asset mix matters less than the pattern: once the module could call through the Safe, the rest was routine spending.
The contract treated an express post-bridge call as valid without proving the call came from a real bridge event. That is the mistake. Payload fields are not a trust anchor. The execution context has to be locked to a message the contract can verify, not a string and a few decoded bytes.

