santifer/career-ops · warning · Error

eightfold: untrusted hostname "${parsed.hostname}" — must ma

Error message

eightfold: untrusted hostname "${parsed.hostname}" — must match *.eightfold.ai

What it means

Thrown by eightfold's assertEightfoldUrl when the URL is valid https but its hostname does not match /^[a-z0-9-]+\.eightfold\.ai$/i (EIGHTFOLD_HOST_RE). Defense-in-depth: fetch() only ever passes buildApiUrl output, whose host is the resolveTenant-validated tenant.host, so the guard is unreachable through normal use. Reachable only via a direct assertEightfoldUrl call.

Source

Thrown at providers/eightfold.mjs:73

const RETRY_POLICY = { retries: 3, baseDelayMs: 500, maxDelayMs: 8_000 };

/**
 * SSRF guard — every request URL passes through here before it is fetched.
 *
 * @param {string} url
 * @returns {string} the same URL, when it is a trusted Eightfold endpoint.
 */
function assertEightfoldUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`eightfold: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`eightfold: URL must use HTTPS: ${url}`);
  if (!EIGHTFOLD_HOST_RE.test(parsed.hostname)) {
    throw new Error(`eightfold: untrusted hostname "${parsed.hostname}" — must match *.eightfold.ai`);
  }
  return url;
}

/** @param {number} ms @param {any} ctx */
function sleep(ms, ctx) {
  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Eightfold reports timestamps as epoch SECONDS (`t_create`, `t_update`), not
 * the ISO strings every other provider gets. Converted here; anything
 * non-finite or non-positive is dropped rather than guessed at.
 *
 * @param {unknown} value
 * @returns {number|undefined} epoch ms, or undefined.
 */

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. For direct callers, pass only https://<tenant>.eightfold.ai/... URLs.
  2. For fetch() use, point api:/careers_url at the canonical <tenant>.eightfold.ai host (a branded CNAME is deliberately rejected — see error 177).
  3. Treat a production hit as a signal that resolveTenant and assertEightfoldUrl have diverged.

Example fix

// before — branded CNAME host
assertEightfoldUrl('https://careers.bayer.com/api/apply/v2/jobs?start=0&num=10');

// after — canonical eightfold.ai tenant host
assertEightfoldUrl('https://bayer.eightfold.ai/api/apply/v2/jobs?start=0&num=10');
Defensive patterns

Strategy: validation

Validate before calling

const EIGHTFOLD_HOST_RE = /^[a-z0-9-]+\.eightfold\.ai$/i;
function isEightfoldHost(u) { try { return EIGHTFOLD_HOST_RE.test(new URL(u).hostname); } catch { return false; } }

Type guard

function isEightfoldEndpoint(u) {
  if (typeof u !== 'string' || !u) return false;
  try {
    const p = new URL(u);
    return p.protocol === 'https:' && /^[a-z0-9-]+\.eightfold\.ai$/i.test(p.hostname);
  } catch { return false; }
}

Try / catch

try { assertEightfoldUrl(url); }
catch (e) {
  if (/^eightfold: untrusted hostname/.test(e.message)) { /* branded CNAME or wrong host — skip */ }
  else throw e;
}

Prevention

When it happens

Trigger: assertEightfoldUrl is called directly with an https URL on a non-*.eightfold.ai host. fetch() validates the tenant host via resolveTenant (which applies EIGHTFOLD_HOST_RE) before any URL is built, so no entry config reaches this branch.

Common situations: A direct integration or test pointing at a branded CNAME (e.g. careers.company.com) or a mock host. Production scans fail earlier at resolveTenant (error 177), not here.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/c4e525f55b05f12c. Report an issue: GitHub.