santifer/career-ops · error · Error

jobvite: untrusted hostname

Error message

jobvite: untrusted hostname "${parsed.hostname}" — must be ${BOARD_HOST} or ${FEED_HOST}

What it means

assertJobviteHost() accepts only two hostnames: BOARD_HOST (jobs.jobvite.com) and FEED_HOST (Jobvite's feed host). Any other parsed hostname throws this untrusted-hostname error, preventing SSRF through a user-controlled careers_url or api URL.

Solutions

  1. Point the URL at https://jobs.jobvite.com/<company-slug> (or the feed host) exactly.
  2. Set company_eid: on the entry so fetch() builds the allowlisted URL from the ID rather than trusting a custom host.
  3. If you have a legitimate third host, update ALLOWED_HOSTS / BOARD_HOST / FEED_HOST constants in providers/jobvite.mjs deliberately.

Example fix

// before (portals.yml)
provider: jobvite
careers_url: https://careers.acme.com/jobs
// after
provider: jobvite
careers_url: https://jobs.jobvite.com/acme
Defensive patterns

Strategy: validation

Validate before calling

const host = new URL(entry.careers_url).hostname;
if (host !== 'jobs.jobvite.com' && host !== FEED_HOST) throw new Error(`${host} is not a jobvite board host`);

Type guard

const isJobviteUrl = (s) => { try { return ['jobs.jobvite.com', FEED_HOST].includes(new URL(s).hostname); } catch { return false; } };

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (e) {
  if (e.message.includes('jobvite: untrusted hostname')) {
    entry.careers_url = `https://jobs.jobvite.com/${entry.slug ?? entry.name.toLowerCase()}`;
  }
}

Prevention

When it happens

Trigger: A jobvite entry whose api:/careers_url points at another domain — e.g. search.jobvite.com, a custom vanity domain, or a lookalike host; or a slug was embedded into the wrong base URL by custom code.

Common situations: Copying a jobvite URL from a company's own website that uses a vanity CNAME; using search.jobvite.com?invalid=1 style links from a retired board; typo'd hostnames.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/701604688e40cf9d. Report an issue: GitHub.

Appendix: source

Thrown at providers/jobvite.mjs:108

// network failure. Sized to absorb a genuinely big tenant on a slow link; the
// board page (a normal HTML document) keeps the default.
const FEED_TIMEOUT_MS = 45_000;

/**
 * Pin a URL to the two known Jobvite hosts over HTTPS.
 * @param {string} url
 */
function assertJobviteHost(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`jobvite: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:')
    throw new Error(`jobvite: URL must use HTTPS: ${url}`);
  if (!ALLOWED_HOSTS.has(parsed.hostname))
    throw new Error(`jobvite: untrusted hostname "${parsed.hostname}" — must be ${BOARD_HOST} or ${FEED_HOST}`);
  return url;
}

// NaN-safe Date.parse → epoch ms.
/** @param {string} value */
function toEpochMs(value) {
  if (!value) return undefined;
  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? undefined : parsed;
}

/**
 * The vanity slug from a Jobvite careers URL, or null.
 * Only used to build the board URL for eId discovery.
 *
 * @param {import('./_types.js').PortalEntry} entry
 * @returns {string | null}
 */

View on GitHub (pinned to aac998c7ed)