A Safe lost $7.8 million to its own module
A Gnosis Safe lost 7.8 million dollars in restaked ETH because an enabled module let a passing keeper point it at an attacker's own pool. The Safe itself worked. This kind of bug is old, and it comes down to who gets to choose the target.
In short: if you keep money in a Safe, a shared wallet that needs more than one key to sign, and turn on extra modules for it, this is the case to know. On 15 September 2026 a Safe lost about $7.8 million in restaked ETH, and the attacker never had a key. A module the owner had turned on let anyone who called it pick which pool to send the funds through, and the attacker picked a pool they had just made. The Safe worked as built; the mistake was letting a stranger choose where the money goes.
On 15 September 2026, an Ethereum user lost about $7.8 million in restaked ETH out of a multi-signature wallet. There is a memorable twist (a front-running bot took the money before the attacker could), but the more useful part is the four seconds before it, when the wallet did exactly what it was told to do, by someone who should not have been able to tell it anything.
It is worth walking through, because it is one of the most common ways a smart account gets emptied, and the flawed part always looks correct.
What actually happened
The victim held roughly 2,900 rsETH (a liquid restaking token) inside a Gnosis Safe, in its Aave-wrapped form. The attacker never had a key.
Instead, they used a public keeper call to point a custom Uniswap V4 liquidity module, one the account had enabled, at a pool the attacker had just created. That pool carried a malicious hook. The hook unwrapped the position into freely transferable rsETH, and the module, acting with the Safe’s authority, moved it.
The bug is where the target came from
Without the restaking and the hooks, the bug is simple. A trusted module was handed a destination (which pool, which contract) by its caller, and it acted on that destination from inside its own privileged context. The validation it did was not enough, because the attacker got to choose the one thing that decided where the money went.
The pool to route through. A contract address, chosen at call time, by a public keeper anyone can invoke.
Nothing that decides a destination. The venue is named from a list the owner installed; the recipient is the account, always; the module composes the call.
A module that takes its target from the caller trusts the caller to aim it, and on a public keeper path the caller can be anyone. The attacker did not need to break in, because the module let them steer.
The twist, since it is a good one
The exploit transaction went into the public mempool, the waiting room where unconfirmed transactions sit in the open. A generalised front-running bot named Yoink saw the pending drain, copied it with a higher fee, and executed first in the same block. The original attacker got nothing.
Can this happen to a DeFiLoops account?
No. The specific move, aiming a trusted module at an attacker’s contract, cannot be expressed against a DeFiLoops account, and that is by design rather than by luck. It does not depend on more careful checks. The agent is never handed a target to aim.
- Choose the pool / contract to route through
- No such field. The agent submits typed parameters; the adapter composes the call and picks the target.
- Name a venue by address
- Refused. Venues are named from the set the owner installed, never as an address in the instruction.
- Redirect the payout to yourself
- The recipient is forced to the account and read from the account, never taken from the instruction.
- Prove you meant this account
- The owner is read from the account. A valid agent signature is not proof the right account, or destination, was meant.
There is a broader reason too: a production DeFiLoops account gives the agent no raw-call path at all. The drained Safe had a module that could execute a caller-chosen call. Our agent cannot compose a call. It submits an amount, a venue named from a list and a range, and a whitelisted adapter builds the transaction. No contract address ever comes from the agent.
The attacker’s plan, step by step
Assume the strongest realistic attacker short of stealing your owner key: they hold the agent key, the one that signs the account’s day-to-day instructions. Here is what they would try, and where each step ends.
- Point a module at a pool you control
There is no module that takes a pool from the caller. Trading runs through adapters that read the venue from the owner’s installed set. An attacker-created pool is not in it, and an address in the instruction is refused, because it would let the caller pick any contract.
- Fall back to a raw call and choose your own target
In production there is no raw-call path for the agent to fall back to. It is not installed, so this step cannot even be written.
- Route through a legitimate adapter and redirect the funds
The recipient is forced to the account, the owner is read from the account, the venue comes from the owner’s set, and the spend is capped per period. The adapter fixes every destination; the instruction sets none of them.
- Install a module of your own, or change the account's root
Those selectors are refused. The agent can never install the kind of redirectable module that drained the Safe, become the owner, or hand the account away.
- Meanwhile, the owner revokes the agent
One transaction, needing nobody’s cooperation, and every future instruction from that key is refused before its target is even inspected, so the stolen key stops working the moment the owner acts.
A stolen agent key, against a production account, cannot aim anything at an attacker and cannot take control. That is the entire security claim, and it holds against this exact attack.
What we do not claim
A security post that stopped there would be selling something. There are three limits, and none of them is this attack.
The part that changed our own confidence
We set out to prove the guard blocks this. Writing that proof turned up that the tests we already had did not prove it. They reverted a layer too early, in account plumbing, and passed because the test framework matched the wrong revert loosely. The protection was real, but the evidence for it was theatre.
A security test that cannot fail when you break the thing it guards is decoration, and it is more dangerous than no test, because it reads as coverage.
So we rebuilt the proof against the guard directly, asserting the exact reason each call is refused, then broke each control on purpose to watch the matching test fail. Removing the self-reference line flips its test. Neutering the revoke path flips another. Now we know the boundary holds instead of assuming it.
How to evaluate any claim like this
You do not need to trust this page. To tell a real boundary from a filter, ask one question: what can the agent, or a passing keeper, write down?
If the answer includes a destination (a pool, a contract, an address the caller gets to choose), then every safeguard around it is a check standing between a stranger’s chosen target and your money, and checks are judged against the attacks somebody already imagined. The September drain is what one caller-chosen target costs.
If the answer is typed parameters and nothing else, the attack in this post has nowhere to land. It needs a target the caller gets to name, and the agent is never given one.
Sources: PeckShieldAlert and Blockaid monitoring reports (15 September 2026), as covered by PANews, Odaily, Foresight News and BlockBeats; on-chain transactions via Etherscan. Figures and mechanism are as reported at the time and may be revised as analysis continues.