santifer/career-ops · error

comeet: untrusted hostname

Error message

comeet: untrusted hostname "${parsed.hostname}" — must be ${COMEET_API_HOST}

What it means

As an SSRF defense, the Comeet provider pins the API hostname to the single fixed origin www.comeet.co (combined with redirect:'error' so a redirect can't move the request off-host). A URL that parses and is https but points at any other hostname is rejected with this error naming the offending host. This prevents a crafted portal entry from making the scanner call an arbitrary internal service.

Solutions

  1. Rewrite the entry's api to the canonical origin: https://www.comeet.co/careers-api/2.0/company/<uid>/positions?token=<token>
  2. Find the company-uid and token from the tenant's actual careers-api link (inspect the network tab on their careers page) — Comeet has no slug→API shortcut
  3. Check for lookalike/typo hostnames (www.comeet.com, comeet.co without www) and correct them
  4. If you legitimately need a different host (self-hosted proxy), you must modify COMEET_API_HOST in providers/comeet.mjs — don't try to sneak it via the entry config

Example fix

// before
api: https://acme.comeet.co/careers-api/2.0/company/acme/positions?token=abc
// after
api: https://www.comeet.co/careers-api/2.0/company/acme/positions?token=abc
Defensive patterns

Strategy: validation

Validate before calling

const COMEET_API_HOST = 'www.comeet.co';
function isCanonicalComeetOrigin(raw) {
  try {
    const u = new URL(raw);
    return u.protocol === 'https:' && u.hostname === COMEET_API_HOST && u.pathname.startsWith('/careers-api/');
  } catch { return false; }
}
if (!isCanonicalComeetOrigin(entry.api)) throw new Error(`entry ${entry.name}: comeet api must be https://www.comeet.co/careers-api/...`);

Type guard

function isComeetApiUrl(raw) {
  if (typeof raw !== 'string' || !raw) return false;
  try {
    const u = new URL(raw);
    return u.protocol === 'https:' && u.hostname === 'www.comeet.co' && u.pathname.startsWith('/careers-api/');
  } catch { return false; }
}

Try / catch

try {
  assertComeetUrl(entry.api);
} catch (err) {
  if (String(err.message).includes('untrusted hostname')) {
    logger.error({entry: entry.name}, 'comeet api must point at https://www.comeet.co — rebuild the URL with the tenant uid and token');
  } else throw err;
}

Prevention

When it happens

Trigger: fetch() gets an entry whose api/careers_url is https and URL-parseable but whose hostname is not exactly 'www.comeet.co' — e.g. comeet.com (no www), a spoofed subdomain (www.comeet.co.evil.io), a company's own careers domain, or an IP address.

Common situations: Using the tenant's branded careers URL (acme.comeet.co or acme.com/jobs) instead of the fixed www.comeet.co careers-api origin; a typo like ww.comeet.co; hostile/misconfigured portal entries in a shared config; assuming Comeet works like Greenhouse where per-tenant subdomains (boards.greenhouse.io/<slug>) are the norm.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at providers/comeet.mjs:38

  try {
    parsed = new URL(raw);
  } catch {
    return false;
  }
  return parsed.protocol === 'https:' && parsed.hostname === COMEET_API_HOST && parsed.pathname.startsWith('/careers-api/');
}

/** @param {string} url */
function assertComeetUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`comeet: invalid URL: ${redactToken(url)}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`comeet: URL must use HTTPS: ${redactToken(url)}`);
  if (parsed.hostname !== COMEET_API_HOST)
    throw new Error(`comeet: untrusted hostname "${parsed.hostname}" — must be ${COMEET_API_HOST}`);
  if (!parsed.pathname.startsWith('/careers-api/'))
    throw new Error(`comeet: URL path must be the careers-api endpoint: ${redactToken(url)}`);
  return url;
}

// Redact the per-tenant ?token= so neither the (informational, possibly-logged)
// DetectHit url nor a thrown validation error carries the secret. Best-effort:
// falls back to a regex strip when the value can't be parsed as a URL.
function redactToken(url) {
  try {
    const parsed = new URL(url);
    if (parsed.searchParams.has('token')) parsed.searchParams.set('token', 'REDACTED');
    return parsed.href;
  } catch {
    return typeof url === 'string' ? url.replace(/([?&]token=)[^&#]*/gi, '$1REDACTED') : url;
  }
}

View on GitHub (pinned to aac998c7ed)