ruvnet/ruflo · error · Error

SSRF guard: only HTTPS URLs are permitted, got

Error message

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

What it means

SONA profiles are kept in an in-memory Map seeded at singleton construction with exactly four built-ins: 'default', 'fast', 'accurate', and 'memory-efficient'. handleProfileGet resolves profileId from input or falls back to state.activeProfileId ('default'), then throws 'Profile <id> not found' when the Map has no such key. Any other ID — typo, custom profile name never created, or a profile from a previous process — fails.

Solutions

  1. Use one of the built-in ids exactly: 'default', 'fast', 'accurate', 'memory-efficient'
  2. List profiles first via the sona profile list tool and pass an id exactly as returned
  3. For custom profiles, create them in the same process before getting them, and re-create after every server restart
  4. Check casing — the Map keys are lowercase; 'Default' with a capital D will not match

Example fix

// before
await client.callTool('sona_profile_get', { profileId: 'Fast' }); // capital F -> undefined in Map -> throws [1132]

// after
await client.callTool('sona_profile_get', { profileId: 'fast' });
Defensive patterns

Strategy: validation

Validate before calling

const BUILT_IN_PROFILES = new Set(['default', 'fast', 'accurate', 'memory-efficient']);
function isKnownProfileId(id: string): boolean {
  return BUILT_IN_PROFILES.has(id); // extend with ids returned by sona_profile_list in this process
}

Type guard

function isBuiltInProfile(id: string): id is 'default' | 'fast' | 'accurate' | 'memory-efficient' {
  return id === 'default' || id === 'fast' || id === 'accurate' || id === 'memory-efficient';
}

Prevention

When it happens

Trigger: sona_profile_get with profileId="fastt" or "FAST" (IDs are case-sensitive lowercase); requesting a custom profile id that was never created via the profile-create tool in this process; referencing a profile by its display name ('Fast') instead of its id ('fast'); after activeProfileId was set to a profile that disappeared on restart.

Common situations: Docs examples using human names rather than ids; scripts hardcoding profile ids that were renamed; assuming profiles persist across restarts like config files (they do not — only the four built-ins are re-seeded).

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/45bc221a2df830e5. Report an issue: GitHub.

Appendix: source

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

    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();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const resp = await fetch(url, {

View on GitHub (pinned to fa13ee4ad6)