BadgerDAO Hack: $120M Stolen Through the Front-End, Not Solidity

BadgerDAO lost $120M in December 2021 without a single line of Solidity being exploited. The attackers compromised Cloudflare, injected JavaScript into the front-end, and tricked users into signing infinite token approvals. Your contracts can be flawless and still drain.

The BadgerDAO hack ($120M, December 2021) was not a smart contract exploit. Attackers obtained a Cloudflare API key, injected malicious JavaScript into app.badger.finance, and prompted users to sign ERC-20 approvals granting an attacker-controlled address unlimited spending allowance over their vault tokens. The Solidity code was untouched. The lesson: token approval phishing turns front-end and infrastructure compromises into on-chain theft, and mitigation requires both off-chain hardening (CSP, SRI, hosting controls) and on-chain defenses like bounded allowances and approval expirations.
··7 min read

The Bug: A Front-End You Don't Control

BadgerDAO's smart contracts were fine. Audited, deployed, working. On December 2, 2021, attackers still walked away with roughly $120 million in BTC and ERC-20 assets. They did it by owning the part of the stack most Solidity devs treat as someone else's problem: the front-end and its hosting.

The attack chain was almost boring:

1. Attackers obtained a Cloudflare API key tied to BadgerDAO's account (likely via a malicious Cloudflare Workers script created weeks earlier).
2. They selectively injected JavaScript into `app.badger.finance` — only for wallets holding meaningful balances, to avoid detection.
3. The injected script intercepted normal user interactions and replaced the transaction payload with an `approve()` call granting an attacker-controlled address unlimited allowance over the user's vault tokens.
4. Users clicked "Confirm" in MetaMask. The signature was real. The intent was not.
5. Hours or days later, the attacker called `transferFrom()` and emptied the wallets.

No reentrancy. No integer overflow. No oracle manipulation. Just `approve(attacker, type(uint256).max)` signed by the rightful owner, exactly as ERC-20 specifies.

What the Malicious Approval Looked Like

The vulnerable pattern is the standard one — and that's the problem. Here's what most dApps still prompt:

```solidity
// What the UI asked users to sign
IERC20(bToken).approve(
0xSPENDER, // swapped by injected JS
type(uint256).max // "infinite" approval, gas-efficient, fatal
);
```

From the contract's perspective this is a legitimate call from the token owner. The ERC-20 standard does not care who told the wallet to send it. The chain executes intent, not honesty.

On-Chain Mitigations

You cannot stop Cloudflare from being breached. You can make a stolen approval less catastrophic.

1. Use Permit2 or EIP-2612 with short deadlines

Instead of long-lived `approve()` calls, request signed permits scoped to a single transaction:

```solidity
function depositWithPermit(
uint256 amount,
uint256 deadline,
uint8 v, bytes32 r, bytes32 s
) external {
require(deadline >= block.timestamp, "expired");
IERC20Permit(token).permit(
msg.sender, address(this), amount, deadline, v, r, s
);
IERC20(token).transferFrom(msg.sender, address(this), amount);
}
```

A signature stolen via injected JS is still bounded: one amount, one spender, one deadline. No standing allowance to drain a week later.

2. Reject unbounded allowances at the protocol layer

If your vault is the spender, you can refuse to pull funds beyond a session-scoped cap:

```solidity
mapping(address => uint256) public sessionCap;
mapping(address => uint256) public sessionExpiry;

function setSession(uint256 cap, uint256 duration) external {
sessionCap[msg.sender] = cap;
sessionExpiry[msg.sender] = block.timestamp + duration;
}

function pull(address user, uint256 amount) internal {
require(block.timestamp <= sessionExpiry[user], "session expired");
require(amount <= sessionCap[user], "over cap");
sessionCap[user] -= amount;
IERC20(token).transferFrom(user, address(this), amount);
}
```

It won't stop a user from approving an *unrelated* address from a phished UI, but it ends the "infinite approval to my own vault" antipattern that made Badger users juicy targets in the first place.

3. Time-locked withdrawals on high-value vaults

For strategy contracts holding institutional balances, a withdrawal delay with a public mempool event gives users (and bots) a chance to revoke before funds leave. Badger had no such tripwire.

Off-Chain Hardening

This is the part Solidity devs hate hearing, but it's where Badger actually broke:

