santifer/career-ops · error · Error

nofluffjobs: untrusted hostname "${parsed.hostname}" — must

Error message

nofluffjobs: untrusted hostname "${parsed.hostname}" — must be nofluffjobs.com

What it means

Thrown by nofluffjobs' assertNoFluffUrl() when the parsed hostname is not in the ALLOWED_HOSTS set (currently only 'nofluffjobs.com'). This is the third SSRF gate pinning all requests to the legitimate NoFluffJobs domain. Unlike a single-host constant, it uses a Set, signaling that additional regional hosts could be added.

Source

Thrown at providers/nofluffjobs.mjs:23

// It intentionally returns only the core scanner job fields; richer skill and
// salary metadata can be added later if the provider contract is expanded.

const ALLOWED_HOSTS = new Set(['nofluffjobs.com']);
const API_URL = 'https://nofluffjobs.com/api/search/posting';
const JOB_BASE = 'https://nofluffjobs.com/pl/job/';
const PAGE_SIZE = 20;
const MAX_PAGES = 5;

function assertNoFluffUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`nofluffjobs: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`nofluffjobs: URL must use HTTPS: ${url}`);
  if (!ALLOWED_HOSTS.has(parsed.hostname)) {
    throw new Error(`nofluffjobs: untrusted hostname "${parsed.hostname}" — must be nofluffjobs.com`);
  }
  return parsed;
}

function detectUrl(entry) {
  const url = entry.api || entry.careers_url || '';
  if (typeof url !== 'string' || !url.trim()) return null;
  try {
    return { url: assertNoFluffUrl(url).href };
  } catch {
    return null;
  }
}

function normalizeLocation(posting) {
  const parts = [];
  if (posting?.fullyRemote || posting?.location?.fullyRemote) parts.push('Remote');
  if (Array.isArray(posting?.location?.places)) {

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set the URL to exactly 'nofluffjobs.com' (no subdomain, no regional TLD).
  2. If a regional host like nofluffjobs.pl is a legitimate NoFluffJobs endpoint, add it to ALLOWED_HOSTS: new Set(['nofluffjobs.com', 'nofluffjobs.pl']).
  3. Verify the entry's provider field matches the URL — a mismatched provider/URL pair means the wrong provider is being dispatched.

Example fix

// before
const ALLOWED_HOSTS = new Set(['nofluffjobs.com']);
// entry: https://nofluffjobs.pl/api/search/posting → throws

// after — add the regional host if it's a legitimate NFJ endpoint
const ALLOWED_HOSTS = new Set(['nofluffjobs.com', 'nofluffjobs.pl']);
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED_HOSTS = new Set(['nofluffjobs.com']);

/** Check hostname is allowlisted before calling the provider. */
function isTrustedNoFluffUrl(url) {
  try { return ALLOWED_HOSTS.has(new URL(url).hostname); } catch { return false; }
}

if (!isTrustedNoFluffUrl(entry.api)) {
  console.warn(`nofluffjobs entry ${entry.name} has untrusted host`);
  continue;
}

Type guard

/** @param {string} url @returns {boolean} */
function isNoFluffHost(url) {
  try { return new URL(url).hostname === 'nofluffjobs.com'; } catch { return false; }
}

Try / catch

try {
  await nofluffProvider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).includes('untrusted hostname')) {
    console.warn(`nofluffjobs entry ${entry.name} wrong host — fix portals.yml`);
    continue;
  }
  throw err;
}

Prevention

When it happens

Trigger: A valid HTTPS URL whose hostname is not exactly 'nofluffjobs.com': e.g. 'www.nofluffjobs.com', 'nofluffjobs.pl', or a completely different domain. Since NoFluffJobs operates primarily in Poland, a common variant is nofluffjobs.pl. An attacker-controlled domain or a corporate proxy domain also triggers this.

Common situations: Using a regional variant (nofluffjobs.pl) that isn't in the allowlist. Adding www. or a locale subdomain. Pointing at a mirror or CDN domain. The entry was templated from another provider and the domain wasn't changed.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/cb8655ec43900de4. Report an issue: GitHub.