Skip to content
Top-down view of a steel desk tiled with identical teal access badges, where one slot holds a red-framed mirror reflecting the badges and bearing a teal approval stamp, as a gloved hand slides it into place
exploitsOctober 8, 20264 min read

FlashLoopAdapter $305K: A Safe Module That Took the Caller's Word for It

Aron Turner
Aron TurnerCo-Founder & CTO

Updated on October 8, 2026

TL;DR

At 15:08:47 UTC on October 1, 2026, one Ethereum transaction emptied a looped Aave v3 weETH position out of one Safe and took a smaller weETH balance out of a second. Both Safes had enabled FlashLoopAdapter, a custom module that opens and closes looped Aave positions for them. The module's open() and close() checked only ISafe(msg.sender).isModuleEnabled(address(this)), and SlowMist called that check "spoofable via a fake Safe that always returns true." The attacker deployed exactly that, named itself as the flash lender, and pointed the module's raw router call at the victim Safes with execTransactionFromModule as the payload. The attacker kept about 114.09 ETH, roughly $305,000. Aave v3 itself was untouched, Stani Kulechov confirmed. After the drain, two more addresses used the same hole: one planted unlimited aToken approvals on both Safes, and another used the same bug to make both Safes disable the module. The owner has offered a 10% bounty on-chain; the ETH had already gone into Railgun. The fix is to bind a module to the Safes it serves, never to whatever its caller reports.


What Happened to the Two Safes?

Two Safe wallets on Ethereum lost assets in a single transaction on October 1. Defimon Alerts flagged it at 15:08:57 UTC, ten seconds after the block, and noted that both Safes share a single owner. The larger Safe, 0xcfed...169f, held a looped Aave v3 position: weETH supplied as collateral, WETH borrowed against it. The smaller one, 0xe3b2...8520, held loose weETH.

The trace shows four movements:

  • About 1,335 WETH of the larger Safe's Aave debt was repaid.
  • 1,306.48 weETH of collateral was withdrawn from it.
  • 6.43 weETH was transferred out of the smaller Safe.
  • The attacker unwrapped and kept about 114.1 ETH.

CryptoSlate and Cointelegraph carry SlowMist's 114.09 ETH figure. Crypto Briefing put losses at $305,000 to $310,000; the spread is the ETH price used.


What Is a Safe Module, and Why Does Enabling One Matter?

A Safe module is a contract the Safe's owners have approved to execute transactions on the Safe's behalf without collecting owner signatures. Once enabled, it calls execTransactionFromModule and the Safe carries out whatever it is told. Safe's own documentation puts it bluntly: modules "can execute arbitrary transactions," and "a malicious module can take over a Safe."

A module becomes a takeover path the moment it will forward someone else's instructions. In practice every module is another signer, and its access control is the Safe's access control.

FlashLoopAdapter was built for a narrow job. Its verified source describes it as a Safe module that opens and closes Aave v3 loops atomically. The Safe calls open() or close(); the adapter takes a flash loan, swaps through an aggregator router, supplies or withdraws on Aave, and drives the Safe through execTransactionFromModule for every step that touches the position. The design goal, in the contract's own words, is that "the POSITION IS OWNED BY THE SAFE." That part worked. The Safe owned the position right up until a stranger told the module to hand it over.


How Did the FlashLoopAdapter Exploit Work?

The adapter decided who its caller was by asking the caller. The entire gate:

function _start(Params calldata p) internal {
    if (!ISafe(msg.sender).isModuleEnabled(address(this))) revert ModuleNotEnabled();
    _safe = msg.sender;
    _pending = keccak256(abi.encode(msg.sender, p));
    ...
}

isModuleEnabled is a view function on the caller. A real Safe answers it honestly. Any other contract can implement the same signature and return true. The check proves the caller can say the right word; it proves nothing about whether the caller is a Safe, or which Safe. This is CWE-807, reliance on untrusted inputs in a security decision, in four lines.

The contract also declares error NotSafeOwner(); and never uses it. It reads like an owner check that was planned and never wired in.

The second half was the swap leg. Every Params field is caller-supplied, including the flash lender (providerAddr), the router (swapRouter) and the router's calldata (swapCalldata). The swap helper does this with them:

IERC20(tokenIn).approve(router, type(uint256).max);
(bool ok, bytes memory ret) = router.call(data);

The adapter's flash-callback guard checks that the callback comes from providerAddr and that the parameters hash matches what _start committed to. Both hold trivially when the attacker's own contract is the provider. In the trace, the adapter called its "flash lender" and reached the attacker.

So the attacker set swapRouter to a victim Safe and swapCalldata to execTransactionFromModule(...). The adapter made that call itself. The victim Safe saw msg.sender == FlashLoopAdapter, an enabled module, and executed.

The Attack, Step by Step

