{"record":{"id":"b9b00543349d5c8f","repo":"santifer/career-ops","slug":"nofluffjobs-invalid-url-url","errorCode":null,"errorMessage":"nofluffjobs: invalid URL: ${url}","messagePattern":"nofluffjobs: invalid URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/nofluffjobs.mjs","lineNumber":19,"sourceCode":"// @ts-check\n/** @typedef {import('./_types.js').Provider} Provider */\n\n// NoFluffJobs provider — hits the public search posting API.\n// 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","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/nofluffjobs.mjs#L1-L37","documentation":"Thrown by nofluffjobs' assertNoFluffUrl() when new URL(url) throws — the URL is syntactically invalid. This is the first gate of a three-stage SSRF guard (valid URL → HTTPS → trusted host) that constrains all NoFluffJobs API calls to nofluffjobs.com. The guard is invoked from both detectUrl() and fetch().","triggerScenarios":"Called with an unparseable URL string: empty string, undefined, a value with spaces/control characters, or a schemeless path like 'nofluffjobs.com/api/search/posting'. In detectUrl() this is caught and returns null, but direct calls to assertNoFluffUrl() (e.g. from fetch() or tests) surface the throw.","commonSituations":"A portals.yml entry has api or careers_url left empty, set to null, or containing a typo. A config migration or YAML parsing edge case produces a non-string value. A developer passes a relative path expecting it to be resolved against a base URL (this provider does no base resolution).","solutions":["Log the url value passed to assertNoFluffUrl to identify the malformed string.","Ensure the portals.yml entry for nofluffjobs has api or careers_url as a fully-qualified https://nofluffjobs.com/... URL.","If no api/careers_url is needed (the provider uses API_URL constant internally), remove the misconfigured field so detectUrl() returns the default."],"exampleFix":"// before\n// portals.yml has a typo or missing scheme:\njob_boards:\n  nofluff:\n    provider: nofluffjobs\n    api: 'nofluffjobs.com/api/search/posting'  // missing https://\n\n// after\njob_boards:\n  nofluff:\n    provider: nofluffjobs\n    api: 'https://nofluffjobs.com/api/search/posting'","handlingStrategy":"validation","validationCode":"/** Validate a URL string is parseable before passing to assertNoFluffUrl. */\nfunction isValidUrlString(url) {\n  return typeof url === 'string'\n    && url.length > 0\n    && /^https?:\\/\\/.+/i.test(url)\n    && (() => { try { new URL(url); return true; } catch { return false; } })();\n}\n\nif (!isValidUrlString(entry.api) && !isValidUrlString(entry.careers_url)) {\n  console.warn(`nofluffjobs entry ${entry.name} has no valid URL`);\n  continue;\n}","typeGuard":"/** @param {unknown} url @returns {url is string} */\nfunction isParseableUrl(url) {\n  if (typeof url !== 'string' || !url) return false;\n  try { new URL(url); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  await nofluffProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).startsWith('nofluffjobs: invalid URL')) {\n    console.warn(`skipping nofluffjobs entry ${entry.name}: malformed URL`);\n    continue;\n  }\n  throw err;\n}","preventionTips":["Validate URL fields in portals.yml at config-load time.","Always include https:// scheme in portal URLs.","Call detect(entry) before fetch() and skip null results."],"tags":["url-validation","ssrf-guard","nofluffjobs","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}