security: add buyToken whitelist to prevent malicious token swaps#20
Open
senti23 wants to merge 2 commits intoCreator-Bid:mainfrom
Open
security: add buyToken whitelist to prevent malicious token swaps#20senti23 wants to merge 2 commits intoCreator-Bid:mainfrom
senti23 wants to merge 2 commits intoCreator-Bid:mainfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Title
security: add buyToken whitelist to prevent malicious token swapsWhat
We discovered that Clawlett's AI agent could swap Safe funds into any arbitrary token — including honeypots, ruggable tokens, or attacker-controlled contracts. The only guardrail was the AI prompt, which is trivially bypassable.
This PR adds on-chain token whitelisting that the agent physically cannot bypass. Two fixes work together to close all unvalidated swap paths:
Fix 1:
ZodiacHelpersV3.sol— output token validation inaeroExecute()cowPreSign()already had a whitelist check from the initial V3 build. However, an independent audit found thataeroExecute()validated command types and recipient addresses but did NOT validate the output token — the same class of vulnerability the whitelist was designed to prevent. This fix adds_extractOutputToken()+_isWhitelisted()toaeroExecute(), covering V3 packed paths, V2Route[]arrays, and skipping WRAP/UNWRAP. 6 new tests.Fix 2:
initialize.js— removed direct Aerodrome Router permissionThe old
initialize.jsgrantedallowTarget(AeroUniversalRouter, Send)— meaning the agent could call the router directly, skippingaeroExecute()entirely. Now all Aerodrome calls must route through ZodiacHelpers.Why both are needed:
Scenario | Without Fix 1 | Without Fix 2 -- | -- | -- Attack vector | Agent calls aeroExecute() with malicious output token — no check, passes through | Agent calls Aerodrome Router directly — bypasses aeroExecute() entirely Result | Whitelist meaningless for Aerodrome path | Fix 1 meaningless since agent skips itFiles Changed
contracts/src/WhitelistRegistry.sol— new: standalone token registrycontracts/src/ZodiacHelpersV3.sol— new: whitelist-enforced helpers withcowPreSign()+aeroExecute()output token checkscontracts/test/ZodiacHelpersV3.t.sol— new: 21 fork tests against live Base mainnetcontracts/script/DeployV3.s.sol— new: Foundry deployment scriptclawlett/scripts/manage-whitelist.js— new: CLI tool for whitelist management with--executeflagclawlett/scripts/initialize.js— updated: removed direct Aerodrome Router permission, only ZodiacHelpers gets accessSetup for Other Users
# PR Titlesecurity: add buyToken whitelist to prevent malicious token swapsWhat
We discovered that Clawlett's AI agent could swap Safe funds into any arbitrary token — including honeypots, ruggable tokens, or attacker-controlled contracts. The only guardrail was the AI prompt, which is trivially bypassable.
This PR adds on-chain token whitelisting that the agent physically cannot bypass. Two fixes work together to close all unvalidated swap paths:
Fix 1:
ZodiacHelpersV3.sol— output token validation inaeroExecute()cowPreSign()already had a whitelist check from the initial V3 build. However, an independent audit found thataeroExecute()validated command types and recipient addresses but did NOT validate the output token — the same class of vulnerability the whitelist was designed to prevent. This fix adds_extractOutputToken()+_isWhitelisted()toaeroExecute(), covering V3 packed paths, V2Route[]arrays, and skipping WRAP/UNWRAP. 6 new tests.Fix 2:
initialize.js— removed direct Aerodrome Router permissionThe old
initialize.jsgrantedallowTarget(AeroUniversalRouter, Send)— meaning the agent could call the router directly, skippingaeroExecute()entirely. Now all Aerodrome calls must route through ZodiacHelpers.Why both are needed:
aeroExecute()with malicious output token — no check, passes throughaeroExecute()entirelyBoth together: all Aerodrome swaps must go through
aeroExecute()→ which validates the output token against the WhitelistRegistry.New contracts:
[0x7c956833DaaCAC47317c96627d24A2021d1B95E5](https://basescan.org/address/0x7c956833DaaCAC47317c96627d24A2021d1B95E5)) — standalone registry withaddToken/removeToken, owned by the Safe[0xc2B310D652A7793C19f9f6D73cF7bF5D0B10B9Ab](https://basescan.org/address/0xc2B310D652A7793C19f9f6D73cF7bF5D0B10B9Ab)) — updated helpers contract that checksisWhitelisted(buyToken)before allowing swaps via both CoW Protocol and AerodromeNew tooling:
--executeflag (signs + submits Safe txs, no web UI needed)Why
The original ZodiacHelpers contract enforced receiver restrictions (funds always land in the Safe) but did not restrict which token the agent could swap into. ZodiacHelpersV3 added a whitelist check to
cowPreSign()(CoW Protocol path), but left the Aerodrome path (aeroExecute()) unprotected. A compromised agent could still:aeroExecute()to swap Safe USDC → attacker-controlled token (funds land in Safe ✅, but token is worthless)Additionally,
initialize.jsgranted directallowTargetaccess to the Aerodrome Router, allowing the agent to bypassaeroExecute()entirely.Note: The codebase already applies this exact whitelist pattern for Trenches factory contracts via
FactoryNotWhitelisted(). This PR extends the same approach to swap output tokens — same logic, different parameter.Attack Surface (Before Fix → After Fix)
cowPreSign(CoW Protocol)aeroExecute(Aerodrome Universal Router)allowTarget)How It Works
WhitelistRegistry.sol:
mapping(address => bool)+address[]for enumerationonlyOwner(the Safe) canaddToken/removeToken/addTokensZodiacHelpersV3.sol:
_isWhitelisted()callsIWhitelistRegistry(WHITELIST_REGISTRY).isWhitelisted(token)via STATICCALLWHITELIST_REGISTRYstored asimmutable(in bytecode, not storage) — safe for delegatecall contextcowPreSign()reverts iforder.buyTokenis not whitelisted (existing, unchanged)aeroExecute()(new) extracts the output token from each swap command and checks the whitelist:routes[last].tofrom Aerodrome'sRoute[]structinitialize.js:
scopeTarget(AeroUniversalRouter)+allowTarget(AeroUniversalRouter, Send)allowTarget(ZodiacHelpers, Both))aeroExecute()which enforces the whitelistWhy V3 over V2:
Independent Audit
An independent agent audit was performed before submission. Key finding:
aeroExecute()validated command types and recipient addresses but did NOT validate output tokens — the same class of vulnerability the whitelist was designed to prevent. This was fixed and verified with 6 additional tests.Testing
Key test cases for
aeroExecute:Deployed Contracts (Base Mainnet)
[0x7c956833DaaCAC47317c96627d24A2021d1B95E5](https://basescan.org/address/0x7c956833DaaCAC47317c96627d24A2021d1B95E5)[0xc2B310D652A7793C19f9f6D73cF7bF5D0B10B9Ab](https://basescan.org/address/0xc2B310D652A7793C19f9f6D73cF7bF5D0B10B9Ab)Files Changed
contracts/src/WhitelistRegistry.sol— new: standalone token registrycontracts/src/ZodiacHelpersV3.sol— new: whitelist-enforced helpers withcowPreSign()+aeroExecute()output token checkscontracts/test/ZodiacHelpersV3.t.sol— new: 21 fork tests against live Base mainnetcontracts/script/DeployV3.s.sol— new: Foundry deployment scriptclawlett/scripts/manage-whitelist.js— new: CLI tool for whitelist management with--executeflagclawlett/scripts/initialize.js— updated: removed direct Aerodrome Router permission, only ZodiacHelpers gets accessSetup for Other Users