{"record":{"id":"df1557b30c44bc1d","repo":"Mintplex-Labs/anything-llm","slug":"not-a-valid-url","errorCode":null,"errorMessage":"Not a valid URL.","messagePattern":"Not a valid URL\\.","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"warning","filePath":"collector/extensions/index.js","lineNumber":131,"sourceCode":"          data: {\n            title: null,\n            author: null,\n          },\n        });\n      }\n      return;\n    }\n  );\n\n  app.post(\n    \"/ext/website-depth\",\n    [verifyPayloadIntegrity],\n    async function (request, response) {\n      try {\n        const websiteDepth = require(\"../utils/extensions/WebsiteDepth\");\n        const { url, depth = 1, maxLinks = 20 } = reqBody(request);\n        const validatedUrl = validateURL(url);\n        if (!validURL(validatedUrl)) throw new Error(\"Not a valid URL.\");\n        const scrapedData = await websiteDepth(validatedUrl, depth, maxLinks);\n        response.status(200).json({ success: true, data: scrapedData });\n      } catch (e) {\n        console.error(e);\n        response.status(400).json({ success: false, reason: e.message });\n      }\n      return;\n    }\n  );\n\n  app.post(\n    \"/ext/confluence\",\n    [verifyPayloadIntegrity, setDataSigner],\n    async function (request, response) {\n      try {\n        const { loadConfluence } = require(\"../utils/extensions/Confluence\");\n        const { success, reason, data } = await loadConfluence(\n          reqBody(request),","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/526360e320da9d1b36074be5ed64fe76e5bbfbbd/collector/extensions/index.js#L113-L149","documentation":"Thrown by the /ext/website-depth route in collector/extensions/index.js:131 after running the user-supplied URL through validateURL() then validURL(). validURL returns false when the URL cannot be parsed by the URL constructor, its protocol is not http: or https:, or its hostname is a private/loopback-style IP octet (10/127/169/172/192) unless COLLECTOR_ALLOW_ANY_IP is set. The handler answers HTTP 400 with success:false.","triggerScenarios":"POST /ext/website-depth where `url` is empty, not a string, has a non-http protocol (ftp://, file://), is malformed (\"example com\"), or points at a private IP like 10.0.0.5 while COLLECTOR_ALLOW_ANY_IP is not enabled.","commonSituations":"User pastes a bare domain without protocol and validateURL's URL() constructor still rejects it; pointing the depth crawler at an intranet/private-LAN address; passing a URL with embedded spaces or unicode that the URL parser throws on; sending `url` as null/undefined from the UI.","solutions":["Send a fully-qualified http(s) URL, e.g. \"https://example.com\".","If you must crawl a private/LAN IP, start the collector with COLLECTOR_ALLOW_ANY_IP=true (intentional admin opt-in).","Trim/encode the URL on the client before sending.","Confirm the field name is exactly `url` in the JSON body."],"exampleFix":"// before\nfetch('/ext/website-depth', { method: 'POST', body: JSON.stringify({ url: '10.0.0.5:8080/docs' }) });\n\n// after\nfetch('/ext/website-depth', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ url: 'http://10.0.0.5:8080/docs', depth: 2 }),\n});\n// and on the collector host: COLLECTOR_ALLOW_ANY_IP=true","handlingStrategy":"validation","validationCode":"const { validURL, validateURL } = require(\"./utils/url\");\nfunction safeWebsiteDepthInput(url) {\n  const validated = validateURL(url);\n  if (!validURL(validated)) {\n    return { ok: false, reason: `Not a valid URL: ${url}` };\n  }\n  return { ok: true, url: validated };\n}\nconst check = safeWebsiteDepthInput(input.url);\nif (!check.ok) return respondBadRequest(check.reason);","typeGuard":"/** @param {unknown} u */\nfunction isHttpUrlString(u) {\n  if (typeof u !== \"string\" || u.length === 0) return false;\n  try {\n    const parsed = new URL(u.includes(\"://\") ? u : `https://${u}`);\n    return [\"http:\", \"https:\"].includes(parsed.protocol);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const res = await fetch('/ext/website-depth', { method:'POST', body: JSON.stringify({ url, depth, maxLinks }) });\n  if (!res.ok) {\n    const { reason } = await res.json();\n    throw new Error(`website-depth failed (${res.status}): ${reason}`);\n  }\n  return await res.json();\n} catch (e) {\n  // distinguish validation (400) from network\n  if (/Not a valid URL/.test(e.message)) surfaceUserError(e.message);\n  throw e;\n}","preventionTips":["Always normalize the URL with validateURL before sending.","For private/LAN targets, set COLLECTOR_ALLOW_ANY_IP intentionally and document why.","Never send a bare string with spaces or no protocol — validateURL tries to fix it but validURL can still reject private IPs."],"tags":["url-validation","ssrf-guard","website-depth","request-payload"],"backgroundTag":null,"analyzedSha":"526360e320da9d1b36074be5ed64fe76e5bbfbbd","analyzedAt":"2026-08-13T01:45:47.170Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}