DEV Community

ohmygod
ohmygod

Posted on

The Resolv Hack Autopsy: How a Compromised AWS Key Printed $25M in Unbacked Stablecoins

Your smart contracts passed three audits. Your AWS KMS key didn't.


On March 22, 2026, an attacker compromised Resolv's cloud infrastructure, gained access to a privileged signing key stored in AWS KMS, and used it to mint 80 million unbacked USR stablecoins. Total extraction: ~$25 million in ETH. The smart contracts worked exactly as designed. Every signature was valid. Every function call was authorized.

That's the terrifying part.

The Resolv hack isn't a story about buggy Solidity. It's a story about what happens when DeFi protocols trust off-chain infrastructure with on-chain authority — and that infrastructure fails. As protocols grow more complex, this attack surface is expanding faster than anyone wants to admit.

Let's dissect what happened, why it worked, and the eight defense patterns that could have prevented it.

What Happened: The Two-Step Mint That Had No Ceiling

Resolv's USR minting uses a two-step off-chain flow:

  1. requestSwap — User deposits USDC into the USR Counter contract
  2. completeSwap — An off-chain service (the SERVICE_ROLE key) calls back to finalize how much USR to mint

The contract enforces a minimum USR output. But critically: no maximum. No on-chain ratio check. No price oracle validation. No cap. Whatever the privileged key signs gets minted.

// Simplified Resolv minting logic
function completeSwap(
    bytes32 requestId,
    uint256 usrAmount,
    bytes calldata signature
) external {
    // ✅ Checks signature is valid
    require(verifySignature(requestId, usrAmount, signature), "Invalid sig");
    // ✅ Checks minimum output
    require(usrAmount >= requests[requestId].minOutput, "Below minimum");
    // ❌ No maximum output check
    // ❌ No collateral ratio validation
    // ❌ No on-chain price oracle
    _mint(requests[requestId].recipient, usrAmount);
}
Enter fullscreen mode Exit fullscreen mode

The Attack Chain

  1. Compromise AWS KMS — The attacker gained access to Resolv's cloud environment where the SERVICE_ROLE signing key was stored
  2. Deposit ~$200K USDC — Just enough to create legitimate-looking requestSwap calls
  3. Sign inflated completeSwap calls — Used the compromised key to authorize 50M USR in one transaction, 30M in another
  4. Convert USR → wstUSR → stablecoins → ETH — Rotated through DEX pools to maximize extraction
  5. Result: ~11,400 ETH (~$24M) extracted, USR depegged 80%

The attacker even attempted to mint more after the initial extraction, but Resolv's team managed to pause the protocol in time.

Why This Keeps Happening

The Resolv hack follows a pattern we've seen repeatedly in 2026:

Incident Root Cause Loss
Resolv (Mar 2026) Compromised AWS KMS signing key $25M
Step Finance (Jan 2026) Compromised executive devices $27M
Radiant Capital (Oct 2024) Compromised multisig signers via malware $50M
Ronin Bridge (Mar 2022) Compromised validator keys $625M

The common thread: the smart contract was fine; the key management wasn't.

In 2026, the majority of major DeFi losses aren't coming from reentrancy bugs or integer overflows. They're coming from compromised keys, weak operational security, and off-chain infrastructure that has on-chain god-mode access.

8 Off-Chain Security Patterns Every DeFi Protocol Needs

Pattern 1: On-Chain Minting Invariants

Never trust off-chain logic alone to enforce critical business rules. The contract itself must enforce maximum bounds.

// ✅ FIXED: On-chain invariant enforcement
uint256 public constant MAX_MINT_RATIO = 1.05e18; // 105% max
uint256 public constant MAX_SINGLE_MINT = 1_000_000e18; // 1M cap

function completeSwap(
    bytes32 requestId,
    uint256 usrAmount,
    bytes calldata signature
) external {
    Request memory req = requests[requestId];

    // Off-chain signature validation
    require(verifySignature(requestId, usrAmount, signature), "Invalid sig");

    // ON-CHAIN invariants (can't be bypassed by key compromise)
    uint256 maxAllowed = (req.collateralAmount * MAX_MINT_RATIO) / 1e18;
    require(usrAmount <= maxAllowed, "Exceeds collateral ratio");
    require(usrAmount <= MAX_SINGLE_MINT, "Exceeds single mint cap");

    // Rate limiting
    require(
        block.timestamp >= lastMintTime + MIN_MINT_INTERVAL,
        "Rate limited"
    );
    lastMintTime = block.timestamp;

    _mint(req.recipient, usrAmount);
}
Enter fullscreen mode Exit fullscreen mode

