{"record":{"id":"6bb817e301945a8d","repo":"koala73/worldmonitor","slug":"serverurl-hostname-is-blocked-hostname","errorCode":null,"errorMessage":"serverUrl hostname is blocked: ${hostname}","messagePattern":"serverUrl hostname is blocked: (.+?)","errorType":"exception","errorClass":"McpProxySsrfError","httpStatus":422,"severity":"error","filePath":"api/mcp-proxy.ts","lineNumber":157,"sourceCode":"  return (Array.isArray(data?.Answer) ? data.Answer : [])\n    .filter(answer => answer?.type === expectedType && typeof answer?.data === 'string')\n    .map(answer => answer.data);\n}\n\nasync function defaultResolveHostname(hostname) {\n  const resolveHostnameForTest = getResolveHostnameForTest();\n  if (resolveHostnameForTest) return resolveHostnameForTest(hostname);\n  const records = await Promise.all([\n    resolveDnsJson(hostname, 'A'),\n    resolveDnsJson(hostname, 'AAAA'),\n  ]);\n  return records.flat();\n}\n\nasync function assertServerUrlSafe(url) {\n  const hostname = url.hostname.toLowerCase();\n  if (BLOCKED_HOSTNAMES.has(hostname)) {\n    throw new McpProxySsrfError(`serverUrl hostname is blocked: ${hostname}`);\n  }\n  if (isBlockedResolvedAddress(hostname)) {\n    throwBlockedAddress(hostname);\n  }\n\n  let resolvedAddresses;\n  try {\n    resolvedAddresses = await defaultResolveHostname(hostname);\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    throw new McpProxySsrfError(`serverUrl DNS resolution failed: ${message}`);\n  }\n\n  if (!resolvedAddresses.length) {\n    throw new McpProxySsrfError('serverUrl DNS resolution returned no addresses');\n  }\n\n  const blocked = resolvedAddresses.find(isBlockedResolvedAddress);","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp-proxy.ts#L139-L175","documentation":"Thrown by assertServerUrlSafe when the lowercased serverUrl hostname is in BLOCKED_HOSTNAMES (localhost, metadata, metadata.internal, metadata.google.internal, instance-data, computemetadata, link-local.s3.amazonaws.com, 169.254.169.254). This is the static cloud-metadata/localhost SSRF gate that runs before any DNS resolution. The hostname is interpolated into the message because it is already non-secret caller input.","triggerScenarios":"POST /api/mcp-proxy with a serverUrl whose host is one of the blocked metadata/localhost literals, e.g. `https://169.254.169.254/`, `https://metadata.google.internal/`, or `https://localhost:8080/`.","commonSituations":"Local dev pointing the MCP proxy at localhost; an adversarial SSRF probe against cloud metadata; a stale config pointing at a metadata hostname.","solutions":["Use a public https MCP server URL.","For local dev, expose the local MCP server via an https tunnel before pointing the proxy at it.","Remove any metadata/localhost hostnames from MCP config fixtures."],"exampleFix":"// before\nproxy({ serverUrl: 'https://localhost:8080/mcp' })\n// after\nproxy({ serverUrl: 'https://mcp-tunnel.example.dev/mcp' })","handlingStrategy":"validation","validationCode":"const BLOCKED_HOSTNAMES = new Set([\n  'localhost','metadata','metadata.internal','metadata.google.internal',\n  'instance-data','computemetadata','link-local.s3.amazonaws.com','169.254.169.254',\n]);\n\nfunction serverUrlHostBlocked(rawUrl: string): boolean {\n  try { return BLOCKED_HOSTNAMES.has(new URL(rawUrl).hostname.toLowerCase()); }\n  catch { return false; }\n}","typeGuard":"function isAllowedMcpHostname(value: unknown): boolean {\n  if (typeof value !== 'string') return false;\n  try { return !BLOCKED_HOSTNAMES.has(new URL(value).hostname.toLowerCase()); } catch { return false; }\n}","tryCatchPattern":"try {\n  await assertServerUrlSafe(new URL(serverUrl));\n} catch (err) {\n  if (err.name === 'McpProxySsrfError' && err.message.startsWith('serverUrl hostname is blocked:')) {\n    return res.status(400).json({ error: 'That MCP server hostname is blocked.' });\n  }\n  throw err;\n}","preventionTips":["Never configure the proxy with localhost or metadata hostnames; use public hostnames or https tunnels for local dev.","Keep test fixtures pointed at public test sinks.","Extend BLOCKED_HOSTNAMES if your infra adds new metadata endpoints."],"tags":["mcp","ssrf","security","metadata","proxy"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}