{"record":{"id":"5fc527509420e2f9","repo":"musistudio/claude-code-router","slug":"remote-manifest-host-resolved-to-a-private-or-rese","errorCode":null,"errorMessage":"Remote manifest host resolved to a private or reserved address: ${address.address}","messagePattern":"Remote manifest host resolved to a private or reserved address: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/providers/manifest-service.ts","lineNumber":247,"sourceCode":"    normalized.endsWith(\".localhost\") ||\n    normalized.endsWith(\".home\") ||\n    normalized.endsWith(\".lan\") ||\n    normalized.endsWith(\".local\") ||\n    normalized.endsWith(\".internal\")\n  ) {\n    throw new Error(`${label} cannot target a local or internal host.`);\n  }\n}\n\nasync function resolveSafeAddress(hostname: string): Promise<SafeAddress> {\n  const addresses = await lookup(hostname, { all: true, verbatim: true });\n  if (addresses.length === 0) {\n    throw new Error(`Could not resolve host: ${hostname}`);\n  }\n\n  for (const address of addresses) {\n    if (!isPublicIpAddress(address.address)) {\n      throw new Error(`Remote manifest host resolved to a private or reserved address: ${address.address}`);\n    }\n  }\n\n  const first = addresses[0];\n  return {\n    address: first.address,\n    family: first.family === 6 ? 6 : 4\n  };\n}\n\nfunction isPublicIpAddress(address: string): boolean {\n  const family = net.isIP(address);\n  if (family === 4) {\n    return isPublicIpv4(address);\n  }\n  if (family === 6) {\n    return isPublicIpv6(address);\n  }","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/providers/manifest-service.ts#L229-L265","documentation":"After resolving the manifest host, every returned IP is checked with isPublicIpAddress; if any resolved address is private/reserved (RFC1918, loopback, link-local, etc.) this error fires. This defeats DNS-rebinding SSRF: a public-looking hostname that resolves to an internal IP is rejected.","triggerScenarios":"A manifest host whose DNS A/AAAA records include 10.x, 192.168.x, 172.16-31.x, 127.x, 169.254.x, or other non-public ranges — including multi-record responses where only some records are private.","commonSituations":"Wildcard DNS (e.g. nip.io/sslip.io style) mapping to 127.0.0.1; a service with both public and internal A records; DNS rebinding services; split-horizon DNS returning the internal IP to your network.","solutions":["Use a host whose DNS returns only public addresses (remove internal A/AAAA records from the public zone)","Avoid rebinding-friendly domains (nip.io etc.) in manifests","For internal targets, use a local manifest rather than a remote one","Check `dig +short <host>` for the full record set from the machine running the app"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { lookup } from 'node:dns/promises';\nimport { isPublicIpAddress } from './ip';\nconst addrs = await lookup(hostname, { all: true });\nif (addrs.some(a => !isPublicIpAddress(a.address))) throw new Error('non-public resolution');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure public DNS zones contain only public A/AAAA records","Avoid rebinding-style wildcard domains in manifests"],"tags":["ssrf","dns","security","network"],"backgroundTag":"ssrf-private-ip-blocked","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}