santifer/career-ops · error · Error
lever: untrusted hostname
Error message
lever: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_LEVER_HOSTS].join(', ')} What it means
assertLeverUrl enforces a host allowlist (ALLOWED_LEVER_HOSTS, e.g. api.lever.co and jobs.lever.co variants). An https URL at any hostname outside the set is rejected; the message lists the actual hostname and the allowed set. This blocks SSRF through attacker-influenced URLs and stops requests to lookalike domains.
Solutions
- Point the URL at a hostname in ALLOWED_LEVER_HOSTS (read the set from providers/lever.mjs).
- If the entry is a Lever-powered board, find its underlying api.lever.co or jobs.lever.co endpoint and use that.
- If a new legitimate Lever host is needed, add it to ALLOWED_LEVER_HOSTS via a reviewed code change — never bypass the check at runtime.
- Use resolveApiUrl's explicit api: override only with an allowed host; the validator still applies.
Example fix
// before
assertLeverUrl('https://acme.com/api/jobs'); // company's own domain
// after
assertLeverUrl('https://api.lever.co/v0/postings/acme?mode=json'); Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED_LEVER_HOSTS = new Set(['api.lever.co', 'jobs.lever.co']);
function isTrustedLeverHost(u) { try { return ALLOWED_LEVER_HOSTS.has(new URL(u).hostname); } catch { return false; } }
if (!isTrustedLeverHost(url)) throw new Error(`lever host not allowed: ${url}`); Type guard
function isTrustedLeverUrl(v) { if (typeof v !== 'string') return false; try { return ALLOWED_LEVER_HOSTS.has(new URL(v).hostname) && new URL(v).protocol === 'https:'; } catch { return false; } } Try / catch
try {
provider.fetch(entry, ctx);
} catch (e) {
if (e.message.includes('untrusted hostname')) {
console.error(`Lever entry points at non-Lever host: ${e.message}`);
return null;
}
throw e;
} Prevention
- Resolve Lever-powered boards to their api.lever.co/jobs.lever.co endpoints, not the company's own domain
- Keep the host allowlist; extend it only via reviewed code change for new legitimate Lever hosts
- Never let user-supplied URLs choose the request host (SSRF defense)
- Review config diffs for hostname substitutions
When it happens
Trigger: Passing an https URL whose parsed hostname is not in ALLOWED_LEVER_HOSTS — a custom job board domain, a corporate proxy, or an entry whose careers_url is a Lever-hosted page at an unexpected subdomain.
Common situations: Config entry pointing at a company's own domain that merely embeds Lever jobs, a regional Lever mirror, or swapping api.lever.co for a CDN/mock host during development.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- landingjobs: untrusted hostname
- larajobs: untrusted hostname
- pythonorg: untrusted hostname
- torre: untrusted hostname
- weworkremotely: untrusted hostname
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16).
Data as JSON: /api/errors/c3c69021a2cebbeb.
Report an issue: GitHub.
Appendix: source
Thrown at providers/lever.mjs:26
const ALLOWED_LEVER_HOSTS = new Set(['api.lever.co', 'api.eu.lever.co']);
// The v0 postings endpoint returns the whole board in one response, with every
// description inlined, so a large board outgrows _http.mjs's 10s default:
// jobgether is 42.8 MB and aborted at 10s on its own (#4177). Same value and
// reasoning as ASHBY_TIMEOUT_MS, the other one-response board-wide ATS feed.
const LEVER_TIMEOUT_MS = 30_000;
/** @param {string} url */
function assertLeverUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`lever: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`lever: URL must use HTTPS: ${url}`);
if (!ALLOWED_LEVER_HOSTS.has(parsed.hostname))
throw new Error(`lever: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_LEVER_HOSTS].join(', ')}`);
return url;
}
/** @param {import('./_types.js').PortalEntry} entry */
function resolveApiUrl(entry) {
// Explicit api: wins — lets an entry keep a human-facing corporate
// careers_url (e.g. https://www.coalfire.com/careers) while still pinning
// the Lever postings board (mirrors greenhouse's api: precedence).
if (entry.api) {
assertLeverUrl(entry.api);
return entry.api;
}
let url;
try {
url = new URL(entry.careers_url || '');
} catch {
return null;
}View on GitHub (pinned to e7abd431fc)