Skip to content

Security hardening: redirect sanitization, header stripping, ALS isolation tests, and safer command execution#105

Merged
threepointone merged 3 commits intomainfrom
s-fixes
Feb 26, 2026
Merged

Security hardening: redirect sanitization, header stripping, ALS isolation tests, and safer command execution#105
threepointone merged 3 commits intomainfrom
s-fixes

Conversation

@threepointone
Copy link
Copy Markdown
Collaborator

@threepointone threepointone commented Feb 26, 2026

Summary

Comprehensive security hardening pass across the server, shims, and CLI layers. Fixes several classes of vulnerabilities and adds regression tests to prevent regressions.


Changes

Redirect & Rewrite Sanitization

  • config-matchers.tssanitizeDestination() now normalizes both leading slashes AND backslashes. Browsers interpret \ as / in URL contexts, so \/evil.com becomes //evil.com (protocol-relative redirect). The regex ^[\\/]+ collapses any mix of leading slashes and backslashes to a single /.
  • dev-server.ts — Apply the same backslash-aware sanitization to redirect.destination from getServerSideProps/getStaticProps.
  • index.ts — Apply sanitizeDestinationLocal() (same logic) to redirect paths in the Cloudflare Worker entry, ensuring dev/prod parity.

External Rewrite Proxy Hardening (config-matchers.ts)

  • Strip all credential headers — Remove cookie, authorization, x-api-key, and proxy-authorization from requests before forwarding to external rewrite destinations. Previously, all request headers (including session cookies and auth tokens) were forwarded verbatim to third-party hosts.
  • Strip internal headers — Remove all x-middleware-* internal routing headers from proxied requests.
  • Add request timeout — Enforce a 30-second AbortSignal timeout on upstream fetch calls. Returns 504 Gateway Timeout on abort.

Internal x-middleware-* Header Stripping

The x-middleware-* prefix is reserved for internal routing signals (continue, rewrite, override-headers, invoke, request-* unpacking) and must never reach clients. This matches Next.js behavior.

  • prod-server.ts — Strip all x-middleware-* headers from the response after unpacking x-middleware-request-* into the actual request.
  • app-dev-server.ts — Same stripping applied to the App Router path, fixing a dev/prod parity gap where only x-middleware-request-* was previously removed.
  • Test fixtures — Renamed custom middleware headers from x-middleware-test to x-custom-middleware (Pages Router) and x-middleware-ran/x-middleware-pathname to x-mw-ran/x-mw-pathname (App Router) since the prefix is now correctly reserved.

.rsc Suffix Stripped from Middleware NextRequest (app-dev-server.ts)

The .rsc suffix is an internal transport detail for RSC stream requests. Previously, NextRequest.nextUrl.pathname inside middleware would be /about.rsc instead of /about, causing exact-match guards to silently fail. Now the URL is cleaned before constructing the NextRequest.

Draft Mode Cookie Security (headers.ts)

Add Secure flag to the __prerender_bypass draft mode cookie when NODE_ENV === "production". Prevents the cookie from being transmitted over unencrypted HTTP in production.

Safer Command Execution (deploy.ts, cli.ts)

Convert all execSync() calls with string interpolation to execFileSync() with argument arrays. Defense-in-depth against future changes that might introduce user-controlled input.

Security Documentation

  • head.ts — Document SSR/CSR divergence for dangerouslySetInnerHTML and stored XSS risk.
  • fetch-cache.ts — Document AUTH_HEADERS allowlist limitation (only 3 headers keyed; custom auth headers need cache: "no-store").

Tests Added

ALS Per-Request Isolation (tests/nextjs-compat/als-isolation.test.ts)

Four concurrency regression tests verifying AsyncLocalStorage properly isolates per-request state:

  1. Headers — 20 concurrent requests each see only their own x-request-id
  2. Navigation context — 20 concurrent requests each see only their own pathname
  3. Fetch cache scopes — 20 concurrent scopes maintain independent tag state
  4. Cookies — 20 concurrent requests each see only their own session cookie

