{"record":{"id":"e9e802eb9b3230a6","repo":"Budibase/budibase","slug":"import-url-must-not-contain-credentials","errorCode":null,"errorMessage":"Import url must not contain credentials","messagePattern":"Import url must not contain credentials","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/query/import/index.ts","lineNumber":87,"sourceCode":"  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\n    if (!response.ok) {\n      throw new HTTPError(\n        `Failed to fetch import data (status ${response.status})`,\n        response.status","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/query/import/index.ts#L69-L105","documentation":"parseImportUrl throws this HTTPError(400) when the URL embeds user credentials (user:pass@host), which Budibase disallows to prevent credential leakage into stored import sources and SSRF-style misuse.","triggerScenarios":"Import URLs like https://user:password@api.example.com/data.json where basic-auth credentials are embedded in the URL userinfo component.","commonSituations":"Copying URLs from tools that embed basic auth in the host part; API providers documenting credential-in-URL access; secrets pasted into the URL field.","solutions":["Remove the username/password from the URL and use headers for authentication instead","Use API-key headers or a proxy that injects credentials server-side","Configure the datasource auth separately from the import URL"],"exampleFix":"// before\nconst url = 'https://admin:s3cret@api.example.com/data.json'\n// after\nconst url = 'https://api.example.com/data.json' // pass auth via headers","handlingStrategy":"validation","validationCode":"const u = new URL(url)\nif (u.username || u.password) throw new Error('Remove credentials from the URL; supply auth via headers instead')","typeGuard":"function isCredentialFreeUrl(url: string): boolean {\n  try { const u = new URL(url); return !u.username && !u.password } catch { return false }\n}","tryCatchPattern":"try {\n  await importSource(url)\n} catch (err) {\n  if (err instanceof HTTPError && err.message.includes('must not contain credentials')) {\n    // strip userinfo and configure authentication via headers/datasource config\n  }\n}","preventionTips":["Never embed user:pass@ in URLs; use Authorization headers","Sanitize pasted URLs for userinfo before storing","Keep datasource credentials in config, not in the import source URL"],"tags":["security","credentials","validation","import","query"],"backgroundTag":"credentials-in-url","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}