{"record":{"id":"67106249177ac59b","repo":"santifer/career-ops","slug":"workable-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"workable: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_WORKABLE_HOSTS].join(', ')}","messagePattern":"workable: untrusted hostname \"(.+?)\" — must be one of: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/workable.mjs","lineNumber":118,"sourceCode":"// same host, so this process never needs more than one in-flight request to\n// it at a time.\nlet workableQueue = Promise.resolve();\nfunction serialized(fn) {\n  const result = workableQueue.then(fn, fn);\n  workableQueue = result.then(() => undefined, () => undefined);\n  return result;\n}\n\nfunction assertWorkableUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`workable: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`workable: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_WORKABLE_HOSTS.has(parsed.hostname)) {\n    throw new Error(`workable: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_WORKABLE_HOSTS].join(', ')}`);\n  }\n  return url;\n}\n\n/**\n * Extract the account slug from a tracked_companies entry's careers_url.\n * @returns {string|null}\n */\nexport function resolveWorkableSlug(entry) {\n  const raw = entry && typeof entry.careers_url === 'string' ? entry.careers_url : '';\n  if (!raw) return null;\n  let parsed;\n  try {\n    parsed = new URL(raw);\n  } catch {\n    return null;\n  }\n  if (parsed.protocol !== 'https:') return null;","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/workable.mjs#L100-L136","documentation":"assertWorkableUrl enforces a hostname allowlist (ALLOWED_WORKABLE_HOSTS) after the HTTPS check. Any URL whose parsed hostname is not in that set is rejected to prevent SSRF — a configured or derived URL pointing at an arbitrary server is refused before any fetch happens.","triggerScenarios":"assertWorkableUrl called with a parsed hostname outside ALLOWED_WORKABLE_HOSTS, e.g. 'https://evil.example.com/acme' or a typo'd host like 'apply.workable.com.evil.io'.","commonSituations":"A careers_url pointing at a company's own site instead of its Workable board; a hostile/mistyped entry in portals.yml; a custom proxy host that is not allowlisted.","solutions":["Point the URL at an allowlisted Workable host (see the list in the message)","Fix typos or embedded extra domains in the hostname","If a new legitimate Workable host is genuinely needed, add it to ALLOWED_WORKABLE_HOSTS in providers/workable.mjs after verifying it"],"exampleFix":"// before\nassertWorkableUrl('https://jobs.acme.com/widget?account=acme');\n// after\nassertWorkableUrl('https://apply.workable.com/acme/widget');","handlingStrategy":"validation","validationCode":"const ALLOWED = ['apply.workable.com','workable.com'];\nfunction isAllowedWorkableHost(url) { try { return ALLOWED.includes(new URL(url).hostname); } catch { return false; } }","typeGuard":"const isAllowedWorkableHost = (u) => { try { return ['apply.workable.com','workable.com'].includes(new URL(u).hostname); } catch { return false; } };","tryCatchPattern":"try { return await workableProvider.fetch(entry, ctx); } catch (e) { if (e.message.includes('untrusted hostname')) { console.warn(`Entry ${entry.name} points at a non-Workable host; check careers_url`); return []; } throw e; }","preventionTips":["Only configure careers URLs on official Workable board domains","Watch for lookalike hosts (extra suffixes) when copying URLs","Review new portals.yml entries against the allowlist before enabling them"],"tags":["ssrf-guard","hostname-allowlist","url-validation","security"],"backgroundTag":"untrusted-hostname","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"}