openclaw / src /proxy-capture /header-redaction.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
34810d2 verified
Raw History Blame Contribute Delete
2.28 kB
/**
* 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;
}