{"record":{"id":"8cfa264a5d740aca","repo":"koala73/worldmonitor","slug":"serverurl-host-is-not-allowed","errorCode":null,"errorMessage":"serverUrl host is not allowed","messagePattern":"serverUrl host is not allowed","errorType":"exception","errorClass":"McpProxySsrfError","httpStatus":422,"severity":"error","filePath":"api/mcp-proxy.ts","lineNumber":117,"sourceCode":"}\n\n// Generic message surfaced to the caller when a serverUrl resolves to a\n// private/reserved address. The specific blocked IP is deliberately NOT echoed\n// back: returning it turns the proxy into an address oracle (the caller could\n// enumerate internal IPs by observing which hostnames get blocked). SSRF review\n// finding — log the concrete IP server-side for debugging, tell the caller only\n// that the host is disallowed.\nconst SSRF_BLOCKED_PUBLIC_MESSAGE = 'serverUrl host is not allowed';\n\nfunction throwBlockedAddress(blockedAddress) {\n  // Server-side audit/debug log with the concrete blocked address. This is the\n  // only place the resolved internal IP appears; it never reaches the response.\n  console.error('[mcp-proxy]', {\n    event: 'mcp_proxy_ssrf_blocked',\n    ts: new Date().toISOString(),\n    blocked_address: blockedAddress,\n  });\n  throw new McpProxySsrfError(SSRF_BLOCKED_PUBLIC_MESSAGE);\n}\n\nasync function resolveDnsJson(hostname, recordType) {\n  const url = new URL(DNS_JSON_ENDPOINT);\n  url.searchParams.set('name', hostname);\n  url.searchParams.set('type', recordType);\n  const response = await fetch(url.toString(), {\n    headers: {\n      Accept: 'application/dns-json',\n      'User-Agent': 'WorldMonitor-MCP-Proxy/1.0',\n    },\n    signal: AbortSignal.timeout(DNS_RESOLUTION_TIMEOUT_MS),\n  });\n  if (!response.ok) {\n    throw new Error(`DNS ${recordType} lookup failed: HTTP ${response.status}`);\n  }\n  const data = await response.json();\n  if (data?.Status !== 0) {","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp-proxy.ts#L99-L135","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Point serverUrl at a public https MCP server whose DNS resolves to a public IP.","Check the server's DNS for stray private-address records and remove them.","Inspect server-side logs (event mcp_proxy_ssrf_blocked) for the concrete blocked address if you own the deployment.","Do not attempt to bypass — the generic message is intentional; the residual DNS-rebind window is a tracked Edge limitation (issue #5061)."],"exampleFix":"// before\nproxy({ serverUrl: 'https://internal.mcp.local' }) // resolves to 10.0.0.5\n// after\nproxy({ serverUrl: 'https://mcp.example.com' }) // resolves to a public IP","handlingStrategy":"validation","validationCode":"import { isBlockedResolvedAddress } from '../server/_shared/ip-address-classification';\n\nasync function serverUrlResolvesToPublic(url: string): Promise<boolean> {\n  const u = new URL(url);\n  const records = await Promise.all([\n    resolveDnsJson(u.hostname, 'A'), resolveDnsJson(u.hostname, 'AAAA'),\n  ]);\n  const addrs = records.flat();\n  return addrs.length > 0 && !addrs.some(isBlockedResolvedAddress);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await assertServerUrlSafe(new URL(serverUrl));\n} catch (err) {\n  if (err.name === 'McpProxySsrfError' && err.message === 'serverUrl host is not allowed') {\n    return res.status(400).json({ error: 'The MCP server host resolves to a blocked address.' });\n  }\n  throw err;\n}","preventionTips":["Always point the MCP proxy at a public https server whose DNS resolves to a public IP.","Audit your MCP server's DNS for stray private-address records.","Remember the Edge runtime cannot pin the connection — DNS can rebind between validate and fetch (tracked in #5061)."],"tags":["mcp","ssrf","security","proxy","private-ip"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}