{"record":{"id":"2f4fe4d45b4f3e58","repo":"santifer/career-ops","slug":"senjob-invalid-url-url","errorCode":null,"errorMessage":"senjob: invalid URL: ${url}","messagePattern":"senjob: invalid URL: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/senjob.mjs","lineNumber":77,"sourceCode":"const POSTING_ANCHOR_RE =\n  /<a\\s[^>]*href=\"https:\\/\\/senjob\\.com\\/jobseekers\\/[^\"]*?_e_\\d+\\.html\"[^>]*>([\\s\\S]*?)<\\/a>/i;\n\n/** The machine-readable publication date, hidden next to its localized form. */\nconst HIDDEN_ISO_DATE_RE = /display:\\s*none;?\\s*\"?>\\s*(\\d{4}-\\d{2}-\\d{2})\\s*</i;\n\n/** @param {any} ctx @param {number} ms */\nfunction sleep(ctx, ms) {\n  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);\n  return new Promise((r) => setTimeout(r, ms));\n}\n\n/** @param {string} url */\nfunction assertSenjobUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`senjob: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`senjob: URL must use HTTPS: ${url}`);\n  if (parsed.hostname !== TRUSTED_HOST) {\n    throw new Error(`senjob: untrusted hostname \"${parsed.hostname}\" — must be ${TRUSTED_HOST}`);\n  }\n  return url;\n}\n\n/**\n * Collapse a markup fragment to its visible text.\n * Comments are stripped FIRST: the anchor bodies carry `<!-- d ico postulez -->`\n * between the title and a spacer image, and a naive tag strip would leave the\n * comment body sitting inside the title.\n * @param {string} fragment\n * @returns {string}\n */\nexport function visibleText(fragment) {\n  return decodeEntities(","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/providers/senjob.mjs#L59-L95","documentation":"assertSenjobUrl parses its argument with new URL(); malformed strings throw SyntaxError, which is caught and rethrown as this error prefixed with the senjob provider tag. It runs before every listing-page fetch, guaranteeing the provider never requests an unparseable URL.","triggerScenarios":"A string reaching the assertion that new URL() rejects: empty string, missing scheme ('senjob.com/offres-d-emploi.php'), whitespace/newlines, or a corrupted buildListUrl result. With the fixed LIST_URL constant this is only reachable via direct calls or edits to buildListUrl/entry-driven URL construction.","commonSituations":"Calling assertSenjobUrl from custom tooling with a configured (unvalidated) URL; someone changing LIST_URL to a relative path or templated placeholder; string concatenation injecting spaces.","solutions":["Pass only URLs built by buildListUrl(page), which always yields the absolute https://senjob.com/offres-d-emploi.php form","If calling directly, ensure the input is a fully-qualified absolute URL including the https:// scheme","Trim/normalize any user- or config-supplied string before validation"],"exampleFix":"// before\nassertSenjobUrl(entry.careers_url ?? 'senjob.com/offres-d-emploi.php');\n// after\nassertSenjobUrl(buildListUrl(1)); // https://senjob.com/offres-d-emploi.php","handlingStrategy":"validation","validationCode":"function isValidAbsoluteUrl(url) {\n  try { new URL(url); return true; } catch { return false; }\n}\nif (!isValidAbsoluteUrl(candidate)) throw new Error(`senjob: not an absolute URL: ${candidate}`);","typeGuard":"function isValidAbsoluteUrl(url) {\n  try { new URL(url); return typeof url === 'string' && url.length > 0; } catch { return false; }\n}","tryCatchPattern":"try {\n  assertSenjobUrl(url);\n} catch (err) {\n  if (String(err.message).startsWith('senjob: invalid URL')) {\n    console.error(`Rejected malformed URL: ${err.message}`);\n    return null;\n  }\n  throw err;\n}","preventionTips":["Build senjob URLs exclusively with buildListUrl(page) — the constant LIST_URL is always well-formed","Trim config/env strings before passing them into URL construction","Add a smoke test asserting buildListUrl output parses for pages 1..N","If extending the provider, keep URL construction and validation adjacent so malformed input cannot slip in"],"tags":["url-validation","configuration"],"backgroundTag":"invalid-url","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}