# Stale oracle window bypass

Freshness or averaging checks defeated by sentinel values, bucket boundaries, or allowed deviation bands

_Area: DeFi oracles, liquidations and flash loans. Seen in 3 disclosed reports; latest 2025-02-21. Updated 2026-10-24._

## How it works
A protocol assumes its oracle check guarantees "recent, trustworthy price." The check is implemented as a proxy: a sentinel comparison, an integer-bucketed time window, or a deviation/heartbeat bound. Each proxy has edge cases the real freshness property does not, so a price that is actually stale or manipulable still passes. The assumption that breaks is "passing the check means the price is safe."

## Where it shows up
- Freshness checks comparing a timestamp to `0` (or any single sentinel) rather than to `block.timestamp` minus a window.
- Time windows computed with integer division (`block.timestamp / 1 days`) so adjacent blocks straddle a boundary.
- Oracle configs with wide deviation thresholds or long heartbeats relative to the asset's real volatility.
- Multiple feeds combined where each has its own independent deviation band.
- TWAP/observation code that trusts library-set fields without cross-checking the library's actual behavior.

## How to review
The invariant: the price used must be provably fresh and manipulation-resistant for the exact block it is consumed in. Check that freshness compares against `block.timestamp` with an explicit max-age, not a sentinel. For windowed lows/averages, verify that two adjacent blocks cannot satisfy a multi-block window by landing in different buckets — test blocks straddling the boundary. For deviation/heartbeat bounds, compute worst-case drift: the maximum the price can move within the allowed band and heartbeat, and whether that drift is profitable to exploit. For multi-feed pricing, model all feeds drifting to opposite band edges simultaneously, not one at a time [E2](https://bugbountylibrary.com/r/c4%3A2023-11-kelp%3AH-01.md). Cross-reference any oracle library call (e.g. Uniswap `grow()`) against its real source to confirm what values it writes [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-4.md).

## Variants seen
- Sentinel mismatch: check reverts only on `timestamp==0`, but the library sets `1`, so an invalid observation passes [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-4.md).
- Bucket-boundary bypass: day-bucketed two-day low defeated by two blocks straddling midnight [E3](https://bugbountylibrary.com/r/c4%3A2022-10-inverse%3AM-07.md).
- Deviation-band lag: wide thresholds plus 24h heartbeats let mints price off stale rates, diluting holders ~2% [E2](https://bugbountylibrary.com/r/c4%3A2023-11-kelp%3AH-01.md).

## Impact and severity
Led to rebalancing on invalid TWAP prices and user fund loss [E1](https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-4.md), ~2% dilution of existing holders via cheap mints [E2](https://bugbountylibrary.com/r/c4%3A2023-11-kelp%3AH-01.md), and defeat of multi-day manipulation resistance [E3](https://bugbountylibrary.com/r/c4%3A2022-10-inverse%3AM-07.md). Severity rises when the stale price directly sets mint/redeem or liquidation amounts, and when the drift is profitable enough to self-fund.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2025-02-yieldoor%3AM-4.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-11-kelp%3AH-01.md
- E3: https://bugbountylibrary.com/r/c4%3A2022-10-inverse%3AM-07.md
