santifer/career-ops · error · Error
themuse: untrusted hostname
Error message
themuse: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST} What it means
assertMuseUrl() allow-lists a single trusted hostname (TRUSTED_HOST). Even a valid HTTPS URL is rejected if its hostname is anything other than the Muse domain. This SSRF-style guard stops crafted or misconfigured URLs from redirecting the provider at arbitrary hosts.
Solutions
- Correct the hostname in the config to exactly the trusted Muse host the provider expects.
- Check the TRUSTED_HOST constant in providers/themuse.mjs and match your URL against it character-for-character (no subdomain additions).
- Remove mirror/lookalike entries from portals.yml — only the official domain is fetchable by design.
- If a new legitimate host must be supported, extend TRUSTED_HOST to an allow-list in a reviewed change, not ad hoc.
Example fix
// before url: https://api.themuse.com/v2/jobs?page=0 // after url: https://www.themuse.com/api/v2/jobs?page=0
Defensive patterns
Strategy: validation
Validate before calling
const TRUSTED_MUSE_HOST = 'www.themuse.com'; // match the provider's TRUSTED_HOST
function isTrustedMuseUrl(value) {
try {
const u = new URL(value);
return u.protocol === 'https:' && u.hostname === TRUSTED_MUSE_HOST;
} catch { return false; }
}
// filter entries: entries.filter(e => !isTrustedMuseUrl(e.url)) → correct before scanning Type guard
const isMuseHost = (v) => {
try { return new URL(v).hostname === 'www.themuse.com'; } catch { return false; }
}; Try / catch
try {
return await provider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).includes('untrusted hostname')) {
console.warn(`${entry.name} points at a non-Muse host; remove or correct the URL`);
return null;
}
throw err;
} Prevention
- Copy portal URLs from the provider's own docs/examples rather than hand-typing hostnames
- Keep the trusted-host constant as the single source of truth and validate config against it in CI
- Never add mirror or lookalike domains — treat the allow-list as intentional and reviewed
- Watch for template variables silently substituting the wrong hostname into URLs
When it happens
Trigger: assertMuseUrl(url) receives a valid https URL whose parsed.hostname !== TRUSTED_HOST — e.g. a mirror domain, a subdomain typo (themuse.co vs themuse.com), or an attacker-controlled host injected via config.
Common situations: A typo like 'www.themuse.org' or a leading 'api.' added by hand; a regional mirror pasted into portals.yml; a template variable resolving to the wrong host; someone attempting to point the provider at an internal host.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- arbeitnow: untrusted hostname
- ashby: untrusted hostname
- bamboohr: untrusted hostname
- breezy: untrusted hostname
- builtin: untrusted hostname
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/58dc042d93f9116e.
Report an issue: GitHub.
Appendix: source
Thrown at providers/themuse.mjs:92
const retryAfterMs = parseRetryAfterMs(err?.retryAfter);
const delayMs = retryAfterMs !== null ? Math.min(retryAfterMs, RETRY_MAX_DELAY_MS * 4) : (backoff + Math.random() * 250);
await sleep(delayMs, ctx);
}
}
throw lastErr;
}
/** @param {string} url */
function assertMuseUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`themuse: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`themuse: URL must use HTTPS: ${url}`);
if (parsed.hostname !== TRUSTED_HOST) {
throw new Error(`themuse: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
}
return url;
}
/**
* Normalize a single result from the Muse API response. Exported for unit tests.
*
* Field mapping:
* name → title
* refs.landing_page → url
* company.name → company
* locations[0].name → location
*
* Returns null when required fields (title or url) are missing or invalid.
*
* @param {any} j
* @returns {{ title: string, url: string, company: string, location: string } | null}
*/View on GitHub (pinned to aac998c7ed)