Other Regression Tests

  • .rsc suffix stripping (tests/app-router.test.ts) — Asserts middleware sees /about, not /about.rsc
  • Draft mode Secure flag (tests/nextjs-compat/draft-mode.test.ts) — Asserts cookie contains ; Secure in production
  • External proxy credential stripping (tests/shims.test.ts) — Asserts cookie, authorization, x-api-key, proxy-authorization, and x-middleware-* are stripped
  • Backslash normalization (tests/shims.test.ts) — Asserts \/evil.com, \\evil.com, etc. are all collapsed to /evil.com

Files Changed

File What
config-matchers.ts Backslash-aware sanitizeDestination + proxy credential/timeout hardening
server/prod-server.ts Strip all x-middleware-* headers from client responses
server/app-dev-server.ts Strip .rsc from middleware URL + strip x-middleware-* from responses
server/dev-server.ts Backslash-aware redirect sanitization from GSP/GSSP
index.ts Backslash-aware sanitizeDestinationLocal for Worker entry
shims/headers.ts Secure flag on draft mode cookie in production
shims/head.ts Security documentation
shims/fetch-cache.ts Auth header cache key documentation
deploy.ts execSyncexecFileSync
cli.ts execSyncexecFileSync
tests/nextjs-compat/als-isolation.test.ts New — ALS concurrency tests (4 tests)
tests/app-router.test.ts .rsc suffix + header rename
tests/nextjs-compat/draft-mode.test.ts Secure flag test
tests/shims.test.ts Proxy stripping + backslash tests
tests/e2e/pages-router/middleware.spec.ts Header rename
tests/e2e/app-router/headers-cookies.spec.ts Header rename
tests/fixtures/app-basic/middleware.ts Header rename
tests/fixtures/pages-basic/middleware.ts Header rename
tests/fixtures/pages-basic/dist/server/entry.js Header rename (pre-built)

Replace execSync calls with execFileSync (pass argv arrays) to avoid shell interpolation and adjust package-install logic to split package manager and args. Sanitize redirect destinations in SSR paths to prevent protocol-relative open-redirects and use sanitizeDestinationLocal for Next.js redirects. Ensure middleware receives a cleaned pathname by stripping internal .rsc suffix before creating NextRequest and add a test for this behavior. Strip sensitive headers (cookie, authorization and x-middleware-*) from proxied external requests and add a test. Document cache key hazards for auth headers in fetch-cache, add a security note for dangerouslySetInnerHTML in head SSR, and set the Draft Mode cookie to include Secure in production (with tests for production/dev behavior).
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Feb 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/vinext@105

commit: 7002bc6

@github-actions
Copy link
Copy Markdown

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

Change middleware header name from x-middleware-test to x-custom-middleware across fixtures and tests, and clarify server comment about stripping x-middleware-* headers from responses (reserved for internal routing signals). Files updated: packages/vinext/src/server/prod-server.ts (comment tweak), tests/fixtures/pages-basic/middleware.ts (header set), and tests/pages-router.test.ts (expectations and test titles/comments). This ensures tests reflect the new public header name while preserving internal x-middleware-* semantics.
Normalize redirect/rewrite destinations by collapsing any leading slashes or backslashes to a single "/" to avoid protocol-relative redirects and handle browser-backslash behavior. Strip additional sensitive headers (x-api-key, proxy-authorization) from proxied requests and ensure all x-middleware-* internal headers are removed from responses (broadened from x-middleware-request-*). Update dev/server sanitization logic accordingly. Update tests and fixture middleware to use shortened x-mw-* header names and add tests covering backslash normalization.
@threepointone threepointone merged commit 0e750f7 into main Feb 26, 2026
24 checks passed
@threepointone threepointone deleted the s-fixes branch February 26, 2026 08:50
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