santifer/career-ops · error

builtin: untrusted hostname

Error message

builtin: untrusted hostname "${parsed.hostname}" — must be one of ${[...new Set(HOSTS.values())].join(', ')}

What it means

assertHost in the builtin provider throws this when the URL is valid https but its hostname is not in the static HOSTS allowlist of Built In city domains (e.g. www.builtinseattle.com, www.builtinchicago.com). The host comes from user config, so this allowlist re-check is the only barrier preventing the provider from fetching an arbitrary attacker-chosen URL (SSRF).

Solutions

  1. Set host to one of the allowlisted values listed verbatim in the error message (e.g. 'www.builtinseattle.com').
  2. Use the exported resolveHost() helper to check a value: it returns null when the host is not allowlisted.
  3. Fix typos in the city domain and drop any scheme/path from the config value.
  4. If a genuinely new Built In city is missing, add it to the HOSTS map in providers/builtin.mjs rather than bypassing the guard.

Example fix

# before
host: www.builtin.com
# after
host: www.builtinseattle.com
Defensive patterns

Strategy: validation

Validate before calling

import { resolveHost } from './providers/builtin.mjs';
const canonical = resolveHost(cfg.host);
if (canonical === null) {
  throw new Error(`builtin host "${cfg.host}" is not an allowlisted Built In city domain`);
}

Type guard

function isAllowlistedBuiltinHost(host) { return typeof host === 'string' && resolveHost(host) !== null; }

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('builtin: untrusted hostname')) {
    console.warn(`Skipping ${entry.name}: host not a Built In city domain`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: A portals.yml entry with host set to an unknown domain ('jobs.acme.com', 'www.builtin.com', a typo like 'builtinseatle.com'), a non-city Built In property, or any unrelated host routed into the builtin provider's fetch path.

Common situations: Misspelling a city domain; assuming 'builtin.com' itself is fetchable when only per-city sites are allowlisted; a company careers page that merely links to Built In being configured as if it were a board; adding a new Built In city that this version of the provider does not know yet.

Related errors


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

Appendix: source

Thrown at providers/builtin.mjs:174

 * SSRF guard — every request URL passes through here before it is fetched. The
 * host comes from config, so this is the only thing standing between a
 * portals entry and an arbitrary fetch target. It checks the RESOLVED host
 * against the allowlist again rather than trusting the caller.
 *
 * @param {string} url
 * @returns {string}
 */
function assertHost(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`builtin: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`builtin: URL must use HTTPS: ${url}`);
  const host = parsed.hostname.toLowerCase();
  if (HOSTS.get(host) !== host) {
    throw new Error(`builtin: untrusted hostname "${parsed.hostname}" — must be one of ${[...new Set(HOSTS.values())].join(', ')}`);
  }
  return url;
}

/** @param {string} s */
function stripTags(s) {
  return decodeEntities(String(s).replace(/<[^>]*>/g, ' ')).replace(/\s+/g, ' ').trim();
}

/**
 * Text of the first element following an icon marker inside a card.
 * Anchoring on the icon class (rather than on field order) is what keeps this
 * readable when Built In reshuffles the card layout.
 *
 * @param {string} seg  card HTML
 * @param {string} icon FontAwesome class, e.g. 'fa-location-dot'
 * @returns {string} '' when the icon or its text is absent
 */

View on GitHub (pinned to aac998c7ed)