#ActionDetail (from the token-transfer trace)
1Flash loanAttacker contract 0xF091...67ff borrows 11,537.24 WETH from Morpho, roughly nine times what it will need; the excess goes straight back
2Repay the victim's debt1,335.26 WETH repaid to Aave on behalf of the larger Safe, freeing its collateral
3Pass the gateAttacker contract calls the adapter; asked isModuleEnabled, it answers true
4Fake the flash legThe adapter's flash loan goes to providerAddr, the attacker contract, which calls back and passes _validate
5Drive the SafesThe adapter's raw router call targets each victim Safe with execTransactionFromModule
6CollectLarger Safe withdraws 1,306.48 weETH from Aave to the attacker; smaller Safe transfers 6.43 weETH
7Convert1,312.90 weETH swapped across Curve, Fluid, Uniswap and Velora's Augustus router for 1,449.35 WETH
8Settle and keep11,537.24 WETH back to Morpho; 114.096 WETH unwrapped and sent to the attacker EOA

Steps 3 to 5 are where one contract plays three roles:

rendering diagram…

The attacker's preparation was short. Per the attacker EOA's internal transactions, it received 0.049875 ETH of gas money from Railgun's Relay Adapt contract at 14:15 UTC. Its first contract deployment followed at 14:44 UTC, a contract Etherscan now tags "Aave V3 Loop Exploiter 2", and the drain fired at 15:08:47. Under an hour from funding to drain, and no failed attempt against the Safes precedes it on-chain, so whatever testing happened was done off-chain.


Why Did a $3.88M Withdrawal Become a $305K Loss?

Both numbers are correct. They measure different things. The Crypto Times noted that Etherscan prices the larger Safe's withdrawal at about $3.88 million gross, and that the loss is far smaller.

The larger Safe was a deep loop. In token count it owed more WETH (about 1,335) than it held weETH (1,306.48); weETH trades a little above ETH, and that thin margin was the owner's equity. Aave will not release collateral that still backs a loan, so the attacker had to repay the full debt before the weETH could leave, and the prize was the margin.

The trace reconciles to four decimal places. The attacker's swaps turned 1,312.90 weETH into 1,449.3520 WETH. Subtract the 1,335.2558 WETH it spent repaying Aave and you get 114.0962 WETH, the amount it unwrapped and sent to its own wallet, which rounds to SlowMist's figure. We trust 114.09 ETH as the attacker's take. The owner's economic loss is probably a little higher, since the attacker's swaps into WETH paid fees and slippage.


Who Else Used the Hole After the Drain?

The module stayed enabled after the first transaction, and the bug stayed open with it. Two more addresses used it before the day was out. None of the reporting we found mentions the approvals, and no outlet names the address that disabled the module.

At 15:49 UTC, 41 minutes after the drain, a transaction from 0xcccc...3447 drove both Safes through the adapter. Its decoded logs show a failed module execution on each Safe, then a successful one in which each Safe approved 0xcccc...3447 for the maximum uint256 amount of Aave v3 wstETH aTokens. No tokens moved. Our own contract reads on October 8 found both allowances still at that maximum and no aEthwstETH in either Safe. If either Safe ever supplies wstETH to Aave again, that address can pull the aTokens.

At 16:32 UTC, a transaction from labubufam.eth (0x3a06...6B3D) used the same bug to make each Safe disable FlashLoopAdapter. Its logs show DisabledModule emitted by both Safes, each followed by ExecutionFromModuleSuccess. Crypto Briefing reported that the Safes disabled the module. The disabling transaction came from an address other than the owner, through the vulnerable module itself. That was 84 minutes after the drain.

The attacker's exit was quick. At 16:45 UTC the EOA moved 114.13 ETH to a new address, and one minute later that address sent 114.03 ETH into Railgun through the same Relay Adapt contract that had funded it. One syndicated summary on KuCoin's news feed says the funds were split and "funneled into Tornado Cash"; the chain shows a single hop to a new address and one Railgun deposit, and we trust the chain.

At 21:03 UTC the owner sent the attacker an on-chain message: "We are offering a 10% whitehat bounty. Keep 11.41 ETH and return 102.69 ETH," with a deadline of October 3, 18:00 UTC. By then the funds had been inside Railgun for more than four hours. We found no return transaction to the owner's address.


Who Built FlashLoopAdapter?

Nobody has said. Kulechov described it as a "third party external adapter built on top of Aave."

The chain offers some context, which we label as inference. The adapter was deployed on July 16, and both Safes enabled it the next day: the smaller Safe at 08:12 UTC and the larger one at 12:48 UTC on July 17. Both were sent by 0x329c...3eD4, the same address that later wrote to the attacker as "the owners of the two Safes", and that address sent 0.05 ETH to the adapter's deployer in August. The verified source points its readers to a local file path, ~/goals/aave-yield-hunter/engine/CONTRACTS.md. We found no other Safe in the adapter's transaction history. It looks like one operator's private yield tooling, written for their own two Safes, rather than a product anyone else installed.

