Skip to content

fix(api): restrict bootstrap shared caching#4499

Merged
koala73 merged 2 commits into
mainfrom
codex/issue-4495-bootstrap-cache
Jun 28, 2026
Merged

fix(api): restrict bootstrap shared caching#4499
koala73 merged 2 commits into
mainfrom
codex/issue-4495-bootstrap-cache

Conversation

@koala73

@koala73 koala73 commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Restricts successful /api/bootstrap shared caching to auth.kind === "public-weather" only.
  • Keeps anonymous GET /api/bootstrap?keys=weatherAlerts cacheable while making session/user/enterprise bootstrap successes no-store.
  • Adds a session-auth bootstrap regression test to the existing cache/auth matrix.

Intent

Fixes #4495.

The remaining cache boundary after the bootstrap auth hardening was that non-key success kinds could still inherit public/CDN cache headers. This PR makes the shared-cache exception explicit and narrow: anonymous weather bootstrap only.

Non-goals:

  • No changes to bootstrap key selection or payload shape.
  • No UI changes.
  • No changes to API-key validation semantics.

Validation Matrix

Check Reason Result
node --test api/bootstrap-auth.test.mjs api/bootstrap-cors.test.mjs api/_api-key.test.mjs Focused bootstrap/auth/session cache matrix Pass: 47 tests
TMPDIR=/tmp node --import tsx --test tests/embed-public-data-auth.test.mts Public/anonymous bootstrap exception guard Pass: 3 tests
npm run typecheck:api API typecheck and Convex call audit Pass
npm run test:sidecar Broader API/sidecar auth contract suite Pass: 163 tests
git diff --check Diff hygiene Pass
git push -u origin HEAD pre-push hook Repo pre-push gates including typecheck, API typecheck, boundary lint, safe HTML, rate-limit policy, premium-fetch parity, edge function bundle/tests, version sync Pass

Review Gates

  • Baseline reviewer: Pass. Found one fallback branch still using the old enterprise/user-only predicate; fixed before publish.
  • Code Review Expert: Pass. SOLID, security, code-quality, and removal checklists reviewed against the final two-file diff; no remaining findings.
  • Outcome reviewer: Pass. Final diff satisfies the issue acceptance criteria and validation rows are green.

Documentation

Not applicable. This is an internal API response-header policy fix with regression coverage.

Screenshots / UI Evidence

Not applicable. No user-visible UI changed.

Residual Findings

None.

@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jun 28, 2026 11:27am

Request Review

@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Restricts /api/bootstrap shared caching to auth.kind === 'public-weather' only by inverting the previous isKeyAuth predicate. The old check (authKind === 'enterprise' || authKind === 'user') left session and unknown auth kinds falling through to shared CDN-cacheable headers, meaning an HMAC-signed anonymous session request to any non-weather endpoint could have been shared-cached by a CDN — exposing potentially scoped response data to other callers.

  • bootstrap.js: Two sites updated — successCacheHeaders and the getCachedJsonBatch error fallback — both now use authKind !== 'public-weather' as the gate, so session, unknown, enterprise, and user all get no-store.
  • bootstrap-auth.test.mjs: Adds a session-cookie regression test (and snapshots/restores WM_SESSION_SECRET) to the existing auth/cache matrix, exercising the full cryptographic validation path against a test-secret.

Confidence Score: 5/5

Safe to merge — the change is a narrowly scoped inversion of a two-site predicate with no behavior change on the public-weather path and correct tightening everywhere else.

Both changed sites are logically equivalent: replacing an explicit allowlist (enterprise | user) with its complement (!== 'public-weather') correctly pulls session and unknown kinds into the no-store set. The validateBootstrapAuth function already assigns 'session' to HMAC-validated cookie requests and 'public-weather' only to keyless weather-URL requests, so the new gate exactly matches the intended boundary. The new test exercises the real crypto path via issueSessionToken against a properly snapshotted WM_SESSION_SECRET and asserts the same assertNonSharedCacheHeaders helper already used across the matrix.

