UNPKG

@ai-sdk/provider-utils

Version:
87 lines (82 loc) • 3.2 kB
import { fetchWithValidatedRedirects } from './fetch-with-validated-redirects'; import { isSameOrigin } from './is-same-origin'; import { sanitizeRequestHeaders } from './sanitize-request-headers'; // Providers can use arbitrary credential header names. Only established // non-credential request metadata is safe to forward without an origin assertion. const SAFE_UNTRUSTED_FIRST_HOP_HEADERS = new Set([ 'accept', 'accept-language', 'baggage', 'cache-control', 'idempotency-key', 'if-match', 'if-modified-since', 'if-none-match', 'if-range', 'if-unmodified-since', 'pragma', 'range', 'traceparent', 'tracestate', 'user-agent', 'x-correlation-id', 'x-request-id', ]); /** * Fetches an untrusted URL with first-hop credential isolation and validated * redirects. Uses the URL validation, runtime fetch transport, redirect * limits, and cross-origin header stripping of {@link fetchWithValidatedRedirects}. * Inject guarded-fetch on Node.js for connect-time DNS validation. * * Without a matching `credentialedOrigin` (or `trustedOrigin` when it is * omitted), only allowlisted request metadata is sent on the first hop. * Arbitrary caller headers require an explicit matching origin because vendor * credential names cannot be inferred safely. Proxy, metadata, cookie, and * hop-by-hop headers are sanitized even for a matching origin. * * `trustedOrigin` also exempts same-origin hops from URL validation, allowing * developer-configured private endpoints. Both origin options must come from * developer configuration, never from untrusted response data. * * This is an opt-in alternative to `fetchWithValidatedRedirects`, whose * existing first-hop header behavior is preserved for compatibility. */ export async function fetchUntrustedUrl({ headers, credentialedOrigin, untrustedFirstHopHeaders, ...options }: Parameters<typeof fetchWithValidatedRedirects>[0] & { /** * The developer-configured origin allowed to receive arbitrary caller * headers on the first hop. Defaults to `trustedOrigin` when omitted. * An explicit value takes precedence over `trustedOrigin` and does not * exempt the URL from validation. */ credentialedOrigin?: string; /** * Additional sanitized header names safe to disclose to an untrusted first * hop. Use only for non-credential protocol metadata. Credentials require * a matching `credentialedOrigin` instead. Names are case-insensitive. */ untrustedFirstHopHeaders?: readonly string[]; }): Promise<Response> { let firstHopHeaders: Headers | undefined; if (headers !== undefined) { firstHopHeaders = sanitizeRequestHeaders(headers); const origin = credentialedOrigin ?? options.trustedOrigin; if (origin === undefined || !isSameOrigin(options.url, origin)) { const allowedHeaders = new Set([ ...SAFE_UNTRUSTED_FIRST_HOP_HEADERS, ...(untrustedFirstHopHeaders ?? []).map(name => name.toLowerCase()), ]); firstHopHeaders = new Headers( [...firstHopHeaders].filter(([name]) => allowedHeaders.has(name)), ); } } return fetchWithValidatedRedirects({ ...options, headers: firstHopHeaders, }); }