{"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/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts#L33-L69","documentation":"Thrown by validateUrl in the http_fetch MCP tool when the native URL constructor cannot parse the input string. This is the first validation gate of the secure-by-default fetch pipeline (ADR-164 §5.1.8); it rejects malformed URLs before any protocol or host checks run. The error carries code INVALID_URL.","triggerScenarios":"Passing a URL string that fails `new URL(rawUrl)` — empty string, missing scheme ('example.com'), unencoded spaces, stray characters, or a non-string value coerced to string. The check runs inside the validateUrl helper which the http_fetch handler calls.","commonSituations":"Omitting the https:// scheme; pasting a URL with spaces or angle brackets; building a URL from unencoded user input with query parameters; passing a relative path instead of an absolute URL.","solutions":["Provide an absolute URL with an explicit scheme: 'https://example.com/path'.","URL-encode query parameters with encodeURIComponent before concatenating.","Trim whitespace and surrounding angle brackets from pasted URLs.","Pre-validate with `new URL(url)` in a try/catch before calling http_fetch."],"exampleFix":"// before\nhttp_fetch({ url: 'example.com/api?q=a b' })\n// after\nhttp_fetch({ url: 'https://example.com/api?q=' + encodeURIComponent('a b') })","handlingStrategy":"validation","validationCode":"function safeParseUrl(raw) {\n  try { return new URL(raw); }\n  catch { throw new Error(`invalid URL: ${raw}`); }\n}","typeGuard":"function isValidUrl(s: unknown): s is string {\n  if (typeof s !== 'string') return false;\n  try { new URL(s); return true; } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Always include the scheme (https://).","encodeURI/encodeURIComponent query parameters.","Trim pasted URLs of surrounding whitespace/brackets."],"tags":["http-fetch","validation","url","mcp-tools","input-validation"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}