ruvnet/ruflo · error · Error

SSRF guard: invalid URL — ${rawUrl}

Error message

SSRF guard: invalid URL — ${rawUrl}

What it means

First of three sequential checks in assertSafeUrl() (ruflo/src/mcp-bridge/index.js): it runs new URL(rawUrl) inside try/catch and rethrows as this SSRF-guard error when the constructor rejects. This catches strings that are not parseable URLs at all before the protocol/host checks run. The guard exists to prevent server-side request forgery (CWE-918).

Source

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

    }
    return { guidance: groupGuides[topic], topic };
  }

  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();

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Set the base URL env var to a complete 'https://...' string and verify it is defined at startup.
  2. Coerce and validate the URL with new URL() at config-load time so the failure is loud at boot, not at first request.
  3. Default unsafe/empty config to undefined and fail fast with a clear config error instead of passing garbage to assertSafeUrl.
  4. Ensure template strings that build URLs include the scheme and encode path segments.

Example fix

// before
await callCloudFunction(process.env.CF_URL, payload); // CF_URL unset

// after
const base = process.env.CF_URL;
if (!base) throw new Error('CF_URL not configured');
new URL(base); // validate at startup
await callCloudFunction(base, payload);
Defensive patterns

Strategy: validation

Validate before calling

function safeUrl(raw: string): URL | null { try { return new URL(raw); } catch { return null; } }
const u = safeUrl(process.env.CF_URL ?? '');
if (!u) throw new Error('CF_URL is not a valid URL');

Type guard

function isParseableUrl(raw: string): boolean { try { new URL(raw); return true; } catch { return false; } }

Try / catch

try { await callCloudFunction(url, payload); } catch (e) { if (e instanceof Error && e.message.startsWith('SSRF guard: invalid URL')) throw new Error(`Misconfigured backend URL: ${url}`, { cause: e }); throw e; }

Prevention

When it happens

Trigger: Passing a malformed URL to callCloudFunction or any caller of assertSafeUrl: empty string, undefined coerced to 'undefined', 'not a url', 'ftp://', a bare hostname like 'example.com' without a scheme, or a string with illegal characters.

Common situations: An MCP/cloud-function base URL env var is unset (resolves to undefined → 'undefined') or blank; a config typo omits the scheme; a template string concatenates user input that produces an invalid URL; the value was loaded from a YAML/JSON config that quoted it incorrectly.

Related errors


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