Skip to content

fix(gateway): gate CDN-Cache-Control on trusted origin#2496

Merged
koala73 merged 4 commits intomainfrom
fix/cdn-cache-external-bypass
Mar 29, 2026
Merged

fix(gateway): gate CDN-Cache-Control on trusted origin#2496
koala73 merged 4 commits intomainfrom
fix/cdn-cache-external-bypass

Conversation

@koala73
Copy link
Copy Markdown
Owner

@koala73 koala73 commented Mar 29, 2026

Why

External projects have been discovered calling WorldMonitor API endpoints server-side (no Origin header, no key). The Vercel CDN serves s-maxage cached responses without re-invoking the edge function, meaning a 200 cached by a trusted-origin browser request could potentially be served back to an unauthenticated no-origin caller, bypassing validateApiKey().

What changed

server/cors.ts — exports isAllowedOrigin so the gateway can reuse the same trusted-origin logic.

server/gateway.ts — gates CDN-Cache-Control on whether the request came from a trusted origin:

const reqOrigin = request.headers.get('origin') || '';
const cdnCache = isAllowedOrigin(reqOrigin) ? TIER_CDN_CACHE[tier] : null;

No-origin requests now receive only Cache-Control (browser/client hint) and never CDN-Cache-Control, so Vercel CDN never stores their responses. Every no-origin request must reach the edge function, where validateApiKey() returns 401.

Impact

Caller Before After
worldmonitor.app browser CDN cached (unchanged) CDN cached (unchanged)
Tauri desktop CDN cached (unchanged) CDN cached (unchanged)
Vercel preview CDN cached (unchanged) CDN cached (unchanged)
External scraper (no Origin) Potentially served cached 200 Never CDN cached — always 401

Future

When the developer API program ships, the condition can be extended to isAllowedOrigin(reqOrigin) || hasValidDevKey so external developers with PRO keys also benefit from CDN caching.

… bypass

No-origin server-side requests (external scrapers) no longer receive a
CDN-Cache-Control header, so Vercel CDN never caches their responses.
Every request without a trusted origin must reach the edge function,
ensuring validateApiKey() always runs and returns 401 for unauthenticated
callers.

Trusted origins (worldmonitor.app, Vercel previews, Tauri) are unchanged —
CDN caching behaviour and hit rates for the app itself are unaffected.
@mintlify
Copy link
Copy Markdown

mintlify bot commented Mar 29, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
WorldMonitor 🟢 Ready View Preview Mar 29, 2026, 8:21 AM

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview Mar 29, 2026 8:50am

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 29, 2026

Greptile Summary

This PR closes a CDN cache-bypass path by gating the CDN-Cache-Control response header on whether the request originates from a trusted origin. Previously, any no-origin (server-side) request that reached a public endpoint could receive a CDN-Cache-Control: public, s-maxage=… header, causing Vercel CDN to store the 200 response under the (url, origin="") cache key. All subsequent no-origin callers would be served that cached 200 without invoking the edge function — and therefore without running validateApiKey(). The fix reuses the existing isAllowedOrigin() predicate (now exported from cors.ts) to suppress CDN-Cache-Control for all no-origin requests, guaranteeing the edge function is always invoked and the auth check always runs.

Key changes:

  • server/cors.ts: isAllowedOrigin promoted from module-private to exported — no logic change.
  • server/gateway.ts: TIER_CDN_CACHE[tier] is now conditionally assigned — null (no CDN-Cache-Control) when the request has no trusted origin, meaning no-origin responses are never stored in Vercel's shared edge cache.
  • Trusted-origin callers (worldmonitor.app browser, Tauri, Vercel previews) are fully unaffected; they continue to receive CDN-Cache-Control and benefit from CDN caching as before.
  • Cache-Control (browser/client-level) continues to be emitted for all callers, which is intentional and safe since that cache is private to the individual HTTP client.
  • The PR is well-scoped and the comment block above the new conditional clearly explains the threat model. The only gap is the absence of a regression test asserting the header-level behavior (see inline comment).

Confidence Score: 5/5

Safe to merge — the fix is logically correct, has no impact on existing trusted-origin callers, and the only finding is a P2 suggestion to add a regression test.

