Skip to content

fix(prices): sanitize price values from oracle_prices and markets_with_stats#152

Open
6figpsolseeker wants to merge 1 commit intodcccrypto:mainfrom
6figpsolseeker:fix/prices-validation-sanitize
Open

fix(prices): sanitize price values from oracle_prices and markets_with_stats#152
6figpsolseeker wants to merge 1 commit intodcccrypto:mainfrom
6figpsolseeker:fix/prices-validation-sanitize

Conversation

@6figpsolseeker
Copy link
Copy Markdown

@6figpsolseeker 6figpsolseeker commented Apr 9, 2026

Summary

  • Both `/prices/markets` and `/prices/:slab` returned raw Supabase rows without validating numeric price fields. A bad row in the upstream indexer (negative, NaN, Infinity, or absurdly large) would propagate straight to chart consumers, where `lightweight-charts` silently fails on a single corrupt point in an ordered series.
  • This mirrors the sanitization already applied in `src/routes/markets.ts` (`Number.isFinite` + `> 0` + `<= 1_000_000_000` USD bound), with a parallel `1e15` bound for `price_e6` (microUSD).
  • Two strategies, matched to how each endpoint is consumed:
    • `/prices/markets` returns one record per market — bad fields are nulled out so the rest of the row stays intact.
    • `/prices/:slab` feeds an ordered chart series — bad rows are dropped entirely so consumers never see a hole or a NaN.

Out-of-band test cleanup

This PR also updates two stale assertions in `tests/routes/prices.test.ts` that asserted `ascending: false` / `limit(100)`. The source has shipped `ascending: true` / `limit(1500)` for some time (the comment at `prices.ts:30-37` explains why). These assertions were the lone failing test on `main`. Touching them inline since I'm already editing this test file; happy to split out if preferred.

Test plan

  • New test: `/prices/markets` nulls out negative / zero / NaN / Infinity / out-of-range fields while preserving slab and timestamp
  • New test: `/prices/:slab` drops rows with negative / zero / NaN / null / >1e15 `price_e6`, preserves valid rows
  • Full suite: 191/191 passing (was 188/189 on `main`)
  • `npx tsc --noEmit` — clean

Summary by CodeRabbit

  • Bug Fixes
    • Price API endpoints now sanitize invalid price data (negative, zero, non-finite values, or exceeding bounds), replacing corrupted entries with null.
    • Price history responses now filter out entries with invalid price values, displaying only verified data points.

…h_stats

Both /prices/markets and /prices/:slab returned raw rows from Supabase
without validating numeric price fields. A bad row in the upstream
indexer (negative, NaN, Infinity, or absurdly large) would propagate
straight to the chart consumers, where lightweight-charts silently fails
on a single corrupt point in an ordered series.

This mirrors the sanitization already applied in src/routes/markets.ts
(Number.isFinite + > 0 + <= 1_000_000_000 USD bound), with a parallel
1e15 bound for price_e6 (microUSD).

Two strategies, depending on consumer expectations:
- /prices/markets returns one record per market — bad fields are nulled
  so the rest of the row stays intact.
- /prices/:slab feeds an ordered chart series — bad rows are dropped
  entirely so consumers never see a hole or a NaN.

Also updates two pre-existing stale assertions in tests/routes/prices.test.ts
that asserted ascending: false / limit(100) — the source has shipped
ascending: true / limit(1500) for some time and the comment at
prices.ts:30-37 explains why.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Added price validation bounds and sanitization logic to the prices endpoints. The GET /prices/markets response now sanitizes price fields (last_price, mark_price, index_price) by replacing invalid or out-of-bounds values with null. The GET /prices/:slab response filters out rows with invalid price_e6 values. Tests updated to verify sanitization behavior and adjust query parameter expectations.

Changes

