{"record":{"id":"afbb8898bf94e4bc","repo":"santifer/career-ops","slug":"smartrecruiters-invalid-url-url","errorCode":null,"errorMessage":"smartrecruiters: invalid URL: ${url}","messagePattern":"smartrecruiters: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/smartrecruiters.mjs","lineNumber":20,"sourceCode":"/** @typedef {import('./_types.js').Provider} Provider */\n\n// SmartRecruiters provider — hits the public postings API.\n// Auto-detects from careers_url pattern\n// `https://(careers|jobs).smartrecruiters.com/<slug>`. A tracked_companies\n// entry can also set `provider: smartrecruiters` explicitly to bypass\n// detection (useful when the public careers URL is a branded custom domain).\n\nconst ALLOWED_SMARTRECRUITERS_HOSTS = new Set(['api.smartrecruiters.com']);\nconst SR_CAREERS_HOSTS = new Set(['careers.smartrecruiters.com', 'jobs.smartrecruiters.com']);\nconst SR_PAGE_SIZE = 100;\nconst SR_MAX_PAGES = 50;  // safety cap (5000 postings @ 100/page)\n\nfunction assertSmartRecruitersUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`smartrecruiters: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`smartrecruiters: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_SMARTRECRUITERS_HOSTS.has(parsed.hostname)) {\n    throw new Error(`smartrecruiters: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_SMARTRECRUITERS_HOSTS].join(', ')}`);\n  }\n  return url;\n}\n\nfunction resolveSlug(entry) {\n  // entry.api takes precedence over careers_url (mirrors greenhouse/ashby) so a\n  // branded page (e.g. https://jobs.continental.com) can stay as careers_url\n  // while the SmartRecruiters slug is pinned via\n  // api: https://careers.smartrecruiters.com/<slug> in portals.yml.\n  for (const raw of [entry.api, entry.careers_url]) {\n    if (typeof raw !== 'string' || !raw) continue;\n    let parsed;\n    try {\n      parsed = new URL(raw);","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/smartrecruiters.mjs#L2-L38","documentation":"assertSmartRecruitersUrl throws this when new URL(url) raises — the string is not a parseable absolute URL. This is the structural guard preceding the HTTPS and hostname-allowlist checks. smartrecruiters builds API URLs from a resolved slug and the fixed api.smartrecruiters.com host, so a raw parse failure typically indicates a malformed constructed URL or a bad external input.","triggerScenarios":"The URL passed in has no protocol, contains illegal characters, or is an empty/partial string that the URL constructor rejects.","commonSituations":"The slug resolution produced an empty string, yielding a malformed API URL; an external caller passed a non-URL value; a config field contained whitespace or smart quotes.","solutions":["Log the URL string passed into the guard to identify the malformation.","Verify the slug was resolved to a non-empty value before URL construction.","Ensure the API URL is built from the documented base (https://api.smartrecruiters.com/v1/companies/<slug>/postings).","Strip whitespace and validate with new URL() before invoking the provider."],"exampleFix":"// before\nassertSmartRecruitersUrl(slug); // slug is a bare token, not a URL\n// after\nassertSmartRecruitersUrl(`https://api.smartrecruiters.com/v1/companies/${encodeURIComponent(slug)}/postings`);","handlingStrategy":"validation","validationCode":"function isParseableUrl(s) {\n  try { new URL(s); return true; } catch { return false; }\n}\n// smartrecruiters builds API URLs from a slug — ensure the slug is non-empty\nif (!slug || typeof slug !== 'string') {\n  throw new Error('smartrecruiters: empty slug — cannot build API URL');\n}\nconst apiUrl = `https://api.smartrecruiters.com/v1/companies/${encodeURIComponent(slug)}/postings`;\nif (!isParseableUrl(apiUrl)) throw new Error('smartrecruiters: built URL is invalid');","typeGuard":"null","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (e) {\n  if (/smartrecruiters: invalid URL/.test(e.message)) {\n    console.error(`[bug] smartrecruiters URL construction broken — check slug resolution`);\n  } else throw e;\n}","preventionTips":["Ensure the slug resolves to a non-empty string before building the API URL.","Unit-test the URL builder for the smartrecruiters provider.","Validate the built URL parses before invoking the provider."],"tags":["url-validation","provider","smartrecruiters","malformed-url"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}