H-1: `PermissionedProxy` selector filter bypass via `VaultV2.multicall` enables blocked function execution
Summary:
PermissionedProxy.proxy only validates the outer calldata selector. Because permissionedCalls is enforced as a denylist (require(!permissionedCalls[selector], "PD")), an operator can call vault.multicall(...) (not blocked) and tunnel blocked calls like vault.allocate(...) inside it. This bypasses wrapper-level safeguards such as _validateCaps.
Description:
PermissionedProxy.proxy checks only bytes4(data) of the top-level payload (src/utils/PermissionedProxy.sol:63-src/utils/PermissionedProxy.sol:69).
In AlchemistAllocator, raw allocate/deallocate selectors are blocked in permissionedCalls (src/AlchemistAllocator.sol:27-src/AlchemistAllocator.sol:29) to force use of wrapper methods that run _validateCaps (src/AlchemistAllocator.sol:41-src/AlchemistAllocator.sol:48, src/AlchemistAllocator.sol:122-src/AlchemistAllocator.sol:149).
By sending vault.multicall([abi.encodeCall(vault.allocate, ...)]) through proxy:
- Outer selector is
multicall, which is not blocked. VaultV2.multicallexecutes inner payloads viadelegatecall(lib/vault-v2/src/VaultV2.sol:282-lib/vault-v2/src/VaultV2.sol:285).msg.senderremains the allocator wrapper during inner execution, soVaultV2.allocateauthorization passes (lib/vault-v2/src/VaultV2.sol:566-lib/vault-v2/src/VaultV2.sol:568)._validateCapsinAlchemistAllocatoris never reached.
Impact:
High. Operators can execute blocked vault functions by wrapping them in multicall, bypassing intended access/risk controls. For allocator flow, this allows bypassing classifier cap enforcement and allocating above wrapper-imposed limits.
Recommendation:
Block proxying multicall or use a whitelist approach.
Developer Response:
Changed to whitelist on b273fcb