ruvnet/ruflo · error · Error

SSRF guard: only HTTPS URLs are permitted, got ${parsed.prot

Error message

SSRF guard: only HTTPS URLs are permitted, got ${parsed.protocol}

What it means

Second check in assertSafeUrl(): after a URL parses successfully, the guard requires parsed.protocol === 'https:'. Any other scheme (http:, ftp:, ws:, file:, etc.) is rejected. This is an SSRF/transport-security control ensuring all outbound calls from the MCP bridge use TLS.

Source

Thrown at ruflo/src/mcp-bridge/index.js:657

  return { guidance: `Unknown topic '${topic}'. Use 'overview', 'groups', or a specific group name.`, topic };
}

// =============================================================================
// SSRF GUARD — Reject requests to private/loopback ranges (CWE-918)
// =============================================================================

const PRIVATE_IP_RE = /^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|127\.|0\.|::1|fc|fd)/i;

function assertSafeUrl(rawUrl) {
  let parsed;
  try {
    parsed = new URL(rawUrl);
  } catch {
    throw new Error(`SSRF guard: invalid URL — ${rawUrl}`);
  }
  if (parsed.protocol !== "https:") {
    throw new Error(`SSRF guard: only HTTPS URLs are permitted, got ${parsed.protocol}`);
  }
  const host = parsed.hostname;
  if (PRIVATE_IP_RE.test(host) || host === "localhost" || host.endsWith(".local")) {
    throw new Error(`SSRF guard: private/loopback host rejected — ${host}`);
  }
}

// =============================================================================
// HELPER — Call a backend Cloud Function / API
// =============================================================================

async function callCloudFunction(url, payload, timeoutMs = 25000) {
  // Validate the URL before making any network request.
  assertSafeUrl(url);
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const resp = await fetch(url, {

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Change the base URL to its https:// form.
  2. Terminate TLS at a reverse proxy in front of the local/internal service and point the config at the https endpoint.
  3. Add an allowlisted local-override only in non-production with an explicit, reviewed exception — never disable the guard globally.
  4. Audit env vars and config files for any http:// base URLs.

Example fix

// before
const url = 'http://internal-svc.example.com/func';

// after
const url = 'https://internal-svc.example.com/func';
Defensive patterns

Strategy: validation

Validate before calling

function isHttps(raw: string): boolean { try { return new URL(raw).protocol === 'https:'; } catch { return false; } }
if (!isHttps(url)) throw new Error('backend URL must use https');

Type guard

function isHttpsUrl(raw: string): boolean { try { return new URL(raw).protocol === 'https:'; } catch { return false; } }

Try / catch

try { await callCloudFunction(url, payload); } catch (e) { if (e instanceof Error && /only HTTPS URLs are permitted/.test(e.message)) throw new Error('Backend URL must be https:// — check config', { cause: e }); throw e; }

Prevention

When it happens

Trigger: Passing a URL whose scheme is not https: — 'http://api.example.com', 'ws://service', 'ftp://host/file'. A bare hostname that the URL constructor coerces is caught here too if it resolves to a non-https scheme.

Common situations: Local/dev base URLs left as http://localhost:...; an internal service that only exposes plain HTTP; a config copied from a staging doc that used http; a websocket URL passed where an HTTPS endpoint is expected.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/fdfda431f2904f87. Report an issue: GitHub.