santifer/career-ops · error · Error

manfred: untrusted hostname

Error message

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

What it means

assertManfredUrl() pins the hostname to a single trusted host (TRUSTED_HOST, getmanfred.com). Any other hostname is rejected with this error, which names both the offending hostname and the required one. This prevents SSRF-style misuse where a crafted config points the fetcher at an arbitrary server.

Solutions

  1. Point the URL at the exact trusted host getmanfred.com (matching TRUSTED_HOST in providers/manfred.mjs).
  2. If you must test against a local/mock server, inject a fetch context (ctx.fetchJson) in tests instead of changing the hostname.
  3. If the trusted host has legitimately changed, update TRUSTED_HOST in the provider source deliberately — not by editing the URL alone.

Example fix

// before
careers_url: https://api.getmanfred.com/feed
// after
careers_url: https://getmanfred.com/feed
Defensive patterns

Strategy: validation

Validate before calling

const TRUSTED = 'getmanfred.com';
const u = new URL(entry.careers_url);
if (u.hostname !== TRUSTED) throw new Error(`${entry.name}: ${u.hostname} is not the trusted host ${TRUSTED}`);

Type guard

const isTrustedHost = (v, host) => { try { return new URL(v).hostname === host; } catch { return false; } };

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('manfred: untrusted hostname')) {
    console.error('Point the entry back at the pinned host; mirrors/proxies are not allowed');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: A careers_url or feed URL pointing at a look-alike or mirror host (e.g. https://getmanfred.net, https://api.getmanfred.com, https://evil.example) instead of the exact trusted host; a proxy hostname substituted in config.

Common situations: Using a CDN or regional mirror of the Manfred feed; routing through a corporate proxy by rewriting the host; a typo in the domain (.io vs .com); intentionally pointing at a mock server whose host isn't the pinned one.

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@e7abd431fc (2026-09-16). Data as JSON: /api/errors/4bc2e5b6af01af44. Report an issue: GitHub.

Appendix: source

Thrown at providers/manfred.mjs:45

// so any network jitter aborts it. Give it real headroom rather than relying
// on retry alone to paper over a structurally near-timeout request.
const FETCH_TIMEOUT_MS = 25_000;
const TRUSTED_HOST = 'www.getmanfred.com';
const OFFER_BASE = 'https://www.getmanfred.com/ofertas-empleo';
const VALID_LANGS = ['EN', 'ES'];
const DEFAULT_LANG = 'EN';

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

/** Resolve the feed language: `lang` on the entry, uppercased, else EN. */
export function resolveLang(entry) {
  const raw = typeof entry?.lang === 'string' ? entry.lang.trim().toUpperCase() : '';
  return VALID_LANGS.includes(raw) ? raw : DEFAULT_LANG;
}

// The feed reports currency as the SYMBOL, not an ISO code, and the observed
// values include a narrow-no-break-space variant of the euro sign. scan.mjs's
// salary_filter compares currencies case-insensitively as plain strings, so a
// symbol would never match a user's `currency: EUR` — map to ISO, and drop the
// field entirely rather than guess when the symbol is unknown.
const CURRENCY_BY_SYMBOL = new Map([
  ['€', 'EUR'],
  ['£', 'GBP'],

View on GitHub (pinned to e7abd431fc)