{"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":"validation","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/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/nofluffjobs.mjs#L5-L41","documentation":"assertNoFluffUrl restricts hostnames to the ALLOWED_HOSTS set (nofluffjobs.com and its official API hosts). This error is thrown when parsed.hostname is not in that set, blocking third-party, look-alike, or typo'd domains from being queried through this provider.","triggerScenarios":"A careers_url/api entry whose hostname is not in ALLOWED_HOSTS — e.g. 'www.nofluffjobs.de' if only the .com host is allowed, a country subdomain, a staging host, or an entirely different company's board.","commonSituations":"Copy-paste from another provider's config; regional TLD variants (nofluffjobs.pl/hu/de) not in the allowlist; host renamed after a vendor change; typo like 'nofluffjobs.com.evil.io'.","solutions":["Use a hostname that is in ALLOWED_HOSTS (check the constant at the top of providers/nofluffjobs.mjs for the exact list)","If a regional variant is legitimately needed, add it to ALLOWED_HOSTS deliberately and review the change","Move entries for other companies/providers to their correct provider config","Fix typos and stray subdomains in the configured URL"],"exampleFix":"// before\nassertNoFluffUrl('https://jobs.example.com/api');\n// after\nassertNoFluffUrl('https://nofluffjobs.com/api/search/posting');","handlingStrategy":"validation","validationCode":"const parsed = new URL(url);\nif (!ALLOWED_HOSTS.has(parsed.hostname)) throw new Error(`host not allowed: ${parsed.hostname}`);","typeGuard":"function isAllowedHost(u, allowed) {\n  try { return allowed.has(new URL(u).hostname); } catch { return false; }\n}","tryCatchPattern":"try {\n  await nofluffjobs.fetch(entry, ctx);\n} catch (e) {\n  if (String(e.message).startsWith('nofluffjobs: untrusted hostname')) {\n    log.warn(`Entry for ${entry.company} is not a nofluffjobs.com URL; reassign to correct provider`);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Run node audit-portals.mjs after editing provider URLs to catch wrong-entity boards","Check ALLOWED_HOSTS before adding regional or subdomain variants","Keep provider-specific URLs under their own provider config keys","Never append a trusted hostname to an untrusted domain — validate the full host"],"tags":["url-validation","security","hostname","allowlist"],"backgroundTag":"invalid-url","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}