santifer/career-ops · error · Error

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

Error message

yourator: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}

What it means

After confirming HTTPS, assertYouratorUrl pins the hostname to TRUSTED_HOST (www.yourator.co, per the provider's documented API at https://www.yourator.co/api/v4/jobs). Any other hostname is rejected to prevent SSRF-style abuse where a crafted config points the fetcher at an internal or attacker-controlled host. The error names the offending hostname and the required one.

Source

Thrown at providers/yourator.mjs:85

const FEED_BASE = `${SITE_ORIGIN}/api/v4/jobs`;
const TRUSTED_HOST = 'www.yourator.co';
// Safety bound only — the loop stops on payload.hasMore. The live board was 88
// pages on 2026-08-18; this leaves room to grow without silently truncating.
const DEFAULT_MAX_PAGES = 120;
const MAX_PAGES_CAP = 500;
const PAGE_DELAY_MS = 200;

/** @param {string} url */
function assertYouratorUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`yourator: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`yourator: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== TRUSTED_HOST) {
    throw new Error(`yourator: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
  }
  return url;
}

/** Resolve the page cap: a positive integer `max_pages` on the entry, capped. */
function resolveMaxPages(entry) {
  const v = entry?.max_pages;
  if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES_CAP);
  return DEFAULT_MAX_PAGES;
}

/**
 * Canonical URL for a posting — Source Indexing Policy rule 2, "the shortest
 * verifiable path to the employer the source exposes".
 *
 * Prefers `thirdPartyUrl` (the employer's own ATS page), with the board's
 * `utm_*` ad parameters stripped. Accepts any https: origin — the value is
 * display-only and never fetched here. Falls back to the Yourator posting page

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Set the hostname to exactly the trusted host: https://www.yourator.co/... (with www.).
  2. Compare `new URL(url).hostname` against 'www.yourator.co' before invoking the provider.
  3. Remove any host-rewriting proxy/mirror from the configuration; the provider only fetches the official domain.

Example fix

// before
careers_url: https://yourator.co/jobs

// after
careers_url: https://www.yourator.co/jobs
Defensive patterns

Strategy: validation

Validate before calling

const TRUSTED_HOST = 'www.yourator.co';
export function isTrustedYouratorUrl(u) {
  try { return new URL(u).hostname === TRUSTED_HOST; } catch { return false; }
}
if (!isTrustedYouratorUrl(entry.careers_url)) entry.careers_url = 'https://www.yourator.co/jobs';

Type guard

function isYouratorUrl(u) {
  try {
    const p = new URL(u);
    return p.protocol === 'https:' && p.hostname === 'www.yourator.co';
  } catch { return false; }
}

Try / catch

try {
  scanYourator(entry);
} catch (e) {
  if (e.message.includes('untrusted hostname')) {
    console.error(`Bad host in ${entry.careers_url}; defaulting to https://www.yourator.co/jobs`);
    scanYourator({ ...entry, careers_url: 'https://www.yourator.co/jobs' });
  } else throw e;
}

Prevention

When it happens

Trigger: assertYouratorUrl receives an https: URL whose parsed.hostname !== TRUSTED_HOST — e.g. https://yourator.co/jobs (missing www.), https://api.yourator.co/v4/jobs, https://evil.example.com/jobs, or a copy of the feed URL served from a mirror domain.

Common situations: Omitting the www. prefix because the site resolves either way in a browser; pointing at an API subdomain copied from a blog post; a malicious or typosquatted mirror in config; environment-specific overrides that rewrite the host.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/3322aabccc6ba045. Report an issue: GitHub.