{"record":{"id":"2e1d49b260c67783","repo":"Budibase/budibase","slug":"redirects-are-not-permitted","errorCode":null,"errorMessage":"Redirects are not permitted.","messagePattern":"Redirects are not permitted\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/utils/outboundFetch.ts","lineNumber":224,"sourceCode":"      )\n    } catch (error) {\n      const hostname = parseUrl(nextUrl).hostname\n      if (error instanceof Error) {\n        error.message = `Failed to connect to resolved IP for ${hostname}: ${error.message}`\n        throw error\n      }\n      throw new Error(\n        `Failed to connect to resolved IP for ${hostname}: unknown network error`\n      )\n    }\n    if (!isRedirect(response.status)) {\n      return response\n    }\n\n    releaseResponseBody(response)\n\n    if (!followRedirects) {\n      throw new Error(\"Redirects are not permitted.\")\n    }\n\n    if (redirects === MAX_REDIRECTS) {\n      break\n    }\n\n    const location = response.headers.get(\"location\")\n    if (!location) {\n      if (returnRedirectWithoutLocation) {\n        return response\n      }\n      throw new Error(\"Maximum redirect reached.\")\n    }\n\n    const redirectUrl = parseUrl(\n      new URL(location, nextUrl).toString()\n    ).toString()\n    nextRequest = nextRequestForRedirect(nextRequest, response.status)","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/utils/outboundFetch.ts#L206-L242","documentation":"fetchWithBlacklist performs manual redirect handling. When the server responds with a 3xx status and the caller passed followRedirects: false, the library throws instead of following, because silently following redirects would bypass the caller's security intent.","triggerScenarios":"Calling fetchWithBlacklist with { followRedirects: false } (default is true) while the target URL returns 301/302/303/307/308 — e.g. http→https upgrades, trailing-slash redirects, or auth-driven redirects.","commonSituations":"Fetching an http:// URL that 301-redirects to https:// while redirects are disabled; APIs that redirect to a canonical domain; short-link services that always redirect.","solutions":["Pass { followRedirects: true } (or omit the option) if following redirects is acceptable.","Use the final, non-redirecting URL directly (follow the redirect once manually and store the canonical URL).","Check the response manually by using returnRedirectWithoutLocation/inspect the 3xx status before configuring strict no-follow behavior."],"exampleFix":"// before\nawait fetchWithBlacklist(url, {}, { followRedirects: false })\n// after\nawait fetchWithBlacklist(url, {}, { followRedirects: true })","handlingStrategy":"validation","validationCode":"// Resolve the URL to a non-redirecting endpoint first, or declare redirect intent explicitly\nconst res = await fetchWithBlacklist(url, {}, { followRedirects: false })\nif ([301,302,303,307,308].includes(res.status)) {\n  throw new Error(`Endpoint redirects (${res.status}); use its final URL`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await fetchWithBlacklist(url, req, { followRedirects: false })\n} catch (err) {\n  if (err instanceof Error && err.message === \"Redirects are not permitted.\") {\n    // fall back to a redirect-following call or report a misconfigured endpoint\n    return fetchWithBlacklist(url, req, { followRedirects: true })\n  }\n  throw err\n}","preventionTips":["Only set followRedirects: false when you truly need the raw 3xx response.","Store canonical, non-redirecting URLs for integrations.","Document that the default is followRedirects: true."],"tags":["http","redirect","fetch","configuration"],"backgroundTag":"redirect-not-allowed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}