{"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/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts#L37-L73","documentation":"HttpFetchValidationError with code FORBIDDEN_PROTOCOL, thrown by validateUrl() (http-fetch-tools.ts:55) when the URL parses but its scheme is anything other than http: or https:. The http_fetch tool is an internet fetcher, not a local file or arbitrary-scheme reader, so file://, ftp://, data:, ws:, chrome:, and every other protocol are refused before any connection is attempted.","triggerScenarios":"http_fetch with 'file:///etc/hostname', 'file:///home/user/report.html', 'ftp://legacy-host/pub/data.csv', or 'data:text/html,...'. The protocol comparison is lowercased, so casing is not the issue — only http and https pass.","commonSituations":"Trying to read a local file through the fetch tool; legacy ftp links from old documentation; expecting fetch()-like tolerance (browser fetch also rejects file://, but users try anyway); attempting data: URLs for inline payloads.","solutions":["Serve the resource over http(s) — for local files, run a local HTTP server and fetch http://127.0.0.1:PORT (that then hits the PRIVATE_ADDRESS guard, so set CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 if intended)","Read local files with your own code (fs) instead of http_fetch — it is not a file reader","Replace ftp:// sources with an https mirror"],"exampleFix":"// before\nawait callTool('http_fetch', { url: 'file:///home/me/notes.md' });\n// throws: protocol file: not allowed (FORBIDDEN_PROTOCOL)\n\n// after: read local files outside the fetch tool\nimport { readFile } from 'node:fs/promises';\nconst content = await readFile('/home/me/notes.md', 'utf8');","handlingStrategy":"validation","validationCode":"function isHttpUrl(raw: string): boolean {\n  try {\n    const proto = new URL(raw).protocol.toLowerCase();\n    return proto === 'http:' || proto === 'https:';\n  } catch { return false; }\n}\nif (!isHttpUrl(url)) throw new TypeError('only absolute http/https URLs are fetchable');","typeGuard":null,"tryCatchPattern":"try {\n  await callTool('http_fetch', { url });\n} catch (e) {\n  if (e instanceof HttpFetchValidationError && e.code === 'FORBIDDEN_PROTOCOL' && url.startsWith('file:')) {\n    // fall back to local file reading outside the fetch tool\n    return readFile(fileUrlToPath(url), 'utf8');\n  }\n  throw e;\n}","preventionTips":["Remember http_fetch is an internet fetcher, not a file reader — use fs APIs for local files","Normalize legacy ftp:// sources to https mirrors during data migration"],"tags":["validation","url","http","protocol","security","mcp"],"backgroundTag":"url-scheme-not-allowed","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"}