Skip to content

cache: ctx.request.headers and getRequestContext().cookie()/cookies()/theme bypass the cache and capture identity guards #976

Closed

Description

@ivogt

Problem

The identity guards stop cookies(), headers() and non-cacheable variables from baking one visitor's data into a shared entry: a PPR capture, a route cache() miss, "use cache", and a loader's own cache() without key() (#972). Three raw read paths bypass all of them:

  1. ctx.request.headers, in handlers, loaders and "use cache" bodies that receive ctx. ctx.request is the same Request object everywhere: request-context.ts:1184, loader-resolution.ts:529, handler-context.ts:266, middleware.ts:246. "use cache" keys a Request by URL only (cache-runtime.ts:487), so a header-derived value can bake into a shared entry.
  2. getRequestContext().cookie() / .cookies() (request-context.ts:1221-1242).
  3. getRequestContext().theme. theme: ctx.theme in a handler bakes the first visitor's theme into a shared PPR shell or cache() entry #971 guards the handler ctx.theme getter but leaves this one as is.

Constraints (from the #972 investigation)

  • The router reads request.headers itself in about 24 source files, e.g. request-context.ts:1043 and the state-cookie read near :1290.
  • Wrapping the Request in a Proxy breaks fetch(ctx.request), new Request(ctx.request) and clone().
  • A workable shape is a headers accessor on the exposed request that checks the same scopes at access time. key() and "use cache" key-building run outside those scopes, so a keyed read keeps working. The router's own reads would move to a raw reference.
  • Breaking for "use cache" bodies that read ctx.request.headers today.

Proposal

Guard the three raw paths with the same scope checks as cookies()/headers(), and document the raw Request escape hatch, if any. Until then, the loader and caching skills say these reads are unguarded and must be part of key().

Activity

  1. ivogt commented on Sep 30, 2026

    @ivogt
    CollaboratorAuthor

    Decision: guard all raw request-identity reads exactly like cookies()/headers(). That covers ctx.request.headers (handlers, loaders, "use cache" bodies that receive ctx) and getRequestContext().cookie()/cookies(). Inside a cache() boundary, "use cache", or an unkeyed loader cache() fill, the read throws or fails. During a PPR capture it refuses the capture. key() is unaffected. This is a breaking change for handlers under cache()/ppr that read raw headers; the error names the fix: middleware, a live loader, or include the value in key(). Scheduled after the guard unification in the simplify pass.

  2. ivogt commented on Sep 30, 2026

    @ivogt
    CollaboratorAuthor

    Fixed in #999 (35c0ae5).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions