{"record":{"id":"91274b60048cc9b0","repo":"santifer/career-ops","slug":"nodesk-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"nodesk: untrusted hostname \"${parsed.hostname}\" - must be ${TRUSTED_HOST}","messagePattern":"nodesk: untrusted hostname \"(.+?)\" - must be (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/nodesk.mjs","lineNumber":25,"sourceCode":"// and XML, so it is parsed in-process with the same tiny tag extractor\n// approach as providers/personio.mjs rather than adding an XML dependency.\n//\n// Wire in via a `job_boards:` entry with `provider: nodesk`.\n\nconst FEED_URL = 'https://nodesk.co/remote-jobs/index.xml';\nconst TRUSTED_HOST = 'nodesk.co';\n\n/** @param {string} url */\nfunction assertNodeskUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`nodesk: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`nodesk: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`nodesk: untrusted hostname \"${parsed.hostname}\" - must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n// NaN-safe Date.parse - `|| undefined` would also coerce a valid epoch 0.\nfunction toEpochMs(value) {\n  if (!value) return undefined;\n  const parsed = Date.parse(value);\n  return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction fallbackCompany(entry) {\n  return typeof entry?.name === 'string' && entry.name.trim() ? entry.name.trim() : 'NoDesk';\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'nodesk',","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/nodesk.mjs#L7-L43","documentation":"assertNodeskUrl pins the provider to a single trusted host (TRUSTED_HOST). After the URL parses and passes the HTTPS check, this error is thrown when parsed.hostname does not equal TRUSTED_HOST. This prevents the provider from being pointed at look-alike or third-party hosts (SSRF/open-redirect hardening).","triggerScenarios":"Calling the nodesk provider with a URL on a different domain — a mirror, a company careers page, a staging host, or a typo'd hostname like 'nodesk.example.com' or 'www.<trusted-host>' if that is not the pinned host.","commonSituations":"Config entry pointing at the wrong provider's URL (copy-paste across providers); subdomain added or removed ('api.' prefix); hostname changed after a vendor migration; typos in the host portion.","solutions":["Correct the hostname to exactly match the trusted host (check for missing/extra subdomains and typos)","Copy a known-good URL from the provider's documentation or an existing working entry","If the entry belongs to another ATS/provider, move it to the matching provider config instead","If the trusted host itself changed upstream, update TRUSTED_HOST in providers/nodesk.mjs deliberately — do not bypass the assert"],"exampleFix":"// before\nassertNodeskUrl('https://nodesk-mirror.example.net/jobs');\n// after\nassertNodeskUrl('https://' + TRUSTED_HOST + '/jobs');","handlingStrategy":"validation","validationCode":"const parsed = new URL(url);\nif (parsed.hostname !== 'nodesk.example') throw new Error(`wrong host: ${parsed.hostname}`);","typeGuard":"function isTrustedHost(u, trusted) {\n  try { return new URL(u).hostname === trusted; } catch { return false; }\n}","tryCatchPattern":"try {\n  useNodesk(url);\n} catch (e) {\n  if (String(e.message).startsWith('nodesk: untrusted hostname')) {\n    log.warn(`Entry points at wrong host, skipping: ${url}`);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Copy provider URLs from a known-good working entry, not from memory","Keep one canonical TRUSTED_HOST constant and reference it when writing configs","Route each company entry to the provider that actually hosts its board","Watch for subdomain drift (www., api., regional prefixes) when editing hosts by hand"],"tags":["url-validation","security","hostname","allowlist"],"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"}