Reproduced Exploit

CAP Labs PriceOracle — global staleness period rejects valid feeds

CAP's oracle stores one staleness period for all assets. Configuring one hour for hourly ETH feeds makes a valid daily USDC-like report revert after five thousand seconds, causing a price-feed DoS.

May 2025Otheroracle3 min read

Chain

Other

Category

oracle

Date

May 2025

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 61525-incorrect-oracle-staleness-period-leads-to-price-feed-dos-tr. Standalone Foundry PoC and full write-up: 61525-incorrect-oracle-staleness-period-price-feed-dos_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/oracle/stale-price · vuln/oracle/missing-validation · vuln/dos/frozen-funds

Reproduction: local synthetic Foundry reduction; the complete passing trace is in output.txt.

Key info#

FieldValue
LossA daily-heartbeat feed is rejected as stale, making dependent lending paths unavailable.
Vulnerable contractPriceOracle.getPrice in test/61525-incorrect-oracle-staleness-period-price-feed-dos.sol
Attacker EOA0x1111111111111111111111111111111111111111
Attack contractExploit
Attack txLocal Foundry Exploit.run()
Chain · block · dateEthereum model · block 0 · synthetic
CompilerSolidity ^0.8.24
Bug classOne global heartbeat/staleness threshold

TL;DR#

CAP's oracle stores one staleness period for all assets. Configuring one hour for hourly ETH feeds makes a valid daily USDC-like report revert after five thousand seconds, causing a price-feed DoS.

Background#

Chainlink feeds publish at asset-specific heartbeats. A robust oracle stores each feed's heartbeat and validates freshness against that value, not a global constant.

The vulnerable code#

SOLIDITY
function getPrice(address asset, uint256 nowTs) external view returns (uint256) {
    Feed memory f = feeds[asset];
    // @> VULN: every asset shares one staleness period; heartbeat is ignored.
    require(nowTs - f.updatedAt <= staleness, "stale price");
    return f.price;
}

Root cause#

The deployment sets staleness = 1 hours even though assets have 1-hour and 1-day heartbeats. The contract cannot distinguish a valid slow feed from a genuinely stale one.

Preconditions#

  • At least two feeds have different update frequencies.
  • The global staleness period is chosen for the faster feed.
  • Lending/borrowing functions require getPrice to succeed.

Attack walkthrough#

  1. Configure a daily-heartbeat feed with updatedAt = 95,000, now = 100,000.
  2. The global one-hour check reverts even though the feed heartbeat allows it.
  3. staleFeedDos and the heartbeat comparison prove the false rejection; see output.txt:4.

Diagrams#

flowchart TD F[Daily feed: heartbeat 86400] --> O[Global staleness = 3600] O --> C{Age = 5000} C -->|global check| R[Revert: stale price] C -->|feed heartbeat| V[Would be valid]

Remediation#

Store a staleness/heartbeat value per feed and validate the Chainlink round's updatedAt against that value. Add configuration tests covering hourly and daily feeds, and define safe behavior when a feed is missing.

How to reproduce#

BASH
cd evm-hack-registry/61525-incorrect-oracle-staleness-period-price-feed-dos_exp
forge test -vvvvv

Sources#

Reference: https://github.com/trailofbits/publications/blob/master/reviews/2025-05-caplabs-coveredagentprotocol-securityreview.pdf


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.