58666 sc low recipient owner not enforced in action verifiers enables theft of swap proceeds

Submitted on Nov 3rd 2025 at 23:02:56 UTC by @Johnyfwesh for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58666

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol

  • Impacts:

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Finding Description and Impact

ZeroXSwapVerifier.decodeAndVerifyActions() never validates the SlippageAndActions.recipient or the propagated owner field, so downstream action verifiers accept calldata that forwards swap proceeds to arbitrary recipients. In both verifier entry points, _verifyExecuteCalldata and _verifyExecuteMetaTxnCalldata, the struct is decoded and the code immediately relays saa.actions to _verifyActions without checking saa.recipient (src/utils/ZeroXSwapVerifier.sol:125-143arrow-up-right). Inside the action dispatch, neither _verifyBasicSellToPool, _verifyUniswapV3VIP, nor _verifyTransferFrom inspect the decoded recipient; _verifyTransferFrom even ignores the from/to parameters entirely (src/utils/ZeroXSwapVerifier.sol:163-246arrow-up-right).

Integrations such as the allocator pipeline inherit the verifier’s result as a hard precondition before calling the 0x settler (src/AlchemistAllocator.solarrow-up-right). Because the library returns true for any calldata that satisfies token and slippage checks, an attacker can craft a bundle where SlippageAndActions.recipient (or a per-action recipient) is the attacker’s address, while keeping the sell token and BPS within bounds. The malicious calldata passes verifySwapCalldata, allowing the privileged caller to forward the payload to the 0x settler, which then executes transfers that siphon proceeds to the attacker instead of the intended owner.

Affected code


Impact

Swap proceeds can be rerouted

  • An attacker supplies quotes where SlippageAndActions.recipient (or the Uniswap VIP recipient) is the attacker.

  • verifySwapCalldata returns true, so orchestrators trust the calldata and forward it to the 0x settler.

  • During settlement, proceeds are transferred to the attacker’s address, resulting in direct fund theft with no on-chain validation preventing it.

Owner/recipient spoofing breaks accounting

  • Integrators that assume the verifier enforces the owner or recipient can account rewards or balances to the wrong party.

  • Because _verifyTransferFrom ignores both from and to, an attacker can pull assets from any approved address while verifySwapCalldata still succeeds, bypassing intended recipient controls.


  1. Explicitly validate saa.recipient against the expected owner (or a caller-provided allowlist) before delegating to _verifyActions.

  2. Within each action verifier, enforce that decoded recipient/output addresses match saa.recipient (or the caller’s supplied target) and that owner is respected for source addresses.

  3. Reject any action that omits or mismatches the enforced addresses, and extend tests to cover each action type to prevent regressions.


Proof of Concept

PoC Test testRecipientCanStealSwapProceeds That will Run in (src/test/Poc.t.sol)

Exploit Sequence

  1. Deploy a victim executor that calls ZeroXSwapVerifier.verifySwapCalldata before delegating swaps to a 0x settler.

  2. Mint vault tokens, transfer them to the executor, and craft swap calldata whose SlippageAndActions.recipient is the attacker.

  3. The verifier returns true because slippage and token filters pass, so the executor approves the settler and forwards the call.

  4. The mock settler executes transferFrom using the malicious recipient, draining the vault while the legitimate owner receives nothing.

The test demonstrates the theft and logs the balance changes: Add this test file in the src/test folder

Result (src/test/Poc.t.sol:101):

Was this helpful?