Files
OmniRoute/src/lib/providers/validation/headers.ts
Jillur Rahman 899c40da67 fix(security): SSRF-guard provider validation probes (block cloud metadata) (#6542)
Merged — thank you, @developerjillur! SSRF-guards the provider-validation probes (block-metadata + no redirect) so a caller-controllable baseUrl can't relay to cloud metadata. Integrated into release/v3.8.47.
2026-07-08 01:35:38 -03:00

135 lines
4.7 KiB
TypeScript

// Request-header builders + custom-UA handling + a direct (proxy-bypassing) HTTPS helper, extracted
// from validation.ts (god-file decomposition). Pure header construction except directHttpsRequest,
// which delegates to safeOutboundFetch with bypassProxyPatch. Behavior is byte-identical.
import { safeOutboundFetch } from "@/shared/network/safeOutboundFetch";
import { getProviderValidationGuard } from "@/shared/network/outboundUrlGuard";
// Standardized desktop Chrome UA for web-cookie/no-auth session probes (minimizes anti-bot detection).
export const STANDARD_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36";
export function getCustomUserAgent(providerSpecificData: any = {}) {
if (typeof providerSpecificData?.customUserAgent !== "string") return null;
const customUserAgent = providerSpecificData.customUserAgent.trim();
return customUserAgent || null;
}
export function applyCustomUserAgent(
headers: Record<string, string>,
providerSpecificData: any = {}
) {
const customUserAgent = getCustomUserAgent(providerSpecificData);
if (!customUserAgent) return headers;
headers["User-Agent"] = customUserAgent;
if ("user-agent" in headers) {
headers["user-agent"] = customUserAgent;
}
return headers;
}
export function withCustomUserAgent(init: RequestInit, providerSpecificData: any = {}) {
return {
...init,
headers: applyCustomUserAgent(
{ ...((init.headers as Record<string, string> | undefined) || {}) },
providerSpecificData
),
};
}
/**
* Direct HTTPS request utility that bypasses the global patched fetch.
* Used for provider validation where the patched fetch has compatibility issues.
* Uses safeOutboundFetch with bypassProxyPatch to use native Node.js fetch directly.
*
* SSRF hardening: provider validation hits a caller-controllable `baseUrl`
* (e.g. the web-cookie `${baseUrl}/models` probe), so it MUST NOT be an open
* relay to cloud-metadata endpoints. We apply `getProviderValidationGuard()`
* (local-first default = "block-metadata": allow LAN/localhost providers but
* reject 169.254.169.254 / link-local IMDS; power users can opt fully out via
* `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). `applyUrlGuard` only checks the
* INITIAL url, so `allowRedirect: false` is required too — otherwise a provider
* could 3xx-redirect the probe to metadata past the guard. Validation targets
* are concrete API endpoints (/models, /chat/completions, /v1/messages) that
* return direct JSON, so blocking redirects loses no legitimate provider.
*/
export function directHttpsRequest(
url: string,
options: { method?: string; headers?: Record<string, string>; body?: string },
timeoutMs: number
): Promise<{ status: number; ok: boolean; text: () => Promise<string> }> {
return safeOutboundFetch(url, {
method: options.method || "GET",
headers: (options.headers || {}) as Record<string, string>,
body: options.body,
timeoutMs,
bypassProxyPatch: true,
allowRedirect: false,
guard: getProviderValidationGuard(),
retry: false,
}).then(async (response) => ({
status: response.status,
ok: response.ok,
text: async () => await response.text(),
}));
}
export function buildBearerHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
export function buildRekaHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
headers["X-Api-Key"] = apiKey;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
export function buildClarifaiHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Key ${apiKey}`;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
export function buildKeyHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Key ${apiKey}`;
}
return applyCustomUserAgent(headers, providerSpecificData);
}
export function buildTokenHeaders(apiKey: string, providerSpecificData: any = {}) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (apiKey) {
headers.Authorization = `Token ${apiKey}`;
}
return applyCustomUserAgent(headers, providerSpecificData);
}