The owner address is not anonymous. On Etherscan it resolves to the ENS name aavechan.eth and carries the public name tag "AAVE: Deployer 13". None of the incident coverage we read names the owner, and nobody has publicly claimed the Safes. Both are labels: Etherscan assigns its name tags, and an ENS primary name is set by whoever controls the address. We report them as they appear and have not confirmed who holds the key. Kulechov's "third party" remark is about the code, and it stands either way: FlashLoopAdapter is not part of Aave v3, whoever owned the Safes it served.


Why Does This Keep Happening to Safe Modules?

Because the module is where the Safe's multisig stops applying, and modules get written like integrations instead of like signers.

We have written up two Safe-module drains before this one. In May, SquidRouterModule lost about $3 million across 86 wallets through a delegated module approval. In September, the rsETH Safe drain went through a router that authorized any call pointed at itself. FlashLoopAdapter's version is simpler than either. The same family also covers Limit Break's forged ERC-2771 sender, where a contract also trusted an identity the caller supplied.

"Zero effect on Aave v3" is true. The position lived in Aave; the authority over it lived in a roughly 200-line contract the owner enabled on their Safes. An audit of Aave or Safe does not cover a module you bolt onto them.

The two fixes are small. Bind the caller to a Safe you registered at install time (or require that the Safe's own owners signed the call), and never let caller-supplied calldata reach an address that trusts the adapter. A router allowlist that excludes every enabled Safe would have killed step 5 even with the gate spoofed.


What Should Operators Do Now?

  1. List every module enabled on every Safe you run. Call getModulesPaginated and read the answer. A module is a signer. If you cannot name who wrote it and who reviewed it, disable it.

  2. Never authenticate a caller by asking the caller. isModuleEnabled, owner(), supportsInterface and their relatives prove nothing when the caller implements them. Store the authorized Safe addresses at setup, or verify through a contract you already trust.

  3. Treat caller-supplied call targets as an exploit primitive. If your module makes target.call(data) with both values from the caller, it will call your own Safes, your own token approvals and your own admin functions on request. Allowlist routers, and refuse any target that is an enabled Safe.

  4. Assume the hole stays open after the first drain. Here a second address used the module 41 minutes later and a third 84 minutes later. The first response to a module exploit is disableModule on every Safe that enabled it, before the postmortem.

  5. Revoke what the follow-on actors left behind. Both Safes still carry the unlimited aEthwstETH approvals from 15:49. Planted approvals outlive the incident unless someone reads the allowance table.

The drain was atomic, so nothing alerts ahead of it. The bug, though, sat in public, verified source of about 200 lines, and the step-5 fix is a router allowlist. That is a review finding, the kind a smart contract audit exists to catch before a module goes live on a funded Safe. After the first block, everything was alertable at 15:08: an unrecognized contract calling open(), ExecutionFromModuleSuccess on both Safes from that caller, and 1,306.48 weETH of collateral leaving Aave in one transaction, with the module live for another 84 minutes and a second actor on the way. Tripwire checks rules like these on every block and pending transaction and alerts over Telegram, Slack or a webhook the moment one breaks, so the owner hears about a stranger's module execution in the same minute and switches the module off before anyone comes back for a second pass.


Frequently Asked Questions

Was Aave v3 hacked in the FlashLoopAdapter exploit?

No. Aave founder Stani Kulechov said the contract was a third-party adapter with "zero effect on Aave v3." The exploited contract was a custom Safe module that managed looped Aave positions; Aave's pools behaved as designed, including accepting a debt repayment on the victim's behalf.

How much was stolen in the FlashLoopAdapter exploit?

About 114.09 ETH, roughly $305,000. The larger Safe's withdrawal is worth about $3.88 million gross, but the attacker had to repay about 1,335 WETH of the Safe's debt first, so the take was the position's equity. The swap and repayment figures in the trace reconcile to 114.0962 ETH.

Could the Safe owners have stopped the attack?

No, not the first transaction. The drain was atomic: flash loan, repayment, withdrawal and swap settled in one transaction, with no block in which to react. The follow-on use of the module was stoppable; it stayed enabled for 84 minutes, and a second address planted approvals during that window.

Did the attacker return the funds?

No, not as far as the chain shows. The owner offered a 10% bounty on-chain with an October 3 deadline, about four hours after the attacker had already moved 114.03 ETH into Railgun. We found no return transaction to the owner's address.


Sources / References

Aron Turner
Aron Turner

Co-Founder & CTO

CTO of SigIntZero. Engineering leadership, infrastructure architecture, and security tooling.