{"record":{"id":"c3c69021a2cebbeb","repo":"santifer/career-ops","slug":"lever-untrusted-hostname-parsed-hostname-m","errorCode":null,"errorMessage":"lever: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_LEVER_HOSTS].join(', ')}","messagePattern":"lever: untrusted hostname \"(.+?)\" — must be one of: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/lever.mjs","lineNumber":20,"sourceCode":"/** @typedef {import('./_types.js').Provider} Provider */\n\n// Lever provider — hits the public postings endpoint.\n// Auto-detects from careers_url via jobs.(eu.)?lever.co/<slug>.\n// Handles both explicit `api:` URLs and auto-detection from `careers_url`.\n\nconst ALLOWED_LEVER_HOSTS = new Set(['api.lever.co', 'api.eu.lever.co']);\n\n/** @param {string} url */\nfunction assertLeverUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`lever: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`lever: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_LEVER_HOSTS.has(parsed.hostname))\n    throw new Error(`lever: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_LEVER_HOSTS].join(', ')}`);\n  return url;\n}\n\n/** @param {import('./_types.js').PortalEntry} entry */\nfunction resolveApiUrl(entry) {\n  // Explicit api: wins — lets an entry keep a human-facing corporate\n  // careers_url (e.g. https://www.coalfire.com/careers) while still pinning\n  // the Lever postings board (mirrors greenhouse's api: precedence).\n  if (entry.api) {\n    assertLeverUrl(entry.api);\n    return entry.api;\n  }\n  let url;\n  try {\n    url = new URL(entry.careers_url || '');\n  } catch {\n    return null;\n  }","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/lever.mjs#L2-L38","documentation":"Third guard in assertLeverUrl: parsed.hostname must be in ALLOWED_LEVER_HOSTS ({'api.lever.co','api.eu.lever.co'}). The message lists both permitted hosts. This is the SSRF allowlist — it stops a config from directing the fetch at an arbitrary host.","triggerScenarios":"An HTTPS URL whose hostname is neither api.lever.co nor api.eu.lever.co — e.g. jobs.lever.co (the human-facing host, not the API), www.lever.co, api.lever.co.evil.com, or a corporate domain. The bad hostname is interpolated into the message.","commonSituations":"Config used the careers page host (jobs.lever.co) instead of the API host; a regional EU endpoint was spelled wrong; the entry points at a corporate front-end rather than the postings API.","solutions":["Point api: at https://api.lever.co/v0/postings/<slug> (or api.eu.lever.co for EU boards).","If using careers_url auto-detection, confirm it derives an api.* host; otherwise set api: explicitly.","If Lever adds a new regional API host, extend ALLOWED_LEVER_HOSTS after verifying it is official."],"exampleFix":"// before\napi: https://jobs.lever.co/acme\n\n// after\napi: https://api.lever.co/v0/postings/acme","handlingStrategy":"validation","validationCode":"import { URL } from 'node:url';\nconst ALLOWED = new Set(['api.lever.co', 'api.eu.lever.co']);\nexport function isTrustedLeverHost(value) {\n  try { return ALLOWED.has(new URL(value).hostname); } catch { return false; }\n}","typeGuard":"/** @param {string} url */\nfunction isLeverApiHost(url) {\n  try { return new URL(url).hostname === 'api.lever.co' || new URL(url).hostname === 'api.eu.lever.co'; } catch { return false; }\n}","tryCatchPattern":"try {\n  assertLeverUrl(url);\n} catch (err) {\n  if (err.message.includes('untrusted hostname')) console.error(`[security] ${err.message}`);\n  throw err;\n}","preventionTips":["Use api.lever.co (or api.eu.lever.co for EU boards) — never the jobs.lever.co careers host — for the api: field.","Treat an untrusted-hostname error as a security signal, not a typo to silence.","Extend ALLOWED_LEVER_HOSTS only after verifying a new host is an official Lever API endpoint."],"tags":["url-validation","ssrf","host-allowlist","lever","security"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}