58604 sc low verification bypass in verifyexecutemetatxncalldata enables arbitrary 0x actions to pass checks and execute in the zeroxswapverifier sol contract
Submitted on Nov 3rd 2025 at 14:00:30 UTC by @Kissiahmyo for Audit Comp | Alchemix V3
Report ID: #58604
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol
Impacts:
Protocol insolvency
Description
Brief/Intro
The ZeroXSwapVerifier.sol library’s _verifyExecuteMetaTxnCalldata decodes both a SlippageAndActions struct and a separate bytes[] actions argument, but it verifies only the struct’s embedded actions. If the actual 0x Settler execution reads the separate actions argument (a common ABI style), a malicious caller can craft calldata where benign actions are placed in the struct while harmful actions are placed in the second parameter bytes[] actions. The verifier returns success, but settlement executes the harmful actions, bypassing whitelists, slippage limits, and recipient restrictions. This creates a practical path to reroute assets or violate risk controls in production.
Vulnerability Details
Context and goal: verifySwapCalldata() -> decodeAndVerifyActions() -> _verifyExecuteMetaTxnCalldata()
ZeroXSwapVerifieris designed to statically validate 0x Settler calldata for two entry points,execute(SlippageAndActions, bytes[])andexecuteMetaTxn(SlippageAndActions, bytes[], address, bytes). It enforces token whitelists, slippage bounds, allowed action types, and target token constraints before execution.The top-level entry
verifySwapCalldatachecks the function selector, then delegates todecodeAndVerifyActionswhich routes to_verifyExecuteCalldataor_verifyExecuteMetaTxnCalldatabased on selector.
Vulnerable function and code: In src/utils/ZeroXSwapVerifier.sol, _verifyExecuteMetaTxnCalldata decodes (SlippageAndActions, bytes[], address, bytes) but disregards the bytes[] parameter and uses only saa.actions for verification:
ZeroXSwapVerifier.sol::verifyExecuteMetaTxnCalldata#L140
This creates a semantic mismatch: the verifier inspects the struct’s actions, while the actual Settler contract may execute the separate bytes[] actions argument. If these two are not guaranteed identical at runtime, verification can be trivially bypassed.
Impact Details
Gate bypass of risk controls
Many 0x integration ABIs use a struct for metadata (
recipient,buyToken,minAmountOut) and passactionsas a separate array argument that is executed. The current verifier trustssaa.actionsand ignores the secondactionsparameter.An attacker can: Construct calldata with “safe-looking” actions embedded in
SlippageAndActions.actionsto satisfy verification rules. Place malicious or policy-breaking actions in the secondbytes[] actionsparameter that the Settler will actually execute. PassverifySwapCalldataand proceed to execution in the same transaction, achieving “verified but harmful” behavior.
Asset misdirection and loss:
Integrations commonly use
ZeroXSwapVerifier.verifySwapCalldata(...)as a pre-execution gate before calling 0x Settler. With this bypass, “verify then execute” can lead to asset redirection, draining vault/user funds or performing out-of-policy trades.Severity is high where this library gates treasury, vault strategies, or user swaps; a single transaction can pass verification and still execute malicious actions, enabling immediate theft or policy violations.
Additional notes that exacerbate risk:
The function includes a
TODO shall we also verify saa.buyToken ?, indicating important fields likebuyTokenmight be unverified. This weakens policy enforcement further (e.g., wrong output token).The library does not assert consistency between the struct
actionsand the separateactionsparameter, nor does it canonicalize or cross-check intended recipients against actual execution paths.
Recommendation
Validate the exact actions array that will be executed and eliminate ambiguity between duplicated parameters. Bind verification to the execution payload by enforcing equality between the struct’s saa.actions and the separate bytes[] actions argument, or remove one source entirely.
References
File: src/utils/ZeroXSwapVerifier.sol
_verifyExecuteMetaTxnCalldata: decodes(SlippageAndActions, bytes[], address, bytes)but verifies onlysaa.actions.
Entry points: verifySwapCalldata() -> decodeAndVerifyActions() -> _verifyExecuteMetaTxnCalldata()
verifySwapCalldata(bytes calldata, address owner, address targetToken, uint256 maxSlippageBps)decodeAndVerifyActions(bytes calldata, address owner, address targetToken, uint256 maxSlippageBps)
ZeroXSwapVerifier.sol::verifyExecuteMetaTxnCalldata#L140
Proof of Concept
Proof of Concept
The PoC test_poc_executeMetaTxn_verification_bypass can be added at the end of the ZeroXSwapVerifier.t.sol test file to successfully demonstrate the verification bypass vulnerability.
The PoC could be successfully added to the end of ZeroXSwapVerifier.t.sol . The test test_poc_executeMetaTxn_verification_bypass() demonstrates the verification bypass vulnerability by:
Creating benign actions in the
SlippageAndActionsstruct that pass verification (correct token, acceptable slippage) Creating malicious actions in the separatebytes[]parameter that would normally fail verification (wrong token, excessive slippage, suspicious recipient)Showing that verification passes because the verifier only checks
saa.actions(the benign ones) while ignoring the bytes[] actions parameter that would actually be executed by the 0x SettlerProving the bypass works by demonstrating that if the malicious actions were placed directly in the struct, verification would correctly fail
The test passes, confirming that the vulnerability exists and can be exploited to bypass token validation, slippage limits, and recipient restrictions. This PoC provides concrete evidence of the security flaw described in the bug report.
Was this helpful?