{"record":{"id":"cb8655ec43900de4","repo":"santifer/career-ops","slug":"nofluffjobs-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"nofluffjobs: untrusted hostname \"${parsed.hostname}\" — must be nofluffjobs.com","messagePattern":"nofluffjobs: untrusted hostname \"(.+?)\" — must be nofluffjobs\\.com","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/nofluffjobs.mjs","lineNumber":23,"sourceCode":"// It intentionally returns only the core scanner job fields; richer skill and\n// salary metadata can be added later if the provider contract is expanded.\n\nconst ALLOWED_HOSTS = new Set(['nofluffjobs.com']);\nconst API_URL = 'https://nofluffjobs.com/api/search/posting';\nconst JOB_BASE = 'https://nofluffjobs.com/pl/job/';\nconst PAGE_SIZE = 20;\nconst MAX_PAGES = 5;\n\nfunction assertNoFluffUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`nofluffjobs: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`nofluffjobs: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_HOSTS.has(parsed.hostname)) {\n    throw new Error(`nofluffjobs: untrusted hostname \"${parsed.hostname}\" — must be nofluffjobs.com`);\n  }\n  return parsed;\n}\n\nfunction detectUrl(entry) {\n  const url = entry.api || entry.careers_url || '';\n  if (typeof url !== 'string' || !url.trim()) return null;\n  try {\n    return { url: assertNoFluffUrl(url).href };\n  } catch {\n    return null;\n  }\n}\n\nfunction normalizeLocation(posting) {\n  const parts = [];\n  if (posting?.fullyRemote || posting?.location?.fullyRemote) parts.push('Remote');\n  if (Array.isArray(posting?.location?.places)) {","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/nofluffjobs.mjs#L5-L41","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set the URL to exactly 'nofluffjobs.com' (no subdomain, no regional TLD).","If a regional host like nofluffjobs.pl is a legitimate NoFluffJobs endpoint, add it to ALLOWED_HOSTS: new Set(['nofluffjobs.com', 'nofluffjobs.pl']).","Verify the entry's provider field matches the URL — a mismatched provider/URL pair means the wrong provider is being dispatched."],"exampleFix":"// before\nconst ALLOWED_HOSTS = new Set(['nofluffjobs.com']);\n// entry: https://nofluffjobs.pl/api/search/posting → throws\n\n// after — add the regional host if it's a legitimate NFJ endpoint\nconst ALLOWED_HOSTS = new Set(['nofluffjobs.com', 'nofluffjobs.pl']);","handlingStrategy":"validation","validationCode":"const ALLOWED_HOSTS = new Set(['nofluffjobs.com']);\n\n/** Check hostname is allowlisted before calling the provider. */\nfunction isTrustedNoFluffUrl(url) {\n  try { return ALLOWED_HOSTS.has(new URL(url).hostname); } catch { return false; }\n}\n\nif (!isTrustedNoFluffUrl(entry.api)) {\n  console.warn(`nofluffjobs entry ${entry.name} has untrusted host`);\n  continue;\n}","typeGuard":"/** @param {string} url @returns {boolean} */\nfunction isNoFluffHost(url) {\n  try { return new URL(url).hostname === 'nofluffjobs.com'; } catch { return false; }\n}","tryCatchPattern":"try {\n  await nofluffProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('untrusted hostname')) {\n    console.warn(`nofluffjobs entry ${entry.name} wrong host — fix portals.yml`);\n    continue;\n  }\n  throw err;\n}","preventionTips":["Ensure nofluffjobs URLs use exactly 'nofluffjobs.com' (no www., no .pl).","If regional hosts are legitimate, add them to ALLOWED_HOSTS in the provider.","Run a config audit checking provider/hostname consistency."],"tags":["url-validation","ssrf-guard","hostname-allowlist","nofluffjobs","security"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}