Root Cause
The FlashLoopAdapter module protected its open() and close() functions with this authorization check:
require(ISafe(msg.sender).isModuleEnabled(address(this)), "not enabled");
This pattern asks msg.sender — the calling address — whether this module is enabled on it. An attacker can trivially bypass it by deploying a malicious Safe contract that unconditionally returns true from isModuleEnabled, regardless of actual state.
This is the self-referential authorization anti-pattern: the module trusts the entity that controls msg.sender to honestly report its own authorization. Any contract implementing ISafe can lie.
// Attacker's malicious Safe — passes the check unconditionally
contract MaliciousSafe {
function isModuleEnabled(address) external pure returns (bool) {
return true; // always "enabled"
}
// ... minimal ISafe interface stubs
}
// Attacker calls: FlashLoopAdapter.open() with malicious Safe as msg.sender
// → check passes → flash loan executes → victim collateral drained
Additionally, both victim Safe wallets shared a single owner, concentrating blast radius.
Attack Steps
| Step | Action | Detail |
|---|---|---|
| 1 | Deploy malicious Safe | Implements ISafe with isModuleEnabled always returning true |
| 2 | Bypass access control | Calls FlashLoopAdapter.open() — check passes against attacker-controlled Safe |
| 3 | Flash loan | Borrows ~1,335 WETH to repay victim wallet's outstanding Aave v3 debt |
| 4 | Unlock collateral | Debt repayment unlocks ~1,306 weETH in collateral from victim wallet |
| 5 | Drain wallet 2 | Second victim wallet drained using same technique (shared owner) |
| 6 | Repay flash loan | Net profit: |
Impact
- USD loss: approximately $305,000–$310,000 (114.09 ETH)
- Chain: Ethereum mainnet
- Protocol: FlashLoopAdapter (third-party Safe module); Aave v3 core contracts confirmed unaffected by Aave founder Stani Kulechov
- Recovery: None confirmed as of October 2026
- Additional risk surface: Ownership of the vulnerable module remains unclear; highlights systemic risks from third-party DeFi tooling with unclear audit provenance
Lessons for Auditors
- Never use
msg.sender.check()as the sole authorization gate. Any interface call tomsg.sendercan be spoofed by a malicious contract implementing that interface. Authorization must be verified by a trusted, immutable third party (e.g., a canonical Safe singleton or on-chain registry). - Modules should verify callers through the Safe's own trust model. Safe modules can check
Safe(safe).isModuleEnabled(address(this))wheresafeis a pre-stored, trusted address — not the dynamicmsg.sender. - Third-party modules extending major protocols carry independent risk. Aave v3's audit scope does not cover third-party Safe modules. Users must separately audit every module they enable.
- Flash-loan + collateral-unlock is a standard post-bypass drain pattern. After defeating access control, attackers routinely use a flash loan to clear victim debt in a single atomic transaction, releasing collateral for immediate withdrawal. Test this pattern against any module that can interact with lending protocol positions.