{"id":"691de7d78943990b","repo":"sindresorhus/got","slug":"the-url-option-must-stay-on-the-same-origin-as","errorCode":null,"errorMessage":"The `url` option must stay on the same origin as `prefixUrl` when `allowAbsoluteUrls` is false","messagePattern":"The `url` option must stay on the same origin as `prefixUrl` when `allowAbsoluteUrls` is false","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"source/core/options.ts","lineNumber":1070,"sourceCode":"\nexport const assertUrlHasSameOriginAsPrefixUrlIfNeeded = (options: Options, url: URL): void => {\n\tif (!options.prefixUrl || options.allowAbsoluteUrls) {\n\t\treturn;\n\t}\n\n\tlet prefixUrl: URL;\n\n\ttry {\n\t\tprefixUrl = new URL(options.prefixUrl);\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (isSameOrigin(prefixUrl, url)) {\n\t\treturn;\n\t}\n\n\tthrow new Error('The `url` option must stay on the same origin as `prefixUrl` when `allowAbsoluteUrls` is false');\n};\n\nexport type UrlPrefixBoundary = {\n\turl?: URL;\n\tprefixUrl?: string;\n\tallowAbsoluteUrls?: boolean;\n};\n\nexport const getUrlPrefixBoundary = (options: Options): UrlPrefixBoundary => ({\n\turl: options.url instanceof URL ? new URL(options.url) : undefined,\n\tprefixUrl: options.prefixUrl.toString(),\n\tallowAbsoluteUrls: options.allowAbsoluteUrls,\n});\n\nexport const hasUrlOrPrefixUrlBoundaryChanged = (options: Options, currentUrl: URL, previous: UrlPrefixBoundary): boolean => (\n\tcurrentUrl.href !== previous.url?.href\n\t|| options.prefixUrl.toString() !== previous.prefixUrl\n\t|| options.allowAbsoluteUrls !== previous.allowAbsoluteUrls","sourceCodeStart":1052,"sourceCodeEnd":1088,"githubUrl":"https://github.com/sindresorhus/got/blob/e3924aa1e53a6ca3eb93a43618ce532442a89b40/source/core/options.ts#L1052-L1088","documentation":"Thrown at source/core/options.ts:1070 by `assertUrlHasSameOriginAsPrefixUrlIfNeeded`. Even when the per-call URL is technically relative and resolves under prefixUrl, if the resolved origin differs from the prefixUrl origin AND allowAbsoluteUrls is false, got throws. This catches cross-origin hops that arise from prefixUrl/path resolution (e.g. prefixUrl on `https://a.com` resolving a path that flips origin via redirect or option overrides) — the library treats origin changes as security-relevant because they may leak credentials or headers across trust boundaries.","triggerScenarios":"Calling applyUrlOverride / afterResponse retry with a URL whose origin differs from prefixUrl; prefixUrl set to `https://a.example.com` and a retry/hook switches to `https://b.example.com`; an option merge that changes the resolved URL to a different origin while allowAbsoluteUrls is false.","commonSituations":"Multi-tenant API clients where a redirect moves between subdomains; SSO flows that hop from app origin to IdP origin; retry hooks that switch to a fallback host; misconfigured prefixUrl with a typo'd host.","solutions":["Confirm the intended target origin matches prefixUrl, or set `allowAbsoluteUrls: true` if cross-origin calls are intentional.","In retry/afterResponse hooks that change origin, use a separate got instance configured for that origin instead of mutating options.url.","Verify prefixUrl spelling/protocol/host — a stray `http://` vs `https://` or `www.` vs bare domain will trigger this."],"exampleFix":"// before\nconst client = got.extend({ prefixUrl: 'https://api.example.com' });\nhooks: { afterResponse: [(r, retry) => retry({ url: 'https://api.other.com/path' })] }\n\n// after — separate instance for the other origin\nconst client = got.extend({ prefixUrl: 'https://api.example.com' });\nconst other = got.extend({ prefixUrl: 'https://api.other.com' });\nhooks: { afterResponse: [(r) => r.statusCode === 308 ? other('path') : r] }","handlingStrategy":"validation","validationCode":"function assertSameOrigin(prefixUrl, targetUrl) {\n  if (!prefixUrl) return;\n  const a = new URL(prefixUrl);\n  const b = targetUrl instanceof URL ? targetUrl : new URL(targetUrl, prefixUrl);\n  if (a.origin !== b.origin) {\n    throw new Error(`Cross-origin jump from ${a.origin} to ${b.origin} blocked by allowAbsoluteUrls=false`);\n  }\n}\nassertSameOrigin(options.prefixUrl, options.url);","typeGuard":"function isSameOrigin(a: string | URL, b: string | URL): boolean {\n  const ua = a instanceof URL ? a : new URL(a);\n  const ub = b instanceof URL ? b : new URL(b);\n  return ua.origin === ub.origin;\n}","tryCatchPattern":"try {\n  await client(url, options);\n} catch (error) {\n  if (error instanceof Error && /must stay on the same origin as `prefixUrl`/.test(error.message)) {\n    // route cross-origin calls through a dedicated instance\n    const crossOriginClient = got.extend({ prefixUrl: new URL(url).origin, allowAbsoluteUrls: true });\n    return crossOriginClient(new URL(url).pathname);\n  }\n  throw error;\n}","preventionTips":["Keep each got instance scoped to a single origin; use separate instances for other origins.","In retry/afterResponse hooks that change hosts, build a fresh instance instead of mutating options.url.","Double-check prefixUrl spelling (protocol, host, www. prefix) — a single character causes origin divergence."],"tags":["url","prefix-url","same-origin","security","cors"],"analyzedSha":"e3924aa1e53a6ca3eb93a43618ce532442a89b40","analyzedAt":"2026-08-03T19:22:24.770Z","schemaVersion":2}