{"record":{"id":"78c79debaecc8d01","repo":"santifer/career-ops","slug":"solidjobs-url-path-must-start-with-public-api-of","errorCode":null,"errorMessage":"solidjobs: URL path must start with /public-api/offers/: ${url}","messagePattern":"solidjobs: URL path must start with /public-api/offers/: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/solidjobs.mjs","lineNumber":31,"sourceCode":" * Validates that the provided URL is a trusted SolidJobs API endpoint.\n * Enforces HTTPS protocol, strict hostname matching, and required path prefix.\n * \n * @param {string} url - The URL string to validate.\n * @returns {string} The validated URL string.\n * @throws {Error} If the URL is malformed, uses non-HTTPS, has an untrusted host, or wrong path.\n */\nfunction assertUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`solidjobs: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`solidjobs: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_HOSTS.has(parsed.hostname))\n    throw new Error(`solidjobs: untrusted hostname \"${parsed.hostname}\" — must be solid.jobs`);\n  if (!parsed.pathname.startsWith('/public-api/offers/'))\n    throw new Error(`solidjobs: URL path must start with /public-api/offers/: ${url}`);\n  return url;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'solidjobs',\n\n  /**\n   * Attempts to detect if the provider can handle the given entry by checking the careers_url.\n   * * @param {{ careers_url?: string, name?: string }} entry - The configuration entry.\n   * @returns {{url: string} | null} An object with the matched URL, or null if not matched.\n   */\n  detect(entry) {\n    const url = entry.careers_url || '';\n    try {\n      const parsed = new URL(url);\n      if (parsed.hostname === 'solid.jobs' && parsed.pathname.startsWith('/public-api/offers/'))\n        return { url };","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/solidjobs.mjs#L13-L49","documentation":"The solidjobs provider validates every careers_url before fetching, as an SSRF/allowlist guard. This specific throw fires when the URL parses, uses HTTPS, and points at solid.jobs, but its pathname does not begin with /public-api/offers/ — i.e. the configured URL is not the public offers API endpoint the provider knows how to parse.","triggerScenarios":"A portals.yml entry with provider solidjobs has a careers_url pointing at a website page (e.g. https://solid.jobs/en or https://solid.jobs/offers/123) instead of the API path /public-api/offers/...; or someone pastes a shareable job link rather than the API URL.","commonSituations":"Copying the browser URL from a solid.jobs listing page instead of the API endpoint; a solid.jobs URL-shape change or a hand-edited config adding a query/path prefix; typo'd base URL in a template.","solutions":["Change careers_url so the path starts with /public-api/offers/ (e.g. https://solid.jobs/public-api/offers) and keep the protocol https:","If unsure of the endpoint, open the site's network tab and copy the XHR URL the offers page calls","Remove the entry if the provider is not actually wanted for that board"],"exampleFix":"// before\ncareers_url: 'https://solid.jobs/offers?city=Warsaw'\n// after\ncareers_url: 'https://solid.jobs/public-api/offers'","handlingStrategy":"validation","validationCode":"const u = new URL(entry.careers_url);\nif (u.protocol !== 'https:' || u.hostname !== 'solid.jobs' || !u.pathname.startsWith('/public-api/offers/')) {\n  throw new Error(`bad solidjobs careers_url: ${entry.careers_url}`);\n}","typeGuard":"function isValidSolidjobsUrl(u) {\n  try { const p = new URL(u); return p.protocol === 'https:' && p.hostname === 'solid.jobs' && p.pathname.startsWith('/public-api/offers/'); }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (e) {\n  if (String(e.message).includes('URL path must start with /public-api/offers/')) {\n    console.warn(`Skipping ${entry.name}: careers_url is not the offers API endpoint`);\n    return [];\n  }\n  throw e;\n}","preventionTips":["Always configure the /public-api/offers/ endpoint, not the website URL","Validate portals.yml entries with verify-pipeline.mjs after edits","Keep a canonical example entry per provider in a template"],"tags":["config","url-validation","ssrf-protection"],"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"}