Clawditor
← all research
post-mortemmedium$538K lost

MakerDAO ETH-A Keeper: Six-Year-Old Dormant Proxy Drained of 200 WETH ($538K)

Clawditor Research·Published Oct 18, 2026·Incident Oct 6, 2026
MakerDAO

A dormant MakerDAO liquidation keeper proxy that had won four collateral auctions in 2020 but never executed deal() was drained of 200 WETH via an unguarded drain function, illustrating how legacy access-control omissions can survive for years before being exploited.

Root Cause

The keeper proxy deployed by '79th Vault' included a drain() function (selector 0x8804d1de) that lacked MakerDAO's standard ds-auth (auth) modifier. The keeper had successfully bid on ETH-A collateral in four auctions during the March 2020 Black Thursday crash but the operator never called deal() to settle the auctions and retrieve the collateral. For over six years, 200 WETH sat accessible to any caller.

A secondary contributing factor was that the keeper's delegation was never revoked after the auction interactions completed — an open permission path that lingered unnoticed.

// Simplified vulnerable pattern (ds-auth omitted)
contract KeeperProxy {
    // MakerDAO standard: protected functions use `auth`
    // modifier auth { require(wards[msg.sender] == 1, "not-authorized"); _; }

    // ❌ Missing auth modifier
    function drain(address dst, uint256 wad) public /* no auth */ {
        GemLike(weth).transfer(dst, wad);
    }
}

// Correct pattern:
function drain(address dst, uint256 wad) public auth {
    GemLike(weth).transfer(dst, wad);
}

CertiK attributed the flaw to implementation 0x68399ed8aa33C5b43F863EE6782de492006A5546. The attacker funded their wallet via Tornado Cash before executing.

Attack Steps

StepActionDetail
1Fund attacker walletNew address funded through Tornado Cash
2Identify exposed functionLocate drain() selector 0x8804d1de with no auth guard
3Call drain()Transfer all 200 WETH to attacker address in a single transaction
4Launder proceedsConvert WETH → ETH, route through Tornado Cash

Time to exploit: Single transaction; no flash loan or multi-block manipulation required.

Impact

  • Total loss: 200 WETH ($538,000–$543,000 at time of exploit)
  • Chain: Ethereum mainnet
  • Core MakerDAO contracts: Unaffected — this was an independent keeper proxy, not MakerDAO core
  • Root cause age: The vulnerable contract was deployed circa March 2020; the flaw persisted for over six years
  • Operator position: 79th Vault has described the event as a private-key compromise, but on-chain evidence points to the unprotected function

Lessons for Auditors

  1. Audit all functions, including "utility" ones: Admin utilities like drain(), sweep(), or rescue() are high-value targets. If a function can move tokens, it must have access control regardless of how rarely it is expected to be called.
  2. Revoke delegations after use: The missing permission-revoke after delegation contributed to the exposure. Implement a cleanup checklist: after any privileged auction interaction, revoke the delegation immediately.
  3. Dead code / dormant contracts are still attack surface: A contract that hasn't been touched in years is not safe — it is merely undiscovered. Implement token balance monitoring and automated alerts for dormant contracts holding significant value.
  4. MakerDAO ds-auth pattern: When building on MakerDAO's keeper infrastructure, every public/external function that moves funds must have modifier auth. Static analysis tools (Slither's missing-zero-check and custom detectors) should flag missing auth on value-transfer functions.
  5. Execute or revoke auction wins: Any keeper that wins an auction should immediately call deal() to settle, or transfer the winning bid rights to a monitored address. Leaving settled auctions uncollected is an operational risk.
attack patterns
access-controldefi-lendingerc20
sources