Root Cause
The FlashLoopAdapter module — used to loop leveraged Aave V3 positions inside a Safe multisig — checked whether the calling address had enabled the module using:
require(
ISafe(msg.sender).isModuleEnabled(address(this)),
"Module not enabled"
);
Because msg.sender is attacker-controlled, an attacker can deploy a fake Safe contract that always returns true from isModuleEnabled, bypassing this guard entirely. A second weakness in the _swap() function gave the caller unconstrained control over the router address and calldata, allowing arbitrary calls within the module's execution context.
// Attacker's fake Safe
contract FakeSafe {
function isModuleEnabled(address) external pure returns (bool) {
return true; // always passes the check
}
}
Attack Steps
| Step | Action | Detail |
|---|---|---|
| 1 | Deploy FakeSafe | Returns true for all isModuleEnabled queries |
| 2 | Flash-borrow 11,537 WETH via Morpho | Funds execution within same tx |
| 3 | Call open() on FlashLoopAdapter | FakeSafe passes module check; attacker gains execution context |
| 4 | Repay ~1,300 WETH victim debt via _swap() | Unlocks collateral in victim Safes |
| 5 | Withdraw weETH and Aave collateral assets | Drained across two victim Safe multisigs |
| 6 | Repay Morpho flash loan | Attacker keeps ~114.09 ETH net profit |
Transaction confirmed at 15:08:47 UTC, October 1, 2026, in Ethereum block 26098264.
Impact
- Loss:
114.09 ETH ($305K) - Chain: Ethereum mainnet
- Date: October 1, 2026
- Victims: Two Safe multisig wallets that had enabled FlashLoopAdapter
- Aave core: Not affected. The exploit used Aave's lending pools legitimately; the flaw was in the third-party adapter module.
- Fix: No confirmed remediation or patch was published at time of reporting.
Lessons for Auditors
- Never trust
msg.senderas a capability oracle: Any pattern of the formISafe(msg.sender).someCheck()is trivially forgeable. Use an independent registry or pass a verified safe address through a call chain you control. - Module authentication must be bidirectional: The Safe itself should verify the module call originates from an approved source, not the other way round.
- Unconstrained
_swap()calldata is a critical sink: Any function that passes attacker-supplied router addresses and calldata through module context is equivalent todelegatecallwith arbitrary code — treat it as a critical finding. - Flash-loan composability amplifies reach: Even small net balances in victim wallets become drainable when a flash loan can be used to repay debt and unlock collateral atomically.