{"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":159,"sourceCode":" * Emit one wm_api_usage RequestEvent per proxied call.\n *\n * Before this, `logProxyCall` was the ONLY record of a proxy request and it is\n * `console.log` — Vercel runtime logs are a live tail with no historical query,\n * so `/api/mcp-proxy` had ZERO rows in Axiom and its failure rate could not be\n * asked about after the fact. That is the same hole #4866 closed for `/mcp`;\n * this reuses the gateway's builders so rows are byte-compatible and joinable\n * on customer_id. `logProxyCall` stays: it carries target_host/header_names,\n * which the usage envelope has no field for, and existing log-ingest tooling\n * parses its shape.\n *\n * OPTIONS preflights are deliberately NOT emitted — a static 204 that cannot\n * fail would double row volume for no diagnostic value. /mcp skips them too\n * (McpUsage.skip).\n */\nfunction emitProxyUsage(req, status: number, durationMs: number, ctx, callerIdentity = null): void {\n  if (!ctx) return;\n  try {\n    const usageIdentity = proxyUsageIdentityFor(req, callerIdentity);\n    emitUsageEvents(ctx, [buildRequestEvent({\n      requestId: deriveRequestId(req),\n      domain: 'mcp',\n      route: '/api/mcp-proxy',\n      method: req.method,\n      status,\n      // Measured from handler entry, so this INCLUDES the auth/rate-limit\n      // gates — unlike logProxyCall's `started`, which begins after auth.\n      // The usage row is the end-to-end caller-visible latency.\n      durationMs,\n      reqBytes: deriveReqBytes(req),\n      // Not tracked: the proxy streams upstream bodies through bounded readers\n      // and jsonResponse Content-Length reflects only the local denial/error\n      // envelopes — never the proxied upstream size. Size questions belong to\n      // MAX_MCP_PROXY_RESPONSE_BYTES, not to this row. null (not 0) so unknown\n      // is not confused with an empty body (#8403).\n      resBytes: null,\n      customerId: usageIdentity.customer_id,","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/api/mcp-proxy.ts#L141-L177","documentation":"SSRF guard error in the MCP proxy's assertServerUrlSafe: the serverUrl hostname (lowercased) matches the BLOCKED_HOSTNAMES denylist verbatim (e.g. 'localhost' or metadata-service names). Unlike resolved-IP blocks, the hostname itself is echoed because the caller already knows it.","triggerScenarios":"Thrown at api/mcp-proxy.ts:157 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use the service's public hostname instead of a blocked literal name","If a legitimate service shares a blocked name, expose it via an allowlisted public alias"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}