fix: normalize debug proxy fetch headers

This commit is contained in:
Shakker
2026-05-06 15:30:59 +01:00
parent 79f21a4442
commit 96f80fa3ff
2 changed files with 44 additions and 5 deletions

View File

@@ -78,6 +78,42 @@ describe("debug proxy runtime", () => {
expect(sessionEvents.some((event) => event.kind === "response")).toBe(true);
});
it("normalizes symbol-bearing request headers before calling patched fetch targets", async () => {
fetchTarget.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
const headers = new Headers(init?.headers);
expect(headers.get("content-type")).toBe("application/json");
expect(headers.get("x-hidden")).toBe("yes");
return new Response("{}", { status: 200 });
};
const headers = { "content-type": "application/json" } as Record<string, string> & {
[key: symbol]: unknown;
};
Object.defineProperty(headers, "x-hidden", {
value: "yes",
enumerable: false,
});
Object.defineProperty(headers, Symbol("sensitiveHeaders"), {
value: new Set(["content-type"]),
enumerable: false,
});
initializeDebugProxyCapture("test", settings, deps);
await fetchTarget.fetch("https://api.example.com/messages", {
method: "POST",
headers,
body: "{}",
});
await new Promise((resolve) => setImmediate(resolve));
finalizeDebugProxyCapture(settings, deps);
const request = events.find((event) => event.kind === "request");
expect(JSON.parse(String(request?.headersJson))).toMatchObject({
"content-type": "application/json",
"x-hidden": "yes",
});
expect(Object.getOwnPropertySymbols(headers)).toHaveLength(1);
});
it("redacts sensitive request and response headers before persistence", async () => {
initializeDebugProxyCapture("test", settings, deps);
captureHttpExchange(

View File

@@ -1,5 +1,6 @@
import { randomUUID } from "node:crypto";
import { URL } from "node:url";
import { normalizeRequestInitHeadersForFetch } from "../infra/fetch-headers.js";
import { resolveDebugProxySettings, type DebugProxySettings } from "./env.js";
import {
closeDebugProxyCaptureStore,
@@ -174,8 +175,9 @@ function installDebugProxyGlobalFetchPatch(
fetchTarget[DEBUG_PROXY_FETCH_PATCH_KEY] = { originalFetch };
fetchTarget.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = resolveUrlString(input);
const normalizedInit = normalizeRequestInitHeadersForFetch(init);
try {
const response = await originalFetch(input, init);
const response = await originalFetch(input, normalizedInit);
if (url && /^https?:/i.test(url)) {
captureHttpExchange(
{
@@ -184,17 +186,18 @@ function installDebugProxyGlobalFetchPatch(
(typeof Request !== "undefined" && input instanceof Request
? input.method
: undefined) ??
init?.method ??
normalizedInit?.method ??
"GET",
requestHeaders:
(typeof Request !== "undefined" && input instanceof Request
? input.headers
: undefined) ?? (init?.headers as Headers | Record<string, string> | undefined),
: undefined) ??
(normalizedInit?.headers as Headers | Record<string, string> | undefined),
requestBody:
(typeof Request !== "undefined" && input instanceof Request
? (input as Request & { body?: BodyInit | null }).body
: undefined) ??
(init as (RequestInit & { body?: BodyInit | null }) | undefined)?.body ??
(normalizedInit as (RequestInit & { body?: BodyInit | null }) | undefined)?.body ??
null,
response,
transport: "http",
@@ -225,7 +228,7 @@ function installDebugProxyGlobalFetchPatch(
(typeof Request !== "undefined" && input instanceof Request
? input.method
: undefined) ??
init?.method ??
normalizedInit?.method ??
"GET",
host: parsed.host,
path: `${parsed.pathname}${parsed.search}`,