fix: replace stripRelayHeaders with filterHeaders and add BLOCKED_HEADERS set
This commit is contained in:
@@ -2,7 +2,7 @@ import {
|
|||||||
buildRelayRequest,
|
buildRelayRequest,
|
||||||
createRelayResponse,
|
createRelayResponse,
|
||||||
normalizeTargetUrl,
|
normalizeTargetUrl,
|
||||||
stripRelayHeaders,
|
filterHeaders,
|
||||||
isAllowedTarget,
|
isAllowedTarget,
|
||||||
} from "@/lib/relay-utils";
|
} from "@/lib/relay-utils";
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ async function handler(req: Request): Promise<Response> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = stripRelayHeaders(new Headers(req.headers));
|
const headers = filterHeaders(new Headers(req.headers));
|
||||||
const fetchOptions = buildRelayRequest(req, targetUrl, headers);
|
const fetchOptions = buildRelayRequest(req, targetUrl, headers);
|
||||||
|
|
||||||
const response = await fetch(targetUrl, fetchOptions);
|
const response = await fetch(targetUrl, fetchOptions);
|
||||||
|
|||||||
+80
-31
@@ -1,49 +1,98 @@
|
|||||||
export interface RelayOptions {
|
export const ALLOWED_HEADERS = new Set([
|
||||||
stripHeaders?: string[];
|
"content-type",
|
||||||
}
|
"accept",
|
||||||
|
"accept-encoding",
|
||||||
|
"accept-language",
|
||||||
|
"user-agent",
|
||||||
|
"referer",
|
||||||
|
"origin",
|
||||||
|
"authorization",
|
||||||
|
"proxy-authorization",
|
||||||
|
"cache-control",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const BLOCKED_HEADERS = new Set([
|
||||||
|
"x-vercel-id",
|
||||||
|
"x-vercel-deployment-url",
|
||||||
|
"x-vercel-oidc-token",
|
||||||
|
"x-vercel-oidc-token-ts",
|
||||||
|
"x-vercel-signature",
|
||||||
|
"x-vercel-edgified",
|
||||||
|
"x-vercel-ip-city",
|
||||||
|
"x-vercel-ip-country",
|
||||||
|
"x-vercel-ip-country-region",
|
||||||
|
"x-vercel-ip-latency",
|
||||||
|
"x-vercel-deployment-config",
|
||||||
|
"x-vercel-rewritten-query",
|
||||||
|
"cf-ray",
|
||||||
|
"cf-connecting-ip",
|
||||||
|
"cf-ipcountry",
|
||||||
|
"cf-ray-id",
|
||||||
|
"x-forwarded-for",
|
||||||
|
"x-forwarded-host",
|
||||||
|
"x-forwarded-proto",
|
||||||
|
"forwarded",
|
||||||
|
"cookie",
|
||||||
|
"set-cookie",
|
||||||
|
"x-real-ip",
|
||||||
|
"x-cluster-client-ip",
|
||||||
|
"x-api-key",
|
||||||
|
"x-cache",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const INTERNAL_HEADERS = new Set(["x-relay-target", "x-relay-path", "host"]);
|
||||||
|
|
||||||
export function normalizeTargetUrl(target: string | null, relayPath: string): string | null {
|
export function normalizeTargetUrl(target: string | null, relayPath: string): string | null {
|
||||||
if (!target) return null;
|
if (!target) return null;
|
||||||
return target.replace(/\/$/, "") + relayPath;
|
return target.replace(/\/$/, "") + relayPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stripRelayHeaders(headers: Headers): Headers {
|
export function filterHeaders(headers: Headers): Headers {
|
||||||
const stripped = new Headers(headers);
|
const filtered = new Headers();
|
||||||
stripped.delete("x-relay-target");
|
for (const [key, value] of headers.entries()) {
|
||||||
stripped.delete("x-relay-path");
|
const lowerKey = key.toLowerCase();
|
||||||
stripped.delete("host");
|
if (INTERNAL_HEADERS.has(lowerKey)) continue;
|
||||||
return stripped;
|
if (BLOCKED_HEADERS.has(lowerKey)) continue;
|
||||||
|
if (lowerKey.startsWith("x-vercel-")) continue;
|
||||||
|
if (lowerKey.startsWith("cf-")) continue;
|
||||||
|
if (lowerKey.startsWith("x-forwarded-")) continue;
|
||||||
|
// Forward remaining headers
|
||||||
|
filtered.set(key, value);
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldSendBody(method: string): boolean {
|
export function shouldSendBody(method: string): boolean {
|
||||||
return method !== "GET" && method !== "HEAD";
|
return method !== "GET" && method !== "HEAD";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildRelayRequest(
|
export function buildRelayRequest(
|
||||||
req: Request,
|
req: Request,
|
||||||
targetUrl: string,
|
targetUrl: string,
|
||||||
headers: Headers
|
headers: Headers
|
||||||
): RequestInit {
|
): RequestInit {
|
||||||
return {
|
return {
|
||||||
method: req.method,
|
method: req.method,
|
||||||
headers,
|
headers,
|
||||||
body: shouldSendBody(req.method) ? req.body : undefined,
|
body: shouldSendBody(req.method) ? req.body : undefined,
|
||||||
duplex: "half",
|
duplex: "half",
|
||||||
} as any;
|
} as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAllowedTarget(url: string): boolean {
|
export function isAllowedTarget(url: string): boolean {
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
return ["http:", "https:"].includes(parsed.protocol);
|
return ["http:", "https:"].includes(parsed.protocol);
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRelayResponse(response: Response): Response {
|
export function createRelayResponse(response: Response): Response {
|
||||||
return new Response(response.body, {
|
const headers = new Headers(response.headers);
|
||||||
status: response.status,
|
// Ensure CORS for our test page if needed, but usually we just forward
|
||||||
headers: response.headers,
|
return new Response(response.body, {
|
||||||
});
|
status: response.status,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||||
|
"bunVersion": "1",
|
||||||
|
"installCommand": "bun install"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user