October 16, 2026
|
Exploit Postmortem
MakerDAO Keeper Exploit

MakerDAO Keeper's $543K Loss and How Olympix Would Have Prevented It

‍Date: October 6, 2026

Chain: Ethereum

Loss: 200 ETH, about $543K at the time of the exploit

Class: missing access control on a function that hands out Vat permissions

In March 2020, during MakerDAO's Black Thursday liquidations, an automated bidder won four lots of ETH collateral for nothing. It bid zero DAI on each 50 ETH lot, won all four, and then never came back to collect them. For six and a half years, 200 ETH sat inside a MakerDAO auction contract, credited to that keeper.

On October 6, 2026, someone else collected it. A wallet funded with 0.1 ETH from Tornado Cash deployed an attack contract at 06:12:11 UTC, and one minute later a single transaction settled the four 2020 auctions, pulled the collateral out of MakerDAO, and delivered 200 ETH, worth about $543,000, to the attacker. MakerDAO's core contracts did exactly what they were built to do the whole time. The keeper's own code handed the money over, through one function that skipped the permission check every other sensitive function in the contract enforced.

Zero Bids on Black Thursday

On March 12, 2020, the price of ETH fell roughly 50% within 24 hours, and MakerDAO's liquidation system was flooded with undercollateralized vaults. MakerDAO sold the seized collateral through auction contracts called Flippers, and third-party bots called keepers did the bidding. Network congestion combined with low DAI liquidity meant some keepers won large lots of ETH by bidding zero. CoinDesk later reported that about $8M in collateral left the system this way.

A keeper that wins a Flipper auction still has to call deal() to settle it. Settling moves the collateral into the winner's account in the Vat, MakerDAO's core accounting contract, and from there it can be withdrawn through a GemJoin adapter. This keeper, an upgradeable proxy at 0x9c05a05893Ada984FC20D0DA0c046De5Cc0e8273, won auctions 1457 through 1460 from the ETH-A Flipper and never called deal() on any of them. MakerDAO has since rebranded as Sky and retired the Flipper, but the contract kept its records, and the 200 ETH stayed exactly where the auctions left it.

Every Function Guarded Except One

Anyone can call deal() on a Flipper auction, so settling the four lots was never the obstacle. Settling them only credits the collateral to the keeper's own Vat account, and moving anything out of that account takes the keeper's permission. In the Vat, that permission is called hope. When an account calls hope on another address, it gives that address full control over its collateral and internal DAI.

According to CertiK's analysis, every privileged function in the keeper's implementation, including its own wrappers for hope, flux, and move, runs a ds-auth check first and reverts for any caller who isn't authorized. ds-auth is the same access-control library MakerDAO's own contracts use, and the keeper applied it consistently, with one exception.

Function 0x8804d1de accepts any address as an adapter. It asks the adapter for a couple of details, checks with the Vat whether the keeper has already delegated to it, calls Vat.hope on it if it hasn't, and then calls the adapter's join() function. At no point does it check who is calling. The researcher who decompiled the unverified implementation for the DeFiHackLabs proof of concept found no owner lookup or caller comparison anywhere before the function's body runs, and the function never calls nope to take the permission back afterward.

That made the one function in the contract that gives the keeper's Vat permissions to an outside address also the one function anyone could call, and it finished by handing execution to code the caller wrote while that permission was live. The implementation's source was never verified on Etherscan, so its code couldn't be read there, and the flaw sat in the live implementation next to 200 ETH that needed exactly this permission to move.

Sixty Seconds From Deployment to Drain

  1. At 05:57:23 UTC, a new wallet received 0.1 ETH from Tornado Cash, its only funding.
  2. At 06:12:11 UTC, the attacker deployed an attack contract.
  3. At 06:13:11 UTC, the exploit transaction ran. Inside it, the attack contract created an adapter module preloaded with the addresses of the ETH-A Flipper, the Vat, the ETH-A GemJoin and WETH, a payout address, and the four auction IDs.
  4. The attack contract called the keeper's function 0x8804d1de with the module's address. The keeper called Vat.hope on the module, giving it control of the keeper's Vat account, and then called the module's join().
  5. Inside that callback, the module called deal() on auctions 1457, 1458, 1459, and 1460, which credited 200 ETH of collateral to the keeper's Vat account. It then used its new permission to flux all 200 ETH to itself and exited it through GemJoin as 200 WETH.
  6. The module forwarded the WETH to the attack contract, which unwrapped it and sent 200 ETH to the attacker's wallet, all within the same transaction.
  7. At 06:19:35 UTC, the first 10 ETH deposit reached the Tornado Cash router, and the rest followed in 10 ETH lots.