The logic is sound: isAllowedOrigin('') correctly returns false (guarded by Boolean(origin)), so no-origin requests always receive cdnCache = null and never emit CDN-Cache-Control. Trusted-origin behavior is unchanged. The single comment is a non-blocking P2 test-coverage suggestion.

No files require special attention; server/gateway.ts holds the core change and reads correctly.

Important Files Changed

Filename Overview
server/gateway.ts Gates CDN-Cache-Control on isAllowedOrigin(reqOrigin); no-origin requests receive only Cache-Control and are never CDN-cached, ensuring the edge function always runs so validateApiKey() can return 401.
server/cors.ts Exports isAllowedOrigin (previously module-private) so the gateway can reuse the same trusted-origin pattern matching; no logic changed.
CHANGELOG.md Adds a Security section entry describing the CDN cache bypass fix; accurate and well-placed.
docs/changelog.mdx Adds matching Security section for the Mintlify docs site; description matches the implementation accurately.

Sequence Diagram

sequenceDiagram
    participant B as Browser (trusted origin)
    participant S as Scraper (no origin)
    participant CDN as Vercel CDN
    participant EF as Edge Function
    participant AK as validateApiKey()

    note over B,AK: BEFORE fix
    B->>CDN: GET /api/… Origin: worldmonitor.app
    CDN->>EF: cache miss → invoke
    EF->>AK: check key
    AK-->>EF: ✓ allowed
    EF-->>CDN: 200 + CDN-Cache-Control (cached under origin=worldmonitor.app)
    CDN-->>B: 200

    S->>CDN: GET /api/… (no Origin, no key)
    CDN->>EF: cache miss → invoke
    EF->>AK: check key (public route)
    AK-->>EF: ✓ allowed
    EF-->>CDN: 200 + CDN-Cache-Control (cached under origin=empty)
    CDN-->>S: 200 ← cached for all future no-origin callers!

    S->>CDN: GET /api/… (no Origin, no key) second request
    CDN-->>S: 200 (served from cache, edge function never invoked)

    note over B,AK: AFTER fix
    B->>CDN: GET /api/… Origin: worldmonitor.app
    CDN->>EF: cache miss → invoke
    EF-->>CDN: 200 + CDN-Cache-Control (still cached for trusted origins)
    CDN-->>B: 200

    S->>CDN: GET /api/… (no Origin, no key)
    CDN->>EF: always a miss — no CDN-Cache-Control ever set for no-origin
    EF->>AK: check key
    AK-->>EF: ✗ 401
    EF-->>S: 401 (never cached)
Loading

Reviews (1): Last reviewed commit: "fix(gateway): gate CDN-Cache-Control on ..." | Re-trigger Greptile

@koala73
Copy link
Copy Markdown
Owner Author

koala73 commented Mar 29, 2026

Thanks for the review. Addressing each point:

1. Cache-Control with s-maxage

TIER_HEADERS contains no s-maxage and no public — every value is max-age=N, stale-while-revalidate=... only. The gateway comment at line 43–46 already documents this: "CF sees no public s-maxage and passes through." Per HTTP spec, shared caches must not cache responses lacking public or s-maxage. The s-maxage values only exist in TIER_CDN_CACHE, which is what this PR gates. No change needed here.

2. Empty string short-circuit

Confirmed correct. isAllowedOrigin('') returns false via the Boolean(origin) guard.

3. Tauri Origin header

Tauri's webview (WKWebView on macOS/Linux, WebView2 on Windows) is a full browser engine and does send Origin on cross-origin requests. All three Tauri protocol variants are explicitly listed in PRODUCTION_PATTERNS in server/cors.ts:

  • tauri://localhost — macOS/Linux
  • https://tauri.localhost — Windows
  • asset://localhost — older protocol variant

isAllowedOrigin() returns true for all three, so CDN-Cache-Control is set correctly for Tauri webview requests. The impact table is accurate.

The only Tauri path that produces no-Origin requests is the sidecar's Node.js fetch(), but per the desktop architecture the sidecar calls upstream APIs directly and never routes through Vercel edge functions.

@koala73 koala73 merged commit f482c13 into main Mar 29, 2026
8 checks passed
@koala73 koala73 deleted the fix/cdn-cache-external-bypass branch March 29, 2026 08:52
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