No files require special attention.

Important Files Changed

Filename Overview
api/bootstrap.js Two-site predicate fix: successCacheHeaders and the Redis-error fallback both updated from the explicit enterprise/user allowlist to the narrower authKind !== 'public-weather' check, correctly adding session and unknown kinds to the no-store set.
api/bootstrap-auth.test.mjs Adds WM_SESSION_SECRET to env snapshot/restore and a session-authenticated regression test that exercises the real HMAC path via issueSessionToken, correctly asserting no-store on the response.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Handler as bootstrap handler
    participant Auth as validateBootstrapAuth
    participant Redis as getCachedJsonBatch
    participant Headers as successCacheHeaders

    Client->>Handler: "GET /api/bootstrap?keys=..."
    Handler->>Auth: validateBootstrapAuth(req)

    alt "No API key header AND ?keys=weatherAlerts only"
        Auth-->>Handler: "{ ok:true, kind:'public-weather' }"
    else Session cookie (wms_ token)
        Auth->>Auth: validateApiKey → kind:'session'
        Auth-->>Handler: "{ ok:true, kind:'session' }"
    else Enterprise key
        Auth-->>Handler: "{ ok:true, kind:'enterprise' }"
    else wm_ user key
        Auth-->>Handler: "{ ok:true, kind:'user' }"
    end

    Handler->>Redis: getCachedJsonBatch(keys)

    alt Redis throws
        Note over Handler: auth.kind === 'public-weather'? no-cache : no-store
        Handler-->>Client: "200 { data:{}, missing } + no-cache OR no-store"
    else Redis succeeds
        Redis-->>Handler: cached data map
        Handler->>Headers: successCacheHeaders(tier, auth.kind, cors)
        alt "auth.kind === 'public-weather'"
            Headers-->>Handler: Cache-Control: public + CDN-Cache-Control
        else any other kind (session, enterprise, user, unknown)
            Headers-->>Handler: Cache-Control: no-store
        end
        Handler-->>Client: "200 { data, missing } + cache headers"
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Handler as bootstrap handler
    participant Auth as validateBootstrapAuth
    participant Redis as getCachedJsonBatch
    participant Headers as successCacheHeaders

    Client->>Handler: "GET /api/bootstrap?keys=..."
    Handler->>Auth: validateBootstrapAuth(req)

    alt "No API key header AND ?keys=weatherAlerts only"
        Auth-->>Handler: "{ ok:true, kind:'public-weather' }"
    else Session cookie (wms_ token)
        Auth->>Auth: validateApiKey → kind:'session'
        Auth-->>Handler: "{ ok:true, kind:'session' }"
    else Enterprise key
        Auth-->>Handler: "{ ok:true, kind:'enterprise' }"
    else wm_ user key
        Auth-->>Handler: "{ ok:true, kind:'user' }"
    end

    Handler->>Redis: getCachedJsonBatch(keys)

    alt Redis throws
        Note over Handler: auth.kind === 'public-weather'? no-cache : no-store
        Handler-->>Client: "200 { data:{}, missing } + no-cache OR no-store"
    else Redis succeeds
        Redis-->>Handler: cached data map
        Handler->>Headers: successCacheHeaders(tier, auth.kind, cors)
        alt "auth.kind === 'public-weather'"
            Headers-->>Handler: Cache-Control: public + CDN-Cache-Control
        else any other kind (session, enterprise, user, unknown)
            Headers-->>Handler: Cache-Control: no-store
        end
        Handler-->>Client: "200 { data, missing } + cache headers"
    end
Loading

Reviews (1): Last reviewed commit: "fix(api): restrict bootstrap shared cach..." | Re-trigger Greptile

@koala73 koala73 merged commit 7a20a4d into main Jun 28, 2026
24 checks passed
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.

fix(api): restrict bootstrap shared caching to anonymous weather bootstrap

1 participant