Cohort / File(s) Summary
Price Route Implementation
src/routes/prices.ts
Added MAX_SANE_PRICE_USD and MAX_SANE_PRICE_E6 constants with sanitizeUsdPrice helper. Modified GET /prices/markets to map and sanitize price fields (non-finite, ≤0, or out-of-bounds values become null). Modified GET /prices/:slab to filter rows, retaining only entries with valid positive finite price_e6 within bounds.
Price Route Tests
tests/routes/prices.test.ts
Extended GET /prices/markets tests to verify null sanitization for invalid price values while preserving slab_address and updated_at. Extended GET /prices/:slab tests to verify rows with invalid/missing price_e6 are omitted from response. Updated query expectations: timestamp ordering changed to ascending and limit increased from 100 to 1500.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With sanity bounds set firm and true,
Bad prices now turn into blue!
Invalid values cleaned away,
Markets sparkle bright today! ✨
The rabbit's code makes data right,
Every price shines pure and bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding sanitization for price values from price-related endpoints to prevent corrupt data propagation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/routes/prices.ts (1)

10-19: Centralize price-sanitization rules in a shared utility.

This duplicates rules already documented as mirrored from src/routes/markets.ts. Extracting bounds + validators into a shared helper will prevent rule drift between endpoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/prices.ts` around lines 10 - 19, Extract MAX_SANE_PRICE_USD,
MAX_SANE_PRICE_E6 and sanitizeUsdPrice into a shared utility module (e.g., a new
exported helper) and import it from both places so the bounds and validator are
single-source; move the constant definitions and the predicate logic into that
helper, add a corresponding sanitizePriceE6 (or exported constant) for microUSD
usage, update references in sanitizeUsdPrice and any callers in routes/prices.ts
and routes/markets.ts to import the helper, and ensure exports are typed and
tested so both endpoints use the same centralized rules.
tests/routes/prices.test.ts (1)

107-108: Clean up contradictory inline fixture comment.

The “wait this is > 1e9” note reads like leftover debugging text and makes the test intent harder to parse.

Suggested cleanup
-          index_price: 50000000000,        // valid (clamped under 1e9)... wait this is > 1e9
+          index_price: 50000000000,        // > 1e9 bound → null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/routes/prices.test.ts` around lines 107 - 108, The inline fixture
comment next to the index_price value is contradictory and looks like leftover
debugging text; update the test fixture by either removing the phrase "wait this
is > 1e9" from the comment or make the value and comment consistent (e.g., set
index_price <= 1e9 if you mean "clamped under 1e9" or change the comment to
reflect that index_price is intentionally > 1e9); target the test object
containing the index_price and updated_at fields in the prices test and ensure
the comment accurately describes the fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/routes/prices.ts`:
- Around line 10-19: Extract MAX_SANE_PRICE_USD, MAX_SANE_PRICE_E6 and
sanitizeUsdPrice into a shared utility module (e.g., a new exported helper) and
import it from both places so the bounds and validator are single-source; move
the constant definitions and the predicate logic into that helper, add a
corresponding sanitizePriceE6 (or exported constant) for microUSD usage, update
references in sanitizeUsdPrice and any callers in routes/prices.ts and
routes/markets.ts to import the helper, and ensure exports are typed and tested
so both endpoints use the same centralized rules.

In `@tests/routes/prices.test.ts`:
- Around line 107-108: The inline fixture comment next to the index_price value
is contradictory and looks like leftover debugging text; update the test fixture
by either removing the phrase "wait this is > 1e9" from the comment or make the
value and comment consistent (e.g., set index_price <= 1e9 if you mean "clamped
under 1e9" or change the comment to reflect that index_price is intentionally >
1e9); target the test object containing the index_price and updated_at fields in
the prices test and ensure the comment accurately describes the fixture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2c56d52-96bc-4552-ba2c-e3bc4e839dd6

📥 Commits

Reviewing files that changed from the base of the PR and between fb94c29 and 93956a0.

📒 Files selected for processing (2)
  • src/routes/prices.ts
  • tests/routes/prices.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant