{"record":{"id":"d0fb50deed6018bc","repo":"Budibase/budibase","slug":"only-http-s-urls-are-allowed-for-query-import","errorCode":null,"errorMessage":"Only HTTP(S) URLs are allowed for query import","messagePattern":"Only HTTP\\(S\\) URLs are allowed for query import","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/query/import/index.ts","lineNumber":83,"sourceCode":"  }\n}\n\nconst stringToHashKey = (input: string) =>\n  crypto.createHash(\"sha512\").update(JSON.stringify(input)).digest(\"hex\")\n\nconst buildCacheKey = (input: ImporterInput) =>\n  `openapiSpecs:${stringToHashKey(JSON.stringify(\"data\" in input ? input.data : input.url))}`\n\nfunction parseImportUrl(url: string): URL {\n  let parsed: URL\n  try {\n    parsed = new URL(url)\n  } catch {\n    throw new HTTPError(\"Invalid import url\", 400)\n  }\n\n  if (!ALLOWED_IMPORT_PROTOCOLS.has(parsed.protocol)) {\n    throw new HTTPError(\"Only HTTP(S) URLs are allowed for query import\", 400)\n  }\n\n  if (parsed.username || parsed.password) {\n    throw new HTTPError(\"Import url must not contain credentials\", 400)\n  }\n\n  return parsed\n}\n\nasync function fetchFromUrl(url: string): Promise<string> {\n  try {\n    // validate protocol / credentials up front for clear 400 errors\n    parseImportUrl(url)\n    // fetchWithBlacklist resolves and validates the target, pins the request to\n    // the validated IP (preventing DNS rebinding between validation and the\n    // actual connection) and safely follows redirects, re-validating each hop.\n    const response = await utils.fetchWithBlacklist(url)\n","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/query/import/index.ts#L65-L101","documentation":"parseImportUrl throws this HTTPError(400) when the URL parses but its protocol is not in ALLOWED_IMPORT_PROTOCOLS (HTTP/HTTPS), blocking schemes like file:, ftp:, data:.","triggerScenarios":"Importing a query source from a URL using ftp://, file://, data:, or other non-HTTP(S) schemes.","commonSituations":"Trying to import local files via file:// URLs; FTP-hosted datasets; data: URIs pasted as source URLs.","solutions":["Use an http:// or https:// URL for the import source","Upload local files directly instead of referencing file:// paths","Move FTP-hosted data behind an HTTP(S) endpoint"],"exampleFix":"// before\nconst url = 'file:///home/user/data.json'\n// after\nconst url = 'https://myapi.example.com/data.json'","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set(['http:', 'https:'])\nconst u = new URL(url)\nif (!ALLOWED.has(u.protocol)) throw new Error('Only http:// or https:// import sources are supported')","typeGuard":"function isHttpUrl(url: string): boolean {\n  try { return ['http:', 'https:'].includes(new URL(url).protocol) } catch { return false }\n}","tryCatchPattern":"try {\n  await importSource(url)\n} catch (err) {\n  if (err instanceof HTTPError && err.message.includes('Only HTTP(S) URLs')) {\n    // reject file://, ftp://, data: sources and ask for an HTTP(S) endpoint\n  }\n}","preventionTips":["Never build import URLs from file paths or data URIs","Upload local files through the app instead of file:// references","Whitelist schemes in your own UI before submission"],"tags":["security","ssrf","validation","import","query"],"backgroundTag":"invalid-url-scheme","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}