ruvnet/ruflo · error · Error

SSRF guard: invalid URL — ${rawUrl}

Error message

SSRF guard: invalid URL — ${rawUrl}

What it means

Identical SSRF guard to error 4, duplicated in ruflo/src/ruvocal/mcp-bridge/index.js:743. assertSafeUrl() runs new URL(rawUrl) in try/catch and rethrows when the constructor rejects, catching non-parseable URL strings before scheme/host checks. Both copies exist because the ruvocal (chat UI) package vendors its own mcp-bridge rather than importing the shared one.

Source

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

    };
  } catch (err) {
    if (err.name === "AbortError" || err.name === "TimeoutError") return { error: "Search timed out" };
    return { error: err.message };
  }
}

// =============================================================================
// 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 relevant ruvocal env var (e.g. OPENAI_BASE_URL or MCP backend URL) to a full 'https://...' value.
  2. Validate the URL with new URL() at app startup so misconfig fails loud at boot.
  3. When building URLs from user input, construct via URL/URLSearchParams and check .origin before passing to callCloudFunction.
  4. Keep the two mcp-bridge copies in sync if you patch the guard — the duplication means a fix in one file does not protect the other.

Example fix

// before
const target = process.env.RUVOCAL_CF_URL; // undefined
await callCloudFunction(target, payload);

// after
const target = process.env.RUVOCAL_CF_URL;
if (!target) throw new Error('RUVOCAL_CF_URL missing');
new URL(target);
await callCloudFunction(target, 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.RUVOCAL_CF_URL ?? '');
if (!u) throw new Error('RUVOCAL_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(`Ruvocal backend URL invalid: ${url}`, { cause: e }); throw e; }

Prevention

When it happens

Trigger: A chat-UI/MCP code path calls its local assertSafeUrl/callCloudFunction with a malformed URL: empty string, undefined, 'undefined', a bare hostname without scheme, or a string with illegal characters.

Common situations: A ruvocal MCP_SERVERS or backend base-url env var unset or blank; a template-built URL that drops the scheme; config loaded from .env.local with a quoting error; user-supplied tool endpoint that is not a valid URL.

Related errors


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