From 6a051e959d780aaad28c0b67d46eeb2deb95b793 Mon Sep 17 00:00:00 2001 From: Vivek Date: Wed, 5 Aug 2026 15:44:06 +0530 Subject: [PATCH] docs: state the WEBJS_NO_TRUST_PROXY condition once, at the instruction The deployment page stated the flag's precondition twice in one paragraph, once as "when the container is directly exposed" at the point of instruction and again as "only when nothing trusted sits in front of the container" three sentences later. Say it once, in the wording runtime.md already uses, and let the closing sentences carry only the consequences. The original writeup wanted the opening clause deleted outright on the grounds that the narrow framing was wrong. It is not. forwarded.js says the flag "is the remedy for a directly-exposed container, and it also narrows the forwarded-host exposure above", and runtime.md says setting it on a genuinely proxied deploy is a misconfiguration. So the clause was incomplete rather than wrong, and deleting it would have left the imperative unconditioned at the one place a reader acts on it, for a flag that breaks the legacy CSRF fallback when set on a proxied deploy. What a reader behind a CDN was actually missing is that this flag is not their remedy for a forged X-Forwarded-Host. Cloudflare and Railway forward that header rather than overwriting it, so the exposure is real in their topology, and the fix is to key shared state by the request origin, which is what the HTML response cache does. That sentence is now on the page. --- website/app/docs/deployment/page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/docs/deployment/page.ts b/website/app/docs/deployment/page.ts index d7355880e..ce2851b97 100644 --- a/website/app/docs/deployment/page.ts +++ b/website/app/docs/deployment/page.ts @@ -60,7 +60,7 @@ npm run start -- --port 8080
  • Permissions-Policy: camera=(), microphone=(), geolocation=()
  • Strict-Transport-Security: max-age=63072000; includeSubDomains in production over HTTPS only
  • -

    HSTS is gated to production AND HTTPS. WebJs detects the original scheme from X-Forwarded-Proto (the header the trusted edge proxy forwards after terminating TLS), honoring the same proxy-trust posture as the rest of the framework, so HSTS is never set on a plain-HTTP hop or in dev. Set WEBJS_NO_TRUST_PROXY=1 to stop trusting forwarded headers when the container is directly exposed. It is one flag, read in one place, honored by everything that resolves the request ORIGIN: the URL rewrite, the HSTS scheme check, and the CSRF host resolution alike. It covers client-IP resolution too, so it outranks an explicit rateLimit({ trustProxy: true }) and puts that limiter back on the stamped socket IP. Set it only when nothing trusted sits in front of the container, because on a genuinely proxied deploy it makes the legacy CSRF fallback compare Origin against the internal Host and reject a legitimate cross-host request.

    +

    HSTS is gated to production AND HTTPS. WebJs detects the original scheme from X-Forwarded-Proto (the header the trusted edge proxy forwards after terminating TLS), honoring the same proxy-trust posture as the rest of the framework, so HSTS is never set on a plain-HTTP hop or in dev. Set WEBJS_NO_TRUST_PROXY=1 to stop trusting forwarded headers when nothing trusted sits in front of the container. It is one flag, read in one place, honored by everything that resolves the request ORIGIN: the URL rewrite, the HSTS scheme check, and the CSRF host resolution alike. It covers client-IP resolution too, so it outranks an explicit rateLimit({ trustProxy: true }) and puts that limiter back on the stamped socket IP. On a genuinely proxied deploy setting it is a misconfiguration, and it shows up as one: the legacy CSRF fallback then compares Origin against the internal Host and rejects a legitimate cross-host request. Behind a CDN the flag is not what protects you from a forged X-Forwarded-Host, which Cloudflare and Railway forward rather than overwrite. Anything shared that you derive from the request origin must be keyed by that origin instead, the way the HTML response cache already keys itself.

    A default is set only when the response does not already carry that header, so anything your middleware, a route.{js,ts} handler, or expose sets always wins.

    Per-path overrides

    Declare per-path header rules in package.json under "webjs": { "headers": [...] }, shaped like Next's. The source is a path pattern matched with the native URLPattern API, so :param and :rest* tokens work: