You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PPR: capture-time taint guard — refuse to store shells that read per-request data into shell content #653
Opting a route into PPR asserts "this document's shell contains nothing visitor-specific," but nothing verifies it. A handler is free to read cookies()/headers() (PPR is not a cache() scope — the existing INSIDE_CACHE_EXEC guard does not apply) and bake the value into the shared prelude. Middleware guards access, not content. Today this is a silent cross-visitor leak waiting on a code-review miss.
Direction (defense in depth — explicit opt-in remains the model; router-level default-on is rejected, see #648 notes)
During a capture render, flag any per-request read (cookies()/headers()/other request-identity surfaces) that executes OUTSIDE a hole's masked scope, reusing the taint machinery pattern from the cache scopes. If tainted: REFUSE to store (route silently stays axis 1) with a once-per-key diagnostic naming the read and the fix ("move it into a hole or set ppr: false"). Loaders are already exempt by construction (masked at capture). Decide during design whether a per-route escape hatch (ppr: { allowRequestReads: true }) is wanted or whether refusal is absolute.
Test plan
Unit: cookies() in handler → capture refuses + diagnostic; same read inside a hole (behind loading()) → capture proceeds; repeated requests warn once. E2e (dev + production): a tainted fixture stays x-rango-shell MISS with the diagnostic, an equivalent hole-scoped fixture reaches HIT.
cookies() / headers() trip under _shellCaptureRun (src/server/cookie-store.ts). The capture then refuses on _shellCaptureGuardTripped (src/rsc/shell-capture.ts) — no store write, once-per-key diagnostic, route stays axis 1. Loaders are already masked at capture, so a hole-scoped read still bakes. Matches the issue's direction (defense in depth on an explicit ppr opt-in; no router-level default-on).
Activity
ivogt commentedon Aug 30, 2026
Capture-time identity guard is in.
cookies()/headers()trip under_shellCaptureRun(src/server/cookie-store.ts). The capture then refuses on_shellCaptureGuardTripped(src/rsc/shell-capture.ts) — no store write, once-per-key diagnostic, route stays axis 1. Loaders are already masked at capture, so a hole-scoped read still bakes. Matches the issue's direction (defense in depth on an explicitppropt-in; no router-level default-on).