/** * User-supplied upstream extra headers: names we never forward (Host / hop-by-hop / framing). * Changing this list requires syncing: `sanitizeUpstreamHeadersMap` (models.ts), Zod * `upstreamHeaderNameSchema` / record refine (schemas.ts), and `upstream-headers-sanitize` tests. */ const FORBIDDEN = new Set( [ "host", "connection", "content-length", "keep-alive", "proxy-connection", "transfer-encoding", "te", "trailer", "upgrade", ].map((s) => s.toLowerCase()) ); export function isForbiddenUpstreamHeaderName(name: string): boolean { return FORBIDDEN.has(String(name).trim().toLowerCase()); } /** * Auth headers that must come from the connection's credentials, never from * operator-set per-provider custom headers. Kept here as the single source of * truth so the Zod `customHeadersSchema` (schemas.ts) and the executor's * apply-loop (open-sse/executors/default.ts) cannot drift apart. */ const FORBIDDEN_AUTH = new Set( ["authorization", "x-api-key", "x-goog-api-key", "api-key"].map((s) => s.toLowerCase()) ); /** * Forbidden for operator-supplied custom headers: the hop-by-hop/framing set * PLUS auth headers (owned by the credential layer). Use this in both the * validation schema and the executor so there is one canonical denylist. */ export function isForbiddenCustomHeaderName(name: string): boolean { const n = String(name).trim().toLowerCase(); return isForbiddenUpstreamHeaderName(n) || FORBIDDEN_AUTH.has(n); }