{"record":{"id":"95705e26b2157680","repo":"santifer/career-ops","slug":"solidjobs-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"solidjobs: untrusted hostname \"${parsed.hostname}\" — must be solid.jobs","messagePattern":"solidjobs: untrusted hostname \"(.+?)\" — must be solid\\.jobs","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/solidjobs.mjs","lineNumber":29,"sourceCode":"\n/**\n * 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);","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/solidjobs.mjs#L11-L47","documentation":"assertUrl pins the hostname to ALLOWED_HOSTS (only solid.jobs): a valid HTTPS URL on any other host — www.solid.jobs, a mirror, a branded domain — is rejected. This is a host-pinning/SSRF guard ensuring the provider only ever fetches the official SolidJobs public API with its /public-api/offers/ path prefix.","triggerScenarios":"careers_url pointing at https://www.solid.jobs/... (www fails the exact-host check), a typo'd host (solidjob.com, solid.jobs.eu), or a proxy/rewrite host; calling assertUrl with a non-API page on solid.jobs would pass the host check but then fail the path check instead.","commonSituations":"Adding www. to the domain out of habit; copying a marketing-site URL (solid.jobs landing page) instead of the API URL; typos; confusing the public careers site with the public-api endpoint.","solutions":["Use exactly https://solid.jobs/public-api/offers/<division> — no www, no alternate TLD","Rely on detect(), which auto-accepts only solid.jobs + /public-api/offers/ URLs, rather than hand-built URLs","Fix hostname typos in portals.yml and re-run","If SolidJobs ever serves the API from a new official host, update ALLOWED_HOSTS in providers/solidjobs.mjs"],"exampleFix":"// before\ncareers_url: 'https://www.solid.jobs/public-api/offers/it'  // www not allowed\n// after\ncareers_url: 'https://solid.jobs/public-api/offers/it'","handlingStrategy":"validation","validationCode":"function isTrustedSolidjobsUrl(url) {\n  try {\n    const u = new URL(url);\n    return u.protocol === 'https:' &&\n      u.hostname === 'solid.jobs' &&\n      u.pathname.startsWith('/public-api/offers/');\n  } catch { return false; }\n}\nif (!isTrustedSolidjobsUrl(entry.careers_url)) throw new Error('must be https://solid.jobs/public-api/offers/<division>');","typeGuard":"const isSolidjobsApiUrl = (url) => {\n  try {\n    const u = new URL(url);\n    return u.hostname === 'solid.jobs' && u.pathname.startsWith('/public-api/offers/');\n  } catch { return false; }\n};","tryCatchPattern":"try {\n  await solidjobsProvider.fetch(entry, ctx);\n} catch (e) {\n  if (String(e.message).includes('untrusted hostname')) {\n    console.error(`Entry ${entry.name}: host must be exactly solid.jobs (no www, no TLD variants)`);\n  } else throw e;\n}","preventionTips":["Use the exact host solid.jobs — the www variant fails the exact-match check","Ensure the path starts with /public-api/offers/ (the host check alone is not enough; the next check rejects wrong paths)","Copy URLs from the documented API form rather than the marketing site","Run detect() against the entry first — it only matches fully valid solid.jobs API URLs"],"tags":["url-validation","ssrf-guard","hostname-allowlist","config-error"],"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"}