{"record":{"id":"7aba912ba532475b","repo":"ruvnet/ruflo","slug":"invalid-url","errorCode":"INVALID_URL","errorMessage":"invalid URL: ${rawUrl}","messagePattern":"invalid URL: (.+?)","errorType":"validation","errorClass":"HttpFetchValidationError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts","lineNumber":51,"sourceCode":"\nexport class HttpFetchValidationError extends Error {\n  constructor(message: string, public readonly code: string) {\n    super(message);\n    this.name = 'HttpFetchValidationError';\n  }\n}\n\n/**\n * Decide whether the URL is permitted under the default secure-by-default\n * allowlist. Block file://, ftp://, RFC-1918 private addresses, loopback,\n * link-local — unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 is set.\n */\nexport function validateUrl(rawUrl: string): URL {\n  let parsed: URL;\n  try {\n    parsed = new URL(rawUrl);\n  } catch {\n    throw new HttpFetchValidationError(`invalid URL: ${rawUrl}`, 'INVALID_URL');\n  }\n  const proto = parsed.protocol.toLowerCase();\n  if (proto !== 'http:' && proto !== 'https:') {\n    throw new HttpFetchValidationError(\n      `protocol ${parsed.protocol} not allowed (only http: and https:)`,\n      'FORBIDDEN_PROTOCOL',\n    );\n  }\n  const host = parsed.hostname.toLowerCase();\n  const allowPrivate = process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE === '1';\n  if (!allowPrivate && isPrivateOrLoopback(host)) {\n    throw new HttpFetchValidationError(\n      `host ${host} is loopback/private/link-local; set CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 to override`,\n      'PRIVATE_ADDRESS',\n    );\n  }\n  return parsed;\n}","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts#L33-L69","documentation":"HttpFetchValidationError with code INVALID_URL, thrown by validateUrl() (http-fetch-tools.ts:51) when `new URL(rawUrl)` throws — i.e. the string is not an absolute, parseable URL. The http_fetch tool only accepts absolute http(s) URLs, so URLs that a browser might auto-correct (missing scheme) are rejected here.","triggerScenarios":"Calling http_fetch with 'example.com/path' (no scheme), 'http//example.com' (missing colon), leading/trailing whitespace, control characters, or unencoded brackets. Only fully qualified absolute URLs parse; relative paths like '/api/v1' also fail.","commonSituations":"Copy-pasting a bare domain from a browser bar; user input where 'https://' was stripped; concatenating a base URL and path with a missing slash; URLs with spaces or unicode not yet encoded.","solutions":["Always include the scheme: 'https://example.com/path'","Trim whitespace and encode the URL before sending: encodeURI(raw.trim())","Build from a base: new URL(path, baseUrl).toString() guarantees a valid absolute URL"],"exampleFix":"// before\nawait callTool('http_fetch', { url: 'example.com/api/data' });\n// throws HttpFetchValidationError: invalid URL (INVALID_URL)\n\n// after\nawait callTool('http_fetch', { url: new URL('/api/data', 'https://example.com').toString() });","handlingStrategy":"validation","validationCode":"function toAbsoluteHttpUrl(raw: string): string {\n  const trimmed = raw.trim();\n  const withScheme = /^https?:\\/\\//i.test(trimmed) ? trimmed : `https://${trimmed}`;\n  return new URL(withScheme).toString(); // throws here = genuinely invalid\n}\nconst url = toAbsoluteHttpUrl(userInput);","typeGuard":"function isParseableUrl(v: string): boolean {\n  try { new URL(v); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  await callTool('http_fetch', { url });\n} catch (e) {\n  if (e instanceof HttpFetchValidationError && e.code === 'INVALID_URL') {\n    return { error: 'bad_request', detail: 'send a fully qualified http(s) URL' };\n  }\n  throw e;\n}","preventionTips":["Always construct URLs with new URL(path, base) rather than string concatenation","Trim and encode user-supplied URLs before passing them to the tool"],"tags":["validation","url","http","mcp"],"backgroundTag":"invalid-url-format","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}