File size: 2,283 Bytes
34810d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /**
* Canonical header redaction for debug proxy captures.
*
* Both capture writers — the patched-fetch runtime and the standalone proxy
* server — must redact identically. A capture that leaks credentials is worse
* than no capture, and the standalone path previously stored raw headers while
* the runtime path redacted, so this policy lives in one leaf module that both
* import rather than being duplicated per writer.
*/
import { isHeadersLike, type HeadersLike } from "../infra/fetch-headers.js";
import { redactRegisteredSecretValues } from "../logging/secret-redaction-registry.js";
import { isLikelySensitiveModelProviderHeaderName } from "../secrets/model-provider-header-policy.js";
export const REDACTED_CAPTURE_HEADER_VALUE = "[REDACTED]";
function isSensitiveCaptureHeaderName(name: string): boolean {
const normalized = name.trim().toLowerCase();
return (
isLikelySensitiveModelProviderHeaderName(normalized) ||
normalized === "cookie" ||
normalized === "set-cookie" ||
normalized.includes("session")
);
}
export function redactedCaptureHeaders(
headers: HeadersLike | Record<string, string | string[] | undefined> | undefined,
additionalSensitiveNames?: Iterable<string>,
): Record<string, string> | undefined {
if (!headers) {
return undefined;
}
const additionalSensitive = new Set(
[...(additionalSensitiveNames ?? [])].map((name) => name.trim().toLowerCase()),
);
const entries = isHeadersLike(headers) ? Array.from(headers.entries()) : Object.entries(headers);
const redacted: Record<string, string> = {};
for (const [name, value] of entries) {
// Header names are matched exactly and by sensitive fragments because
// providers use many token/key naming variants. Names that pass the check
// still run through value redaction so a registered secret pasted into an
// innocuous header does not survive.
if (additionalSensitive.has(name.trim().toLowerCase()) || isSensitiveCaptureHeaderName(name)) {
redacted[name] = REDACTED_CAPTURE_HEADER_VALUE;
continue;
}
const flattened = Array.isArray(value) ? value.join(", ") : (value ?? "");
redacted[name] = redactRegisteredSecretValues(flattened, () => REDACTED_CAPTURE_HEADER_VALUE);
}
return redacted;
}
|