The Rule: If a compromised key can violate a business invariant, that invariant isn't enforced — it's suggested.

Pattern 2: Tiered Key Authority with Circuit Breakers

No single key should have unlimited minting authority. Implement tiered thresholds:

contract TieredMinting {
    // Tier 1: Service key — up to 100K per tx, 500K daily
    address public serviceKey;
    uint256 public constant TIER1_TX_LIMIT = 100_000e18;
    uint256 public constant TIER1_DAILY_LIMIT = 500_000e18;

    // Tier 2: Multisig (3/5) — up to 5M per tx
    address public governanceMultisig;
    uint256 public constant TIER2_TX_LIMIT = 5_000_000e18;

    // Tier 3: Timelock (48h) — unlimited but delayed
    address public timelockController;

    // Circuit breaker: auto-pause if daily volume exceeds threshold
    uint256 public constant CIRCUIT_BREAKER_THRESHOLD = 2_000_000e18;

    modifier withCircuitBreaker(uint256 amount) {
        dailyVolume += amount;
        if (dailyVolume > CIRCUIT_BREAKER_THRESHOLD) {
            _pause();
            emit CircuitBreakerTriggered(dailyVolume);
            revert("Circuit breaker triggered");
        }
        _;
    }
}
Enter fullscreen mode Exit fullscreen mode

If Resolv had a $1M daily cap enforced on-chain, the attacker could have stolen at most $1M before the circuit breaker fired — a 96% reduction in damage.

Pattern 3: Oracle-Backed Sanity Checks

Even if your minting uses off-chain pricing, add an on-chain oracle as a sanity check:

function completeSwap(
    bytes32 requestId,
    uint256 usrAmount,
    bytes calldata signature
) external {
    uint256 collateralValue = getOraclePrice(USDC) * req.collateralAmount / 1e18;
    uint256 mintValue = usrAmount; // USR targets $1 peg

    // Allow 10% deviation from oracle price
    require(
        mintValue <= collateralValue * 110 / 100,
        "Mint exceeds oracle-validated collateral value"
    );
    // ...
}
Enter fullscreen mode Exit fullscreen mode

This creates a defense-in-depth layer: even if the off-chain service is fully compromised, the on-chain oracle prevents minting $25M from a $200K deposit.

Pattern 4: KMS Key Isolation and Rotation

The Resolv attacker compromised a single AWS KMS environment and gained access to the protocol's most powerful key. Better practices:

  • Dedicated AWS accounts for each critical key (not shared with development or staging)
  • Hardware Security Modules (HSMs) for keys with minting authority — AWS CloudHSM or dedicated Thales Luna
  • Automatic key rotation with 30-day maximum lifetime
  • Break-glass procedures that require out-of-band verification (phone call + hardware token) for any key recovery
  • Geo-fencing: KMS key usage restricted to specific IP ranges and VPC endpoints
