/** * Network guard for the translation worker. * * The worker is allowed to fetch exactly two kinds of URLs: * 1. the self-hosted model files (GET/HEAD only), and * 2. same-origin ONNX runtime assets (.wasm/.mjs/.js). * * Everything else throws before the request leaves the worker. Message text is * never put into a URL or a request body; it only travels through * `postMessage` between the UI and the worker. */ export interface TranslationGuardOptions { modelOrigin: string; modelPathPrefix: string; appOrigin: string; /** Only the self-hosted ONNX runtime (e.g. `/ort/`) may be fetched app-side. */ appAssetPathPrefix: string; } const APP_ASSET = /\.(?:wasm|mjs|js)$/i; export function createGuardedFetch( realFetch: typeof fetch, options: TranslationGuardOptions, ): typeof fetch { return (async (input: RequestInfo | URL, init?: RequestInit) => { const request = input instanceof Request ? input : null; const raw = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url; const url = new URL(raw, request?.url ?? options.appOrigin); const method = (init?.method ?? request?.method ?? 'GET').toUpperCase(); const read = method === 'GET' || method === 'HEAD'; const isModel = url.origin === options.modelOrigin && url.pathname.startsWith(options.modelPathPrefix) && !url.search && read; const isAppAsset = url.origin === options.appOrigin && url.pathname.startsWith(options.appAssetPathPrefix) && APP_ASSET.test(url.pathname) && read; if (!isModel && !isAppAsset) { throw new Error('blocked network request'); } return realFetch(input as RequestInfo, init); }) as typeof fetch; }