{"record":{"id":"4ad400b40b242a2f","repo":"santifer/career-ops","slug":"access-denied-localhost-or-internal-domain-target","errorCode":null,"errorMessage":"Access denied: Localhost or internal domain target detected.","messagePattern":"Access denied: Localhost or internal domain target detected\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"upskill.mjs","lineNumber":859,"sourceCode":"\n  if (failures.length > 0) {\n    console.error(`upskill self-test failed: ${failures.join('; ')}`);\n    process.exit(1);\n  }\n  console.log('upskill self-test OK (extraction, suppression guards, weighting, tiering, report parsing, known-skills comment handling)');\n  process.exit(0);\n}\n\n// Helper function to enforce egress guard against SSRF (Private/Loopback IPs)\nconst dnsCache = new Map();\n\nasync function validateUrlSecurity(urlString) {\n  const dns = await import('dns/promises');\n  const url = new URL(urlString.endsWith('.') ? urlString.slice(0, -1) : urlString);\n  const hostname = url.hostname;\n\n  if (hostname === 'localhost' || hostname.endsWith('.local')) {\n    throw new Error('Access denied: Localhost or internal domain target detected.');\n  }\n\n  let addresses;\n  if (dnsCache.has(hostname)) {\n    addresses = dnsCache.get(hostname);\n  } else {\n    addresses = await dns.resolve(hostname).catch(() => []);\n    const lookupRes = await dns.lookup(hostname).catch(() => null);\n    if (lookupRes) addresses.push(lookupRes.address);\n    dnsCache.set(hostname, addresses);\n  }\n\n  for (const ip of addresses) {\n    if (/^(127\\.|10\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|169\\.254\\.)/.test(ip)) {\n      throw new Error(`Access denied: Egress guard blocked private target IP ${ip}`);\n    }\n    if (ip === '::1' || ip.startsWith('fe80:') || ip.startsWith('fc00:') || ip.startsWith('fd00:')) {\n      throw new Error(`Access denied: Egress guard blocked private target IPv6 ${ip}`);","sourceCodeStart":841,"sourceCodeEnd":877,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/upskill.mjs#L841-L877","documentation":"upskill.mjs's validateUrlSecurity() is an SSRF egress guard that vetoes URLs aimed at localhost or internal .local domains before any fetch occurs. It runs DNS resolution afterward and blocks private IPs, but this specific check is the hostname-based first line. It exists so untrusted JD URLs can never be used to probe internal services.","triggerScenarios":"Passing a URL whose hostname is `localhost`, `*.localhost`, or ends in `.local` (e.g. http://localhost:8080/jd, http://myserver.local/x) as the --url/input source to upskill.","commonSituations":"Testing the tool against a locally running JD server; an internal corporate portal using a .local mDNS hostname; a typo'd or template URL left as localhost in a script.","solutions":["Serve the JD from a public URL the tool is allowed to fetch.","If the content is local, save it to a file and pass the file path instead of a URL.","If the host is a real public host but named under .local (misconfigured DNS), use its public FQDN or IP-reachable domain instead."],"exampleFix":"// before\nnode upskill.mjs --url http://localhost:3000/job/123\n// after\nnode upskill.mjs --file ./job-123.html","handlingStrategy":"validation","validationCode":"function isBlockedHost(u) {\n  const h = new URL(u).hostname;\n  return h === 'localhost' || h.endsWith('.local');\n}\nif (isBlockedHost(url)) console.error('use a public URL or a local file path instead');","typeGuard":null,"tryCatchPattern":"try {\n  await upskillFromUrl(url);\n} catch (e) {\n  if (e.message.includes('Localhost or internal domain')) {\n    console.error('save the JD locally and use --file instead');\n  } else throw e;\n}","preventionTips":["Never point the tool at localhost or .local hostnames — by design it will refuse.","For local content, use the file/paste input path.","Validate URLs against public DNS resolution before passing them."],"tags":["security","ssrf","url-validation"],"backgroundTag":"invalid-url","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"}