feat(bun): add getOrigin option for reverse proxy support
Some checks failed
Qwik CI / Build optimizer x86_64-unknown-linux-gnu (push) Has been cancelled
Qwik CI / Build optimizer x86_64-apple-darwin (push) Has been cancelled
Qwik CI / Build optimizer aarch64-apple-darwin (push) Has been cancelled
Qwik CI / Setup (push) Has been cancelled
Qwik CI / Build Qwik (push) Has been cancelled
Qwik CI / Build optimizer x86_64-pc-windows-msvc (push) Has been cancelled
Qwik CI / Bundle Qwik (push) Has been cancelled
Qwik CI / Build Other Packages (push) Has been cancelled
Qwik CI / Build Insights (push) Has been cancelled
Qwik CI / Build Docs (push) Has been cancelled
Qwik CI / Unit Tests (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:chromium host:ubuntu-latest]) (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:chromium host:windows-latest]) (push) Has been cancelled
Qwik CI / E2E Tests (map[browser:webkit host:macos-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:macos-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:ubuntu-latest]) (push) Has been cancelled
Qwik CI / E2E CLI Tests (map[host:windows-latest]) (push) Has been cancelled
Qwik CI / Lint Package (push) Has been cancelled
Qwik CI / Release (push) Has been cancelled
Qwik CI / Trigger Qwik City E2E (push) Has been cancelled
Qwik CI / All requirements are met (push) Has been cancelled
Closing Issues For Inactivity / close-issues (push) Has been cancelled

The Bun adapter now supports computing the correct origin when behind
a reverse proxy (Caddy, nginx, etc). This fixes CSRF validation failures
where the browser sends Origin: https://example.com but Bun sees
http://localhost:4000.

Adds:
- getOrigin option in QwikCityBunOptions
- ORIGIN env var support
- PROTOCOL_HEADER/HOST_HEADER env vars for reading proxy headers
- normalizeUrl() for CSRF protection against relative protocol URLs

Matches the existing Node adapter implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamps
2026-01-08 12:18:56 +11:00
parent 508a689b04
commit dfd76b3b4f

View File

@@ -16,6 +16,34 @@ import { setServerPlatform } from '@builder.io/qwik/server';
import { MIME_TYPES } from '../request-handler/mime-types';
import { join, extname } from 'node:path';
function computeOrigin(request: Request, opts?: QwikCityBunOptions): string {
return opts?.getOrigin?.(request) ?? Bun.env.ORIGIN ?? fallbackOrigin(request);
}
function fallbackOrigin(request: Request): string {
const headers = request.headers;
const { PROTOCOL_HEADER, HOST_HEADER } = Bun.env;
const protocol = (PROTOCOL_HEADER && headers.get(PROTOCOL_HEADER)) || 'http';
const host = (HOST_HEADER && headers.get(HOST_HEADER)) || headers.get('host');
return `${protocol}://${host}`;
}
// do not allow the url to have a relative protocol url
// which could bypass of CSRF protections
// for example: new URL("//attacker.com", "https://qwik.build.io")
// would return "https://attacker.com" when it should be "https://qwik.build.io/attacker.com"
function normalizeUrl(url: string, base: string): URL {
const DOUBLE_SLASH_REG = /\/\/|\\\\/g;
return new URL(url.replace(DOUBLE_SLASH_REG, '/'), base);
}
function getUrl(request: Request, origin: string): URL {
const urlObj = new URL(request.url);
return normalizeUrl(urlObj.pathname + urlObj.search, origin);
}
/** @public */
export function createQwikCity(opts: QwikCityBunOptions) {
// @builder.io/qwik-city/middleware/bun
@@ -36,7 +64,8 @@ export function createQwikCity(opts: QwikCityBunOptions) {
async function router(request: Request) {
try {
const url = new URL(request.url);
const origin = computeOrigin(request, opts);
const url = getUrl(request, origin);
const serverRequestEv: ServerRequestEvent<Response> = {
mode: 'server',
@@ -100,7 +129,8 @@ export function createQwikCity(opts: QwikCityBunOptions) {
const notFound = async (request: Request) => {
try {
const url = new URL(request.url);
const origin = computeOrigin(request, opts);
const url = getUrl(request, origin);
// In the development server, we replace the getNotFound function
// For static paths, we assign a static "Not Found" message.
@@ -140,7 +170,8 @@ export function createQwikCity(opts: QwikCityBunOptions) {
const staticFile = async (request: Request) => {
try {
const url = new URL(request.url);
const origin = computeOrigin(request, opts);
const url = getUrl(request, origin);
if (isStaticPath(request.method || 'GET', url)) {
const { filePath, content } = await openStaticFile(url);
@@ -190,5 +221,19 @@ export interface QwikCityBunOptions extends ServerRenderOptions {
/** Set the Cache-Control header for all static files */
cacheControl?: string;
};
/**
* Provide a function that computes the origin of the server, used to resolve relative URLs and
* validate the request origin against CSRF attacks.
*
* When not specified, it defaults to the `ORIGIN` environment variable (if set).
*
* If `ORIGIN` is not set, it's derived from the incoming request, which is not recommended for
* production use. You can specify the `PROTOCOL_HEADER`, `HOST_HEADER` to `X-Forwarded-Proto` and
* `X-Forwarded-Host` respectively to override the default behavior.
*/
getOrigin?: (request: Request) => string | null;
/** Provide a function that returns a `ClientConn` for the given request. */
getClientConn?: (request: Request) => ClientConn;
}