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
- Set host to one of the allowlisted values listed verbatim in the error message (e.g. 'www.builtinseattle.com').
- Use the exported resolveHost() helper to check a value: it returns null when the host is not allowlisted.
- Fix typos in the city domain and drop any scheme/path from the config value.
- 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
- Pick hosts from the allowlist echoed in the error message, not from memory.
- Call resolveHost() at config-load time to reject unknown hosts before any fetch.
- Remember only per-city sites (www.builtin<city>.com) are allowed, not builtin.com itself.
- To support a new city, extend the HOSTS map in providers/builtin.mjs — never bypass the guard.
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
- breezy: untrusted hostname
- careerviet: untrusted hostname
- arbeitnow: untrusted hostname
- ashby: untrusted hostname
- bamboohr: untrusted hostname
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)