**Subresource Integrity (SRI)** on every script tag. Injected JS at the CDN edge fails the hash check.
- **Strict Content Security Policy** locking script origins.
- **IPFS-hosted front-ends** with on-chain content hash pinning (ENS contenthash). Cloudflare can't rewrite what it doesn't serve.
- **2FA + hardware keys on every infra account** — Cloudflare, DNS registrar, GitHub, AWS. Rotate API tokens, audit Workers scripts monthly.
- **Wallet simulation**: integrate Blockaid, Wallet Guard, or Tenderly simulations so users see "this will grant unlimited approval to 0xUNKNOWN" before signing.

Why Audits Missed It

They didn't miss it — it wasn't in scope. A traditional Solidity audit reviews bytecode behavior, not your Cloudflare dashboard. This is why a full-spectrum review matters: at Relymer we run a [free AI-powered audit](https://www.cryptohawking.com/audit) for fast contract-level coverage, but for production protocols holding real TVL you want a [manual audit](https://www.cryptohawking.com/audit/manual) that explicitly maps approval surfaces, front-end trust assumptions, and operational security — not just `slither` output.

Badger's post-mortem confirmed the entry vector was a rogue Cloudflare API token created via a compromised account on November 10, 2021 — three weeks of dwell time before the drain. Three weeks of token rotation hygiene would have killed the attack.

The Uncomfortable Takeaway

ERC-20 approvals are a delegation primitive with no expiration, no scope, and no UX. The standard assumes users understand what they sign. They don't. Until wallets enforce approval bounds by default, every dApp front-end is a phishing surface, and every Cloudflare account is a multi-sig signer you didn't know you had.

Revoke your old approvals. Audit your infra. Stop asking users for `uint256.max`.

FAQ

Was the BadgerDAO smart contract actually vulnerable?

No. The Solidity code was not exploited. Attackers compromised BadgerDAO's Cloudflare account and injected JavaScript into the production front-end that swapped legitimate transaction payloads with malicious ERC-20 `approve()` calls. Users signed those approvals themselves, granting an attacker-controlled address unlimited spending power over their vault tokens. The chain executed the signed intent exactly as specified. This is why we classify it as approval phishing via infrastructure compromise rather than a contract bug — and why audit scope alone wouldn't have caught it.

How do I protect users from infinite approval phishing in my dApp?

Stop requesting `type(uint256).max` approvals. Use EIP-2612 permits or Uniswap's Permit2 to scope approvals to a single transaction with a short deadline. If your protocol architecture needs persistent approvals, request exact amounts and re-prompt per deposit. On the infrastructure side, enable Subresource Integrity on script tags, enforce a strict Content Security Policy, host critical front-ends on IPFS with ENS contenthash pinning, and integrate wallet-level transaction simulation (Blockaid, Wallet Guard) so users see human-readable warnings before signing dangerous calls.

Can I detect a compromised front-end from inside my smart contract?

Not directly — the contract sees a signed transaction from a legitimate EOA and has no oracle into UI integrity. What you can do is constrain damage on-chain: session caps with expiration timestamps, withdrawal delays on high-value vaults that emit events early enough for users to revoke, allowance caps refused by the protocol layer, and rate limits on `transferFrom()` pulls. Combined with off-chain monitoring (Forta, custom subgraphs) that alerts on anomalous approval patterns from your protocol's users, you can shrink the window between phish and drain.

Should I revoke all my old token approvals?

Yes. Use revoke.cash, Etherscan's token approval checker, or your wallet's built-in tool, and remove any infinite allowance you don't actively need. Pay special attention to approvals granted to addresses you can't identify, deprecated protocols, and any dApp that suffered a front-end or infrastructure compromise. Approvals persist forever unless explicitly revoked — a contract you used once in 2021 still has spending rights today. Treat approval hygiene like password hygiene: rotate, scope, and audit periodically.

Does an audit cover front-end and infrastructure security?

A traditional smart contract audit does not. Audit scope is typically Solidity source and deployment configuration. Front-end code, CDN configuration, DNS, API key management, and CI/CD pipelines fall under operational security or web application pentesting. For production protocols, you want both — a manual contract audit covering approval surfaces and economic invariants, plus an OpSec review of your hosting stack. BadgerDAO had contract audits. They did not have a Cloudflare account audit, and that is precisely where they bled $120M.

One Solidity tip + 1 case study per month

BadgerDAO Hack: $120M Stolen Through the Front-End, Not Solidity | Crypto Hawking