DEV Community

L_X_1
L_X_1

Posted on • Originally published at policylayer.com

ERC-20 Approval Attacks: Why AI Agents Are the Perfect Target

In 2024, ERC-20 approval exploits drained over $100 million from DeFi protocols. The attack vector isn't new—it's been known since 2018. But AI agents have turned a manageable risk into a catastrophic one.

This post explains how ERC-20 approval attacks work, why AI agents amplify the damage, and how to prevent them at the intent level.

How ERC-20 Approvals Work

Before an ERC-20 token can be transferred by a third party (like a DEX or lending protocol), the token owner must approve a spending allowance.

// Standard ERC-20 approve function
function approve(address spender, uint256 amount) external returns (bool);

// Example: Approve Uniswap to spend 100 USDC
usdc.approve(UNISWAP_ROUTER, 100_000_000); // 100 USDC (6 decimals)
Enter fullscreen mode Exit fullscreen mode

This creates an allowance that Uniswap can spend on your behalf. The problem? Many dApps request unlimited approval for convenience:

// The dangerous pattern
usdc.approve(UNISWAP_ROUTER, type(uint256).max);
// ↳ Uniswap can now spend ALL your USDC, forever
Enter fullscreen mode Exit fullscreen mode

The Attack Vector

Once unlimited approval is granted, the approved contract can drain the entire token balance at any time. If that contract is compromised, upgraded maliciously, or has a vulnerability—your funds are gone.

Attack flow:

1. User grants unlimited approval to DApp
2. DApp contract is compromised/upgraded
3. Attacker calls transferFrom() for full balance
4. All approved tokens drained instantly
Enter fullscreen mode Exit fullscreen mode

This isn't theoretical. Major exploits:

  • BadgerDAO (2021): $120M drained via malicious approvals
  • Transit Swap (2022): $21M stolen through approval exploit
  • Multichain (2023): $125M lost due to compromised approval

Why AI Agents Amplify the Risk

Human traders might approve a contract once. AI agents approve contracts continuously, at scale.

Consider an AI trading agent:

// Agent executes 100 trades per day
for (const trade of trades) {
  // Each trade might require approval
  await token.approve(dex, trade.amount);
  await dex.swap(trade);
}
Enter fullscreen mode Exit fullscreen mode

Problems:

  1. Scale: 100 approvals per day vs 1 approval from a human
  2. Automation: No human reviewing each approval
  3. Variety: Agent might approve dozens of different contracts
  4. Persistence: Agent runs 24/7, constantly creating approvals

An AI agent with wallet access becomes an approval-generating machine. Each approval is a potential future attack vector.

The Infinite Approval Trap

The worst pattern: agents that approve unlimited amounts for "efficiency."

// DANGEROUS: Common pattern in AI agent code
async function ensureApproval(token: Contract, spender: string) {
  const allowance = await token.allowance(wallet.address, spender);
  if (allowance.lt(VERY_LARGE_NUMBER)) {
    // "Just approve max to avoid future transactions"
    await token.approve(spender, ethers.constants.MaxUint256);
  }
}
Enter fullscreen mode Exit fullscreen mode

This pattern exists because:

  • Approval transactions cost gas
  • Developers want to minimize transactions
  • "Approve once, trade forever" seems efficient

But it means every contract your agent has ever interacted with can drain your entire balance of that token.

Intent-Level Prevention

The solution isn't better approval hygiene at the contract level—it's preventing dangerous intents from executing at all.

PolicyLayer blocks risky approvals before they happen:

import { PolicyWallet, createEthersAdapter } from '@policylayer/sdk';

const wallet = new PolicyWallet(adapter, {
  apiKey: process.env.POLICYLAYER_API_KEY,
  metadata: { agentId: 'trading-bot' }
});

// Policy configuration
const policy = {
  // Only approve whitelisted contracts
  approvalWhitelist: [
    '0x...uniswap-router',
    '0x...aave-pool'
  ],

  // Cap approval amounts (no infinite approvals)
  maxApprovalAmount: '1000000000000', // $1M max approval

  // Per-transaction limit (applies to approvals too)
  perTransactionLimit: '100000000000'  // $100k max
};
Enter fullscreen mode Exit fullscreen mode

With this policy:

  • Agent can only approve whitelisted contracts
  • No approval can exceed $1M
  • Each approval is logged and auditable

Implementation: Safe Approval Pattern

Here's how to implement approval controls in your agent:

class SafeApprovalTool {
  private wallet: PolicyWallet;
  private allowedSpenders: Set<string>;

  constructor(wallet: PolicyWallet, allowedSpenders: string[]) {
    this.wallet = wallet;
    this.allowedSpenders = new Set(allowedSpenders.map(s => s.toLowerCase()));
  }

  async approve(
    token: string,
    spender: string,
    amount: string
  ): Promise<{ success: boolean; reason?: string }> {

    // Check 1: Is spender whitelisted?
    if (!this.allowedSpenders.has(spender.toLowerCase())) {
      return {
        success: false,
        reason: `Spender ${spender} not in whitelist`
      };
    }

    // Check 2: Is amount reasonable? (not infinite)
    if (BigInt(amount) === BigInt(2) ** BigInt(256) - BigInt(1)) {
      return {
        success: false,
        reason: 'Infinite approvals not allowed'
      };
    }

    // PolicyLayer enforces limits on the approval intent
    try {
      const result = await this.wallet.send({
        chain: 'ethereum',
        asset: token,
        to: spender,
        amount: amount,
        type: 'approval'  // Special handling for approvals
      });

      return { success: true };
    } catch (error) {
      return {
        success: false,
        reason: error.message
      };
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Approval Hygiene Checklist

For AI agents handling tokens:

Policy-Level Controls:

  • [ ] Whitelist of approved spender contracts
  • [ ] Maximum approval amount (never infinite)
  • [ ] Approval logging and alerting
  • [ ] Periodic approval audit

Agent-Level Controls:

  • [ ] Check existing allowance before approving
  • [ ] Approve exact amount needed, not more
  • [ ] Revoke approvals after use when possible
  • [ ] Alert on unexpected approval requests

Monitoring:

  • [ ] Track all active approvals per token
  • [ ] Alert on approval to unknown contracts
  • [ ] Regular approval cleanup job

Revoking Dangerous Approvals

If your agent has already granted risky approvals, revoke them:

// Revoke approval by setting allowance to 0
await token.approve(riskyContract, 0);

// Or use a batch revocation tool
const approvals = await scanForApprovals(wallet.address);
for (const approval of approvals) {
  if (!isWhitelisted(approval.spender)) {
    await token.approve(approval.spender, 0);
    console.log(`Revoked approval for ${approval.spender}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Tools like Revoke.cash can help identify and revoke dangerous approvals.

The Two-Gate Difference

PolicyLayer's two-gate model catches approval attacks at both stages:

Gate 1 (Intent Validation):

  • Is this spender on the whitelist?
  • Is the approval amount within policy limits?
  • Has this agent exceeded its daily approval budget?

Gate 2 (Execution Verification):

  • Does the executed approval match the approved intent?
  • Has the intent been tampered with?
  • Is this a replay of a previous approval?

Even if an attacker manipulates the agent's reasoning to request a dangerous approval, the policy layer blocks it.


Related Reading


Ready to secure your agent's token approvals?

Top comments (0)