{"record":{"id":"5772d59836e5bc4b","repo":"different-ai/openwork","slug":"a-request-body-cannot-be-redirected-to-another-ori","errorCode":null,"errorMessage":"a request body cannot be redirected to another origin","messagePattern":"a request body cannot be redirected to another origin","errorType":"exception","errorClass":"LocalManagedMcpPrivateUrlError","httpStatus":null,"severity":"error","filePath":"apps/server/src/local-managed-mcp-url-guard.ts","lineNumber":213,"sourceCode":"    lookup: createLocalManagedMcpPublicLookup(),\n    // Node's 250 ms family-attempt default is too aggressive for otherwise\n    // healthy dual-stack MCP providers on some macOS networks. Keep fallback\n    // enabled, but give the first family enough time to establish TLS before\n    // trying the validated alternative address.\n    autoSelectFamily: true,\n    autoSelectFamilyAttemptTimeout: 1_000,\n  },\n});\n\nfunction redirectedRequestInit(init: RequestInit | undefined, status: number, from: URL, to: URL): RequestInit {\n  const headers = new Headers(init?.headers);\n  if (from.protocol === \"https:\" && to.protocol !== \"https:\") {\n    throw new LocalManagedMcpPrivateUrlError(to.toString(), \"an HTTPS request cannot redirect to a less secure protocol\");\n  }\n  const method = (init?.method ?? \"GET\").toUpperCase();\n  if (from.origin !== to.origin) {\n    if ((method !== \"GET\" && method !== \"HEAD\") || init?.body != null) {\n      throw new LocalManagedMcpPrivateUrlError(to.toString(), \"a request body cannot be redirected to another origin\");\n    }\n    for (const name of [\"authorization\", \"cookie\", \"proxy-authorization\", \"mcp-session-id\", \"last-event-id\", \"x-api-key\", \"x-auth-token\"]) {\n      headers.delete(name);\n    }\n  }\n  const switchToGet = (status === 303 && method !== \"HEAD\") || ((status === 301 || status === 302) && method === \"POST\");\n  if (switchToGet) {\n    headers.delete(\"content-length\");\n    headers.delete(\"content-type\");\n    return { ...init, method: \"GET\", body: undefined, headers, redirect: \"manual\" };\n  }\n  return { ...init, headers, redirect: \"manual\" };\n}\n\nasync function guardedTransportFetch(input: string | URL, init?: RequestInit): Promise<Response> {\n  // DOM, Bun, and Undici publish structurally equivalent Fetch API types from\n  // different declarations. Keep the conversion isolated at this transport boundary.\n  const requestInit = { ...init, dispatcher: guardedDispatcher } as unknown as UndiciRequestInit;","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/local-managed-mcp-url-guard.ts#L195-L231","documentation":"The redirect handler strips sensitive headers and forbids forwarding request bodies when a redirect crosses origins. A non-GET/HEAD method or any body on a cross-origin redirect throws LocalManagedMcpPrivateUrlError, because credentials or payloads would leak to the new origin.","triggerScenarios":"A guarded fetch (via createLocalManagedMcpConnection) gets a 301/302/303/307/308 whose Location is on a different origin while the request has a body or uses POST/PUT/etc.; redirectedRequestInit throws for the new URL.","commonSituations":"MCP server moved to a new domain and issued a redirect on a POST; trailing-slash or path rewrites crossing subdomains (app.example.com -> api.example.com); auth-gated endpoints redirecting POSTs to a login host.","solutions":["Update the configured MCP URL to the final destination so no cross-origin redirect happens","Change the server redirect to same-origin (or fix the proxy rewrite) if the move is unintentional","Split the call: make the initial request, then issue the follow-up request to the new origin explicitly with fresh auth","Use 307/308 with a same-origin target if the method/body must be preserved"],"exampleFix":"// before\nawait createLocalManagedMcpConnection({ url: \"https://old.example.com/mcp\" }); // 302 -> https://new.example.net/mcp with POST body\n// after\nawait createLocalManagedMcpConnection({ url: \"https://new.example.net/mcp\" });","handlingStrategy":"try-catch","validationCode":"const res = await fetch(url, { method: \"POST\", body, redirect: \"manual\" });\nif ([301,302,303,307,308].includes(res.status)) {\n  const next = new URL(res.headers.get(\"location\"), url);\n  if (next.origin !== new URL(url).origin) throw new Error(`Cross-origin redirect to ${next.origin}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await guardedFetch(url, { method: \"POST\", body });\n} catch (error) {\n  if (error instanceof LocalManagedMcpPrivateUrlError && error.message.includes(\"cannot be redirected to another origin\")) {\n    // update config to the final origin, or re-issue the request explicitly there\n  }\n  throw error;\n}","preventionTips":["Configure the final, canonical MCP URL so no cross-origin redirects occur","Keep redirects same-origin (trailing-slash rules included)","Never rely on redirects to carry authenticated POST requests to new domains","Strip/re-establish auth headers manually when an endpoint genuinely moves origins"],"tags":["security","redirect","http","cross-origin"],"backgroundTag":"cross-origin-redirect-body-blocked","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}