{"record":{"id":"9326381b6a26f983","repo":"musistudio/claude-code-router","slug":"label-cannot-target-a-local-or-internal-host","errorCode":null,"errorMessage":"${label} cannot target a local or internal host.","messagePattern":"(.+?) cannot target a local or internal host\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/providers/manifest-service.ts","lineNumber":235,"sourceCode":"  }\n  validateRemoteHostname(url.hostname, label);\n  await resolveSafeAddress(url.hostname);\n}\n\nfunction validateRemoteHostname(hostname: string, label: string): void {\n  const normalized = hostname.trim().toLowerCase().replace(/\\.$/, \"\");\n  if (!normalized) {\n    throw new Error(`${label} is invalid.`);\n  }\n  if (\n    normalized === \"localhost\" ||\n    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,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/providers/manifest-service.ts#L217-L253","documentation":"validateRemoteHostname blocks hostnames that resolve to local/internal scopes: 'localhost', '*.localhost', or suffixes .home, .lan, .local, .internal. Remote manifests must only target publicly reachable hosts — this is the first half of an SSRF guard (hostname scope), complemented by IP resolution checks in resolveSafeAddress.","triggerScenarios":"A manifest URL with hostname 'localhost', 'mybox.lan', 'printer.local', 'service.internal', or anything ending in '.localhost'.","commonSituations":"Developer points a 'remote' manifest at an internal service for testing; homelab DNS suffixes (.lan/.home) reused in production manifests; mDNS-style .local names in docs copied into config.","solutions":["Point the manifest at the public DNS name of the service","Expose the internal service through a public HTTPS endpoint (reverse proxy) if it must be referenced remotely","Use a local manifest file for internal/lab targets instead of a remote manifest"],"exampleFix":"// before\n\"baseUrl\": \"https://inference.internal:8080\"\n// after\n\"baseUrl\": \"https://inference.example.com\"","handlingStrategy":"validation","validationCode":"const BANNED = [/^localhost$/i, /\\.localhost$/i, /\\.(home|lan|local|internal)$/i];\nif (BANNED.some(r => r.test(hostname))) throw new Error('internal host not allowed');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use public DNS names in remote manifests","Keep internal/lab targets in local manifests"],"tags":["ssrf","hostname","manifest","security"],"backgroundTag":"ssrf-blocked","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}