{"record":{"id":"258930628fd28e56","repo":"Budibase/budibase","slug":"url-is-blocked-or-could-not-be-resolved-safely","errorCode":null,"errorMessage":"URL is blocked or could not be resolved safely.","messagePattern":"URL is blocked or could not be resolved safely\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/utils/outboundFetch.ts","lineNumber":43,"sourceCode":"    throw new Error(\"Only HTTP(S) URLs are allowed.\")\n  }\n\n  if (parsed.username || parsed.password) {\n    throw new Error(\"URL must not include credentials.\")\n  }\n\n  return parsed\n}\n\nfunction isRedirect(status: number): boolean {\n  return [301, 302, 303, 307, 308].includes(status)\n}\n\nasync function resolveSafePinnedIp(url: string): Promise<string> {\n  const parsed = parseUrl(url)\n  const addresses = await resolveAddress(parsed.hostname)\n  if (addresses.length === 0) {\n    throw new Error(\"URL is blocked or could not be resolved safely.\")\n  }\n\n  for (const address of addresses) {\n    if (await isBlacklisted(address)) {\n      throw new Error(\"URL is blocked or could not be resolved safely.\")\n    }\n  }\n\n  return addresses[0]\n}\n\n// Always pin to the first resolved IP address to avoid DNS rebinding attacks.\nexport function createPinnedLookup(ip: string): LookupFunction {\n  const family = ip.includes(\":\") ? 6 : 4\n  return (_hostname, _options, callback) => {\n    if (typeof _options === \"object\" && _options?.all) {\n      callback(null, [{ address: ip, family }])\n      return","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/utils/outboundFetch.ts#L25-L61","documentation":"outboundFetch.ts performs SSRF protection before every request: resolveSafePinnedIp parses the URL, resolves the hostname via the blacklist module's DNS resolver, and requires at least one resolved address. This error means DNS resolution returned no addresses for the hostname, so the library cannot pin a safe IP and refuses to fetch.","triggerScenarios":"Calling fetchWithBlacklist (via pinnedIp/resolveSafePinnedIp) with a URL whose hostname fails to resolve: empty resolveAddress() result due to a nonexistent domain, broken DNS in the environment, or a hostname that the internal resolver refuses.","commonSituations":"Typo'd or deleted hostnames in user-supplied webhook/query URLs; containers with no working DNS resolver (/etc/resolv.conf missing or pointing at an unreachable resolver); private hostnames only resolvable via custom DNS the Node process doesn't use.","solutions":["Verify the hostname with `nslookup <host>` or `dig <host>` from the same environment to confirm DNS actually resolves.","Fix the URL (typos, removed domains, trailing dots/scheme issues) at the source where it is configured.","Fix container/environment DNS configuration (resolv.conf, CoreDNS, VPC DNS) so the resolver can return A/AAAA records.","If the domain is genuinely internal, ensure the process runs where that internal DNS is reachable."],"exampleFix":"// before\nawait fetchWithBlacklist(\"https://api.exmaple.com/data\")\n// after (typo corrected, verified resolvable)\nawait fetchWithBlacklist(\"https://api.example.com/data\")","handlingStrategy":"validation","validationCode":"let parsed: URL\ntry { parsed = new URL(url) } catch { throw new Error(\"Invalid URL\") }\nif (!/^https?:$/.test(parsed.protocol)) throw new Error(\"Only HTTP(S)\")\nif (parsed.hostname === \"localhost\") throw new Error(\"Blocked host\")\n// then confirm DNS before fetching:\nconst addrs = await dns.promises.lookup(parsed.hostname, { all: true })\nif (addrs.length === 0) throw new Error(\"Hostname does not resolve\")","typeGuard":"const isHttpUrl = (url: string): url is string => {\n  try {\n    const parsed = new URL(url)\n    return parsed.protocol === \"http:\" || parsed.protocol === \"https:\"\n  } catch {\n    return false\n  }\n}","tryCatchPattern":"try {\n  const res = await fetchWithBlacklist(url)\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"blocked or could not be resolved\")) {\n    // surface a \"hostname unreachable / not allowed\" user-facing message\n  } else throw err\n}","preventionTips":["Validate hostnames resolve (dns.lookup) before storing webhook/query URLs.","Reject localhost/internal hostnames at configuration time.","Fix container DNS before relying on external fetches."],"tags":["ssrf","dns","network","url-validation"],"backgroundTag":"dns-resolution-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}