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

  1. Correct the hostname in the config to exactly the trusted Muse host the provider expects.
  2. Check the TRUSTED_HOST constant in providers/themuse.mjs and match your URL against it character-for-character (no subdomain additions).
  3. Remove mirror/lookalike entries from portals.yml — only the official domain is fetchable by design.
  4. 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

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


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)