On October 1, 2026, an attacker used a flash loan to drain about 114.09 ETH (roughly $305,000) from two Safe multisig wallets on Ethereum mainnet in a single transaction. The flash loan amplified the attack; it was not the cause. The exploit stemmed from an access-control flaw in FlashLoopAdapter, a custom Safe module for leveraged Aave v3 positions. The module let callers vouch for their own identity and choose both the target address and calldata. Neither Aave v3 nor Safe’s core contracts was compromised.
Overview
Date: October 1, 2026, 15:08:47 UTC
Network: Ethereum Mainnet
Total Loss: Approximately 114.09 ETH (~$305,000)
Vulnerable Contract:
FlashLoopAdapterat 0x16bb8b912da187870c23ec6756bb3fad061283d8Attack Transaction: 0x75328f916b1a0878724d364da5eb12b255160b894cb36c63ed5d718efc616fc4
Attacker EOA: 0x42c2633438609881c8fBAb82414eb9A0c45F9353
Attacker Contract: 0xF09168963ac7b31917A02Aa82fA9Cd667F4B67ff
Victim Safe:
This was not a compromised-key or malicious-admin incident. The module behaved exactly as implemented. The failure emerged from the interaction between a spoofable caller check, fully caller-controlled swap parameters, and the unconditional authority a Safe grants to an enabled module.
Technical Analysis of the Exploit
Protocol Architecture
A Safe multisig can be extended with modules - contracts the Safe’s owners authorize to execute transactions without collecting owner signatures. Once enabled, a module calls execTransactionFromModule, and the Safe carries out whatever it is told.
FlashLoopAdapter was designed to open and close leveraged positions on Aave v3 in a single transaction. The strategy loops by supplying weETH as collateral and borrowing WETH against it, repeating the cycle to increase the Safe’s exposure. The module’s intended workflows were:
Open: flash-borrow the debt asset, swap it for collateral through an aggregator, and supply that collateral on the Safe’s behalf. The Safe then borrows enough to repay the flash loan, using
execTransactionFromModule.Close: flash-borrow the debt asset and use it to repay the Safe’s Aave debt. The Safe then withdraws collateral to the adapter through
execTransactionFromModule; the adapter swaps it back into the debt asset and repays the flash loan.
On-chain evidence points to the module being private yield tooling rather than a public product: it was deployed on July 16, 2026, and both victim Safes enabled it the next day. No other Safe appears in its transaction history.
Root Cause
The root cause was two flaws that composed into a full takeover primitive.
1. Spoofable caller authentication.
The only gate on open() and close() was:
isModuleEnabled is a view function queried on msg.sender. A genuine Safe reports whether the adapter is enabled, but any contract can implement the same function and return true.
2. Fully caller-controlled swap leg.
Every field in Params is supplied by the caller - including the flash lender (providerAddr), swap router (swapRouter), and router calldata (swapCalldata). The swap helper approves the router to spend tokenIn, then forwards the caller-provided calldata directly to that address:
The flash-callback guard in _validate checks only that the callback came from providerAddr and that the parameters match the hash committed by _start. Because the attacker supplied its own contract as providerAddr, it could satisfy both checks itself.
The attacker then set swapRouter to a victim Safe and swapCalldata to an execTransactionFromModule(...) call. The adapter made that call, so the Safe saw FlashLoopAdapter - its enabled module - as msg.sender and executed the attacker’s instructions.
Attack Flow
In one transaction, the attacker borrowed WETH from Morpho, repaid the Safe’s Aave debt, then exploited FlashLoopAdapter’s spoofable caller check to withdraw both Safes’ weETH. After swapping the collateral, the attacker repaid the loan and sent the remaining proceeds to its EOA. The transaction trace records the key calls and transfers.
1. Flash Loan from Morpho

The attacker’s contract borrowed 11,537.24 WETH from Morpho Blue - about nine times the amount it ultimately needed. It returned the unused funds in the same transaction. Morpho provided permissionless flash liquidity and was repaid in full; its role was limited to financing the attack.
2. Repaying the Victim’s Debt

onBehalfOf = the Safe, payer = the attacker.The larger Safe had a heavily leveraged position: 1,335.26 WETH in variable debt against 1,306.48 weETH in collateral. Because Aave will not release collateral that secures an outstanding loan, the attacker first repaid the Safe’s debt in full. The pool’s Repay event records 1,335.255803 WETH repaid, identifying the Safe as the borrower and the attacker’s contract as the payer. The attacker used flash-borrowed funds for the repayment, then recovered them from the collateral proceeds.
3. Passing the Gate

open() - and answered its own isModuleEnabled check with true.The attacker’s contract called the adapter’s entrypoint and answered true when the adapter queried isModuleEnabled - despite not being a Safe. The adapter then hashed the caller-supplied parameters into _pending and initiated a “flash loan” from providerAddr, which the attacker had set to its own contract.
4. Driving the Safes
The callback check passed because the attacker’s contract was the declared providerAddr. In the callback, the adapter called router.call(data) with a victim Safe as router and an execTransactionFromModule payload as data. Because the adapter was an enabled module, each Safe treated the call as authorized and executed the attacker’s instructions:
The larger Safe withdrew its 1,306.48 weETH Aave collateral to the attacker.
The smaller Safe transferred its 6.43 weETH balance to the attacker.

5. Swapping and Settlement
The attacker routed the combined 1,312.90 weETH through Curve (CurveStableSwapNG), Fluid, and Uniswap - including v3 pools and the Universal Router - using Velora’s AugustusV6 aggregator. The swaps yielded 1,449.3520 WETH. The attacker then:
Repaid Morpho’s 11,537.24 WETH flash loan.
Covered the 1,335.2558 WETH used to repay the victim Safe’s Aave debt.
Converted the remaining 114.0962 WETH to ETH and sent it to the attacker’s EOA.
The transaction trace reconciles the proceeds after the Aave repayment:
1,449.351954 − 1,335.255803 = 114.096151 WETH. This matches both the WETHWithdrawalevent and the internal transfer of 114.096151 ETH to the attacker’s EOA.
Security Lessons
A Module Is a Signer
Enabling a Safe module bypasses the multisig threshold, giving the module authority to execute transactions without owner signatures. Review modules with the same rigor as signers - not as routine integrations. Inventory the modules enabled on every Safe with getModulesPaginated, and disable any module whose author and reviewer you cannot identify.
Never Authenticate the Caller by Asking the Caller
A response from isModuleEnabled(), owner(), or supportsInterface() cannot establish trust when the caller controls the implementation. Instead, bind the module to the Safe addresses it is authorized to serve during setup, or require owner signatures for each call. Never base authorization on claims made by an untrusted caller.
Treat Caller-Supplied Call Targets as an Exploit Primitive
When callers control both the target and calldata in a low-level call (target.call(data)), they can redirect execution to a victim Safe or abuse token approvals and administrative functions. An allowlist of trusted routers - explicitly excluding every enabled Safe - would have prevented this exploit, even if the caller check remained spoofable.
Conclusion
The attacker did not compromise Aave, Safe, or any cryptographic primitive. Instead, they lied about their identity to the module, then used it to execute instructions against the Safes that trusted it. The flash loan financed and amplified the attack; the underlying failure was a module that accepted the caller’s own claim of authorization and executed caller-controlled targets and calldata.
For Safe operators, the lesson is clear: an enabled module has signer-level authority. Treat it with the same scrutiny as a signer, and never enable a module until you have reviewed and understood its access controls.





