{"record":{"id":"467f2fb0ad608e63","repo":"santifer/career-ops","slug":"plugin-egress-cannot-resolve-hostname-err","errorCode":null,"errorMessage":"plugin egress: cannot resolve ${hostname} — ${err.message}","messagePattern":"plugin egress: cannot resolve (.+?) — (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/_net.mjs","lineNumber":95,"sourceCode":"  // An IP literal host: validate directly (no DNS).\n  if (isIP(hostname)) {\n    if (isBlockedIp(hostname)) {\n      if (allowsLocalhost && isLoopbackLiteral(hostname)) return [hostname];\n      throw new Error(`plugin egress to ${hostname} is blocked (private/loopback/metadata range)`);\n    }\n    return [hostname];\n  }\n\n  if (allowsLocalhost && LOOPBACK_HOSTS.has(hostname.toLowerCase())) {\n    // Local-AI providers (Ollama/LM Studio). Resolve but allow loopback through.\n    return ['127.0.0.1'];\n  }\n\n  let addrs;\n  try {\n    addrs = await dnsLookup(hostname, { all: true });\n  } catch (err) {\n    throw new Error(`plugin egress: cannot resolve ${hostname} — ${err.message}`);\n  }\n  if (!addrs.length) throw new Error(`plugin egress: ${hostname} resolved to no addresses`);\n  for (const { address } of addrs) {\n    if (isBlockedIp(address)) {\n      if (allowsLocalhost && isLoopbackLiteral(address)) continue;\n      throw new Error(`plugin egress: ${hostname} resolves to a blocked address (${address}) — possible SSRF/rebinding`);\n    }\n  }\n  return addrs.map(a => a.address);\n}\n\nfunction isLoopbackLiteral(ip) {\n  if (ip === '::1') return true;\n  if (isIP(ip) === 4) return ip.split('.')[0] === '127';\n  return false;\n}\n","sourceCodeStart":77,"sourceCodeEnd":112,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugins/_net.mjs#L77-L112","documentation":"For non-IP-literal hostnames, resolveAndValidate performs a DNS lookup (dnsLookup with all: true) before checking addresses. If resolution itself fails, the original error message is wrapped and rethrown as this error. DNS failure means the egress guard cannot verify the destination, so the request is refused rather than passed through unresolved.","triggerScenarios":"Calling resolveAndValidate(hostname) where hostname has no DNS record (NXDOMAIN), the machine has no network/DNS connectivity, DNS times out, /etc/resolv.conf is broken, or the hostname is malformed so the resolver rejects it.","commonSituations":"Offline laptop or CI runner without network access; typo'd API hostname in plugin config; corporate DNS blocking the domain; hostname only resolvable on a VPN that is not connected; transient DNS server outage.","solutions":["Check network/DNS connectivity (`ping`/`nslookup <hostname>`) and fix the resolver or reconnect before retrying.","Correct the hostname in the plugin configuration if it is a typo.","Connect the VPN or network on which the hostname resolves, if it is an internal-only name.","Retry on transient failures — wrap the call in retry with backoff since DNS outages are often momentary.","If the name is stable and known, use its public IP literal (which skips DNS) — but note it must not be in a blocked range."],"exampleFix":"// before: single attempt dies on transient DNS failure\nconst addrs = await resolveAndValidate(hostname);\n\n// after: retry a few times with backoff\nlet addrs;\nfor (let i = 0; i < 3; i++) {\n  try { addrs = await resolveAndValidate(hostname); break; }\n  catch (e) {\n    if (!e.message.startsWith(\"plugin egress: cannot resolve\") || i === 2) throw e;\n    await new Promise(r => setTimeout(r, 500 * 2 ** i));\n  }\n}","handlingStrategy":"retry","validationCode":"function isProbablyResolvable(hostname) {\n  return typeof hostname === 'string' && hostname.length > 0 && !hostname.includes(' ') && !isIP(hostname);\n}","typeGuard":null,"tryCatchPattern":"async function resolveWithRetry(hostname, opts, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await resolveAndValidate(hostname, opts);\n    } catch (err) {\n      const isDnsFail = err.message.startsWith('plugin egress: cannot resolve ');\n      if (!isDnsFail || i === attempts - 1) throw err;\n      await new Promise(r => setTimeout(r, 500 * 2 ** i));\n    }\n  }\n}","preventionTips":["Verify the hostname spelling and DNS record at plugin-install/config time.","Retry DNS resolution with backoff; outages are often transient.","Detect offline/VPN-required environments early and surface a clear message.","Prefer stable public hostnames with reliable DNS over internal-only names."],"tags":["dns","network","ssrf","plugin-egress"],"backgroundTag":"network-request-failed","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}