{"record":{"id":"50e535f7fa1db266","repo":"koala73/worldmonitor","slug":"serverurl-hostname-is-blocked","errorCode":null,"errorMessage":"serverUrl hostname is blocked","messagePattern":"serverUrl hostname is blocked","errorType":"exception","errorClass":"McpProxySsrfError","httpStatus":null,"severity":"error","filePath":"api/mcp-proxy.ts","lineNumber":333,"sourceCode":"    .filter(answer => answer?.type === expectedType && typeof answer?.data === 'string')\n    .map(answer => answer.data);\n}\n\nasync function defaultResolveHostname(hostname, signal) {\n  const resolveHostnameForTest = getResolveHostnameForTest();\n  if (resolveHostnameForTest) return resolveHostnameForTest(hostname, signal);\n  const records = await Promise.all([\n    resolveDnsJson(hostname, 'A', signal),\n    resolveDnsJson(hostname, 'AAAA', signal),\n  ]);\n  return records.flat();\n}\n\nasync function assertServerUrlSafe(url, signal) {\n  signal?.throwIfAborted();\n  const hostname = url.hostname.toLowerCase();\n  if (BLOCKED_HOSTNAMES.has(hostname)) {\n    throw new McpProxySsrfError('serverUrl hostname is blocked');\n  }\n  if (isBlockedResolvedAddress(hostname)) {\n    throwBlockedAddress(hostname);\n  }\n\n  let resolvedAddresses;\n  try {\n    resolvedAddresses = await defaultResolveHostname(hostname, signal);\n  } catch (error) {\n    signal?.throwIfAborted();\n    throw new McpProxySsrfError('serverUrl DNS resolution failed', { cause: error });\n  }\n  signal?.throwIfAborted();\n\n  if (!resolvedAddresses.length) {\n    throw new McpProxySsrfError('serverUrl DNS resolution returned no addresses');\n  }\n","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/mcp-proxy.ts#L315-L351","documentation":"assertServerUrlSafe performs SSRF checks on user-supplied serverUrl values before the proxy fetches them. If the hostname is in BLOCKED_HOSTNAMES (localhost, metadata endpoints, etc.) it throws McpProxySsrfError('serverUrl hostname is blocked'). The proxy deliberately refuses to fetch internal/reserved hosts.","triggerScenarios":"Registering or fetching an MCP server whose serverUrl hostname resolves to a blocked literal (localhost, 127.0.0.1 names, cloud metadata hosts like 169.254.169.254's hostname forms, *.internal entries) — at validateServerUrl time or revalidateBeforeFetch time on each request.","commonSituations":"Developers pointing serverUrl at a local MCP server (http://localhost:3000) that works locally but is blocked in the deployed proxy; attackers probing SSRF; config copied from internal network docs.","solutions":["Use a public, externally reachable URL for the MCP server (not localhost/127.0.0.1/internal hostnames)","Expose your local MCP server via a public tunnel (e.g. ngrok-style) if you must reach a dev server","If this is your own infrastructure, add the required hostname to the allow path via the sanctioned config mechanism rather than bypassing the check","Treat the error as a security guardrail — do not try to evade it"],"exampleFix":"// before\nregisterMcpServer({ serverUrl: 'http://localhost:8080/mcp' }); // blocked\n// after\nregisterMcpServer({ serverUrl: 'https://mcp.example.com/mcp' }); // public host","handlingStrategy":"validation","validationCode":"const BLOCKED = new Set(['localhost','127.0.0.1','0.0.0.0','169.254.169.254','metadata.google.internal']);\nfunction serverUrlIsPublic(u) { const h = new URL(u).hostname.toLowerCase(); return !BLOCKED.has(h) && !/(^|\\.)local$|\\.internal$/.test(h); }","typeGuard":"function isPublicHttpUrl(v) { try { const u = new URL(v); return (u.protocol === 'https:' || u.protocol === 'http:') && !/(^|\\.)local$|\\.internal$|^localhost$/.test(u.hostname.toLowerCase()); } catch { return false; } }","tryCatchPattern":"try { await validateServerUrl(serverUrl); } catch (e) { if (e instanceof McpProxySsrfError) return { error: 'blocked_server_url', detail: e.message }; throw e; }","preventionTips":["Never point serverUrl at localhost/private hostnames in deployed environments","Use public tunnels for local MCP development","Keep BLOCKED_HOSTNAMES and reserved-CIDR checks current (cloud metadata hosts)","Treat SSRF errors as security signals — log and investigate blocked attempts"],"tags":["ssrf","security","proxy","url","validation"],"backgroundTag":"invalid-url","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}