// AWS KMS Key Policy  restrict usage
{
  "Condition": {
    "StringEquals": {
      "kms:ViaService": "lambda.us-east-1.amazonaws.com"
    },
    "IpAddress": {
      "aws:SourceIp": ["10.0.1.0/24"]
    },
    "NumericLessThan": {
      "kms:RecipientAttestation:PCR0": "1"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 5: Real-Time Anomaly Detection with Automated Response

The gap between "attack detected" and "protocol paused" is where millions are lost. Implement automated monitoring:

  • Hexagate/Forta/Hypernative bots watching for anomalous minting ratios
  • Automated pause triggers — monitoring bots should have PAUSER_ROLE authority
  • Alert escalation: anomaly → auto-pause → team notification → investigation
// Guardian bot can pause if anomaly detected
contract MintingGuardian {
    function emergencyPause(string calldata reason) external onlyGuardian {
        IMintable(usrContract).pause();
        emit EmergencyPause(msg.sender, reason, block.timestamp);
    }
}
Enter fullscreen mode Exit fullscreen mode

Resolv's team did pause the protocol — but only after $25M was already gone. An automated system watching for completeSwap calls where usrAmount / collateralAmount > 2x could have paused the contract between the first and second mint transactions.

Pattern 6: Time-Delayed Large Operations

For any minting operation above a threshold, enforce an on-chain delay:

function completeSwap(/* ... */) external {
    if (usrAmount > LARGE_MINT_THRESHOLD) {
        // Queue for 24h delay — gives team time to verify
        pendingMints[requestId] = PendingMint({
            amount: usrAmount,
            executeAfter: block.timestamp + 24 hours,
            cancelled: false
        });
        emit LargeMintQueued(requestId, usrAmount);
        return; // Don't mint yet
    }
    // Small mints execute immediately
    _executeMint(requestId, usrAmount);
}
Enter fullscreen mode Exit fullscreen mode

This is the same principle behind governance timelocks, applied to minting. A 24-hour delay on any mint above $500K would have given Resolv's team ample time to notice and cancel the attack.

Pattern 7: Multi-Party Computation (MPC) for Critical Keys

Replace single-point-of-failure KMS keys with threshold signatures:

  • 2-of-3 MPC for routine operations (service bot + team member + security partner)
  • 3-of-5 MPC for large operations
  • Keys distributed across different cloud providers (AWS + GCP + on-premise HSM)

Even if the attacker compromises one cloud environment, they need to independently compromise a second environment on a different provider to sign a transaction. This exponentially increases attack difficulty.

Pattern 8: Comprehensive Off-Chain Infrastructure Audit

Smart contract audits are table stakes. Your off-chain infrastructure needs the same rigor:

Audit Checklist:

  • [ ] Key management: Where are signing keys stored? Who has access?
  • [ ] Cloud security: MFA on all accounts? IP restrictions? VPC isolation?
  • [ ] Key authority mapping: What can each key do on-chain? What's the max damage?
  • [ ] Incident response: How fast can you pause? Who gets called? What's the runbook?
  • [ ] Key rotation: When was the last rotation? Is it automated?
  • [ ] Monitoring: What alerts exist for anomalous key usage?
  • [ ] Backup/recovery: Can you revoke a compromised key without losing access?
  • [ ] Supply chain: What third-party services have access to your infrastructure?

Solana Parallel: Account Authority and Upgrade Keys

This isn't just an EVM problem. Solana protocols face identical risks with program upgrade authorities and PDAs:

// Solana: Enforce on-chain invariants for privileged operations
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
    let config = &ctx.accounts.config;

    // On-chain cap enforcement
    require!(
        amount <= config.max_single_mint,
        ErrorCode::ExceedsMintCap
    );

    // Daily rate limiting via on-chain state
    let clock = Clock::get()?;
    let current_day = clock.unix_timestamp / 86400;

    if config.last_mint_day == current_day {
        require!(
            config.daily_minted + amount <= config.daily_limit,
            ErrorCode::DailyLimitExceeded
        );
    }

    // Circuit breaker: check total supply growth rate
    let supply = ctx.accounts.mint.supply;
    let max_growth = supply / 100; // 1% max daily growth
    require!(amount <= max_growth, ErrorCode::GrowthRateExceeded);

    // Execute mint
    mint_to(/* ... */)?;
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

For Solana programs with upgrade authority:

  • Transfer upgrade authority to a multisig (Squads Protocol)
  • Set upgrade authority to None for immutable programs when stable
  • Use timelock programs for upgrades (e.g., Clockwork + governance)

The Uncomfortable Truth

The DeFi security industry has spent years getting better at auditing smart contracts. And it's working — pure smart contract bugs are accounting for a shrinking percentage of losses.

But the attack surface has shifted. In 2026, the biggest exploits target:

  1. Key management (Resolv, Step Finance, Radiant Capital)
  2. Off-chain infrastructure (AWS, cloud services, CI/CD pipelines)
  3. Supply chain (ForceMemo, Trivy, AppsFlyer SDK)
  4. Human factors (phishing, social engineering, device compromise)

Your next audit shouldn't just cover your Solidity. It should cover your AWS IAM policies, your key rotation schedule, your team's device security, and your incident response time.

Because the attacker who hits you next probably won't find a bug in your code. They'll find a shortcut around it.


This article is part of the DeFi Security Research series. Follow for weekly deep-dives into exploits, audit techniques, and defense patterns across EVM and Solana ecosystems.

Further Reading:

Top comments (0)