{"record":{"id":"523b891fab58a0bd","repo":"ruvnet/ruflo","slug":"forbidden-protocol","errorCode":"FORBIDDEN_PROTOCOL","errorMessage":"protocol ${parsed.protocol} not allowed (only http: and https:)","messagePattern":"protocol (.+?) not allowed \\(only http: and https:\\)","errorType":"validation","errorClass":"HttpFetchValidationError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts","lineNumber":55,"sourceCode":"    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}\n\nfunction isPrivateOrLoopback(host: string): boolean {\n  if (host === 'localhost' || host === 'localhost.localdomain') return true;\n  // IPv6 loopback","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts#L37-L73","documentation":"Thrown by validateUrl when the parsed URL's protocol is neither http: nor https:. The http_fetch tool only permits those two schemes to block file://, ftp:, data:, and other transports that could exfiltrate local files or bypass network policy. The error carries code FORBIDDEN_PROTOCOL.","triggerScenarios":"Any URL whose scheme is not http/https: file:///etc/passwd, ftp://host, data:text/html,..., javascript:, blob:, gopher:, etc. The check reads parsed.protocol after a successful URL parse.","commonSituations":"Pointing http_fetch at a local file via file://; a misconfigured base URL with a trailing colon producing an unexpected scheme; copy-pasted data: URLs; test fixtures using non-http schemes.","solutions":["Use only http:// or https:// URLs with http_fetch.","For local file content, read the file directly with fs instead of routing through http_fetch.","If you genuinely need ftp or another scheme, use a dedicated client — http_fetch will not allow it.","Check that the scheme was not mangled by URL construction (e.g. 'http//example.com' missing the colon)."],"exampleFix":"// before\nhttp_fetch({ url: 'file:///etc/hosts' })\n// after\nreadFileSync('/etc/hosts', 'utf-8')  // use fs for local files","handlingStrategy":"validation","validationCode":"function assertHttpUrl(raw) {\n  const u = new URL(raw);\n  if (u.protocol !== 'http:' && u.protocol !== 'https:') {\n    throw new Error(`protocol ${u.protocol} not allowed`);\n  }\n  return u;\n}","typeGuard":"function isHttpUrl(s: unknown): s is string {\n  if (typeof s !== 'string') return false;\n  try { const u = new URL(s); return u.protocol === 'http:' || u.protocol === 'https:'; }\n  catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Reserve http_fetch for http/https only; use fs for local files.","Watch for scheme-mangling typos like 'http//'.","Validate scheme at the application boundary."],"tags":["http-fetch","security","url","validation","ssrf-protection"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}