koala73/worldmonitor · error · McpProxySsrfError

serverUrl host is not allowed

Error message

serverUrl host is not allowed

What it means

Public-facing message thrown by throwBlockedAddress in api/mcp-proxy.ts when the resolved address of a serverUrl is flagged by isBlockedResolvedAddress (private/reserved ranges). The concrete blocked IP is logged server-side via console.error (event: mcp_proxy_ssrf_blocked) but never echoed to the caller — the caller only sees this generic string so the proxy cannot be used as an internal-address oracle. Thrown as a McpProxySsrfError.

Source

Thrown at api/mcp-proxy.ts:117

}

// Generic message surfaced to the caller when a serverUrl resolves to a
// private/reserved address. The specific blocked IP is deliberately NOT echoed
// back: returning it turns the proxy into an address oracle (the caller could
// enumerate internal IPs by observing which hostnames get blocked). SSRF review
// finding — log the concrete IP server-side for debugging, tell the caller only
// that the host is disallowed.
const SSRF_BLOCKED_PUBLIC_MESSAGE = 'serverUrl host is not allowed';

function throwBlockedAddress(blockedAddress) {
  // Server-side audit/debug log with the concrete blocked address. This is the
  // only place the resolved internal IP appears; it never reaches the response.
  console.error('[mcp-proxy]', {
    event: 'mcp_proxy_ssrf_blocked',
    ts: new Date().toISOString(),
    blocked_address: blockedAddress,
  });
  throw new McpProxySsrfError(SSRF_BLOCKED_PUBLIC_MESSAGE);
}

async function resolveDnsJson(hostname, recordType) {
  const url = new URL(DNS_JSON_ENDPOINT);
  url.searchParams.set('name', hostname);
  url.searchParams.set('type', recordType);
  const response = await fetch(url.toString(), {
    headers: {
      Accept: 'application/dns-json',
      'User-Agent': 'WorldMonitor-MCP-Proxy/1.0',
    },
    signal: AbortSignal.timeout(DNS_RESOLUTION_TIMEOUT_MS),
  });
  if (!response.ok) {
    throw new Error(`DNS ${recordType} lookup failed: HTTP ${response.status}`);
  }
  const data = await response.json();
  if (data?.Status !== 0) {

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Point serverUrl at a public https MCP server whose DNS resolves to a public IP.
  2. Check the server's DNS for stray private-address records and remove them.
  3. Inspect server-side logs (event mcp_proxy_ssrf_blocked) for the concrete blocked address if you own the deployment.
  4. Do not attempt to bypass — the generic message is intentional; the residual DNS-rebind window is a tracked Edge limitation (issue #5061).

Example fix

// before
proxy({ serverUrl: 'https://internal.mcp.local' }) // resolves to 10.0.0.5
// after
proxy({ serverUrl: 'https://mcp.example.com' }) // resolves to a public IP
Defensive patterns

Strategy: validation

Validate before calling

import { isBlockedResolvedAddress } from '../server/_shared/ip-address-classification';

async function serverUrlResolvesToPublic(url: string): Promise<boolean> {
  const u = new URL(url);
  const records = await Promise.all([
    resolveDnsJson(u.hostname, 'A'), resolveDnsJson(u.hostname, 'AAAA'),
  ]);
  const addrs = records.flat();
  return addrs.length > 0 && !addrs.some(isBlockedResolvedAddress);
}

Try / catch

try {
  await assertServerUrlSafe(new URL(serverUrl));
} catch (err) {
  if (err.name === 'McpProxySsrfError' && err.message === 'serverUrl host is not allowed') {
    return res.status(400).json({ error: 'The MCP server host resolves to a blocked address.' });
  }
  throw err;
}

Prevention

When it happens

Trigger: A POST to /api/mcp-proxy whose `serverUrl` resolves (via DoH A/AAAA) to a private/reserved IP, or whose hostname is itself a blocked IP literal. assertServerUrlSafe calls throwBlockedAddress, which throws McpProxySsrfError with this message.

Common situations: A user (or attacker) points the MCP proxy at an internal address to attempt SSRF; a misconfigured MCP server URL that resolves internally; DNS-rebinding where the hostname flips to a private IP between validation and fetch.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/8cfa264a5d740aca. Report an issue: GitHub.