Every MakerDAO contract in that sequence behaved correctly. The Flipper settled auctions for their rightful winner, the Vat honored a hope call that really did come from the keeper, and GemJoin released collateral that was really in the account. The attacker never needed the keeper's authorization, because function 0x8804d1de did the authorizing on the attacker's behalf.

What the Keeper Lost, and What It Still Owes

The attacker left with 200 ETH, about $542,982 at the time of the exploit according to CertiK, and started moving it into Tornado Cash six minutes later. The collateral came out of the keeper's own Vat account, and CertiK identifies the keeper's owner as the same address that operated it in March 2020.

The permission is also still out there. Function 0x8804d1de never revokes the delegation it grants, so CertiK lists the attacker's module as still holding control over the keeper's Vat account after the transaction ended. The delegation outlived the attack the same way the collateral outlived Black Thursday.

How a Guarded Contract Hid an Unguarded Function

In the keeper's implementation, the wrappers for the Vat's hope, flux, and move functions all carry ds-auth checks. Function 0x8804d1de registers an adapter and calls it, and its effect on permissions comes from a call it makes into another contract, where hope transfers control over everything the keeper holds in the Vat.

The risk shows up when you follow the call into the Vat and work out what an arbitrary caller can do with the permission it grants. Audits remain a necessary part of shipping contracts like this one, and a time-boxed review is strongest on the functions it's pointed at. Analysis that walks every path to a privileged state, including the paths that look routine, is what catches the one function that doesn't match the rest.

How BugPoCer Finds the Unguarded Path

BugPoCer, Olympix's tool for Counterexample-Guided Invariant Refutation, works from the system outward. It infers the invariants a contract is supposed to hold, executes the code until one of them breaks, and hands back the transaction that breaks it.

This keeper spells out its own invariant. Every function that changes who controls the keeper's Vat account is gated by ds-auth, so the rule the code implies is that only an authorized caller can grant or use that control. BugPoCer tests that rule against every path into the Vat's permission system, and function 0x8804d1de is the path that fails it, since a call from an arbitrary address reaches Vat.hope with no auth check in between.

From there, BugPoCer builds the counterexample by executing the path end to end. A fresh address passes in an adapter it controls, the keeper grants the adapter its Vat permissions and calls back into it, and the adapter settles the four open auctions, moves the 200 ETH of collateral to itself, and exits it through GemJoin. That's the October 6 attack, produced as a working proof of concept before the contract ever reaches mainnet.

Olympix has already shown this class on a real exploit. When the ycdeal3 RWAVault lost $387K in April 2026 to a withdraw function that never checked who was calling, BugPoCer flagged both the withdraw and redeem paths as High-severity missing access control and produced a working proof of concept for each. The keeper's function carries the same defect in a different setting, a sensitive action anyone can reach because one function skipped the check its neighbors enforce.

Six and a Half Years Later

The 200 ETH in this keeper survived Black Thursday, a protocol rebrand, and the retirement of the auction contract holding it. It couldn't survive one function, written alongside properly guarded ones, that gave the keeper's Vat permissions to whoever asked. The rule that function broke was already written into the rest of the contract, which made it a rule a tool could infer and test before deployment. You can see Olympix infer the invariants in your own contracts and prove the violations with a working proof of concept in a free demo.

On-Chain References

Protocol: Third-party MakerDAO ETH-A liquidation keeper

Chain: Ethereum

Date: October 6, 2026, 06:13:11 UTC

Loss: 200 ETH (about $542,982 at the time, per CertiK)

Attacker EOA: 0x01EB...A5FA

Attack contract: 0xEc99...1dcF

Adapter module (still holds Vat delegation): 0xf09a...C170

Keeper proxy: 0x9c05...8273

Keeper implementation (source unverified): 0x6839...5546

Keeper owner: 0xaDC3...F073

ETH-A Flipper: 0xd8a0...5531

Vat: 0x35D1...492B

ETH-A GemJoin: 0x2F0b...042A

Funding tx: 0xd4c0...8ac6

Attack contract deployment tx: 0x4a58...1167

Exploit tx: 0xbb69...a88c

First Tornado Cash deposit: 0xb8ec...efeb

An unguarded function in an otherwise guarded contract is the kind of defect BugPoCer is built to find. See it run against your own contracts, with a proof of concept for every finding that carries real loss. Book a free demo.

Sources

  1. CertiK: MakerDAO Legacy Auction Keeper Incident Analysis
  2. DeFiHackLabs PR #1295: PoC, MakerDAO flip-keeper unprotected drain selector
  3. CoinTelegraph: MakerDAO Takes New Measures to Prevent Another Black Swan Collapse
  4. CoinDesk: Mempool Manipulation Enabled Theft of $8M in MakerDAO Collateral on Black Thursday
  5. CertiKAlert on X: original alert

More from Olympix:

No items found.

Ready to Shift Security Assurance In-House? Talk to Our Security Experts Today.