{"record":{"id":"45bc221a2df830e5","repo":"ruvnet/ruflo","slug":"ssrf-guard-only-https-urls-are-permitted-got-p-45bc22","errorCode":null,"errorMessage":"SSRF guard: only HTTPS URLs are permitted, got ${parsed.protocol}","messagePattern":"SSRF guard: only HTTPS URLs are permitted, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/mcp-bridge/index.js","lineNumber":746,"sourceCode":"    return { error: err.message };\n  }\n}\n\n// =============================================================================\n// SSRF GUARD — Reject requests to private/loopback ranges (CWE-918)\n// =============================================================================\n\nconst PRIVATE_IP_RE = /^(?:10\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.|192\\.168\\.|127\\.|0\\.|::1|fc|fd)/i;\n\nfunction assertSafeUrl(rawUrl) {\n  let parsed;\n  try {\n    parsed = new URL(rawUrl);\n  } catch {\n    throw new Error(`SSRF guard: invalid URL — ${rawUrl}`);\n  }\n  if (parsed.protocol !== \"https:\") {\n    throw new Error(`SSRF guard: only HTTPS URLs are permitted, got ${parsed.protocol}`);\n  }\n  const host = parsed.hostname;\n  if (PRIVATE_IP_RE.test(host) || host === \"localhost\" || host.endsWith(\".local\")) {\n    throw new Error(`SSRF guard: private/loopback host rejected — ${host}`);\n  }\n}\n\n// =============================================================================\n// HELPER — Call a backend Cloud Function / API\n// =============================================================================\n\nasync function callCloudFunction(url, payload, timeoutMs = 25000) {\n  // Validate the URL before making any network request.\n  assertSafeUrl(url);\n  const controller = new AbortController();\n  const timer = setTimeout(() => controller.abort(), timeoutMs);\n  try {\n    const resp = await fetch(url, {","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/ruflo/src/ruvocal/mcp-bridge/index.js#L728-L764","documentation":"Identical HTTPS-only check to error 5, in ruflo/src/ruvocal/mcp-bridge/index.js:746. Once a URL parses, assertSafeUrl requires parsed.protocol === 'https:' and rejects every other scheme. Enforces TLS on all outbound calls from the chat-UI MCP bridge.","triggerScenarios":"A ruvocal callCloudFunction/assertSafeUrl caller passes a URL with a non-https scheme: 'http://...', 'ws://...', 'ftp://...'.","commonSituations":"A dev/staging OPENAI_BASE_URL or MCP endpoint left as http://; an internal inference service exposed only over plain HTTP; a websocket URL used where HTTPS is required; config copied from docs that used http.","solutions":["Switch the configured endpoint to https://.","Put TLS-terminating reverse proxy in front of the HTTP-only service.","Grep ruvocal .env files and config for 'http://' base URLs and convert them.","Do not weaken the guard — exempt only reviewed local profiles."],"exampleFix":"// before\nOPENAI_BASE_URL=http://router.internal/v1\n\n// after\nOPENAI_BASE_URL=https://router.internal/v1","handlingStrategy":"validation","validationCode":"function isHttps(raw: string): boolean { try { return new URL(raw).protocol === 'https:'; } catch { return false; } }\nif (!isHttps(url)) throw new Error('ruvocal endpoint must be https');","typeGuard":"function isHttpsUrl(raw: string): boolean { try { return new URL(raw).protocol === 'https:'; } catch { return false; } }","tryCatchPattern":"try { await callCloudFunction(url, payload); } catch (e) { if (e instanceof Error && /only HTTPS URLs are permitted/.test(e.message)) throw new Error('Switch ruvocal endpoint to https', { cause: e }); throw e; }","preventionTips":["Audit ruvocal .env for http:// base URLs.","Use https:// for OPENAI_BASE_URL and MCP endpoints.","Apply TLS-terminating proxy for HTTP-only internal services."],"tags":["security","ssrf","protocol","tls","ruvocal"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}