{"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/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/solidjobs.mjs#L11-L47","documentation":"assertUrl's third check requires the hostname to be exactly in ALLOWED_HOSTS, which contains only the bare apex 'solid.jobs'. Any other host — including 'www.solid.jobs' or a subdomain — is rejected as untrusted. This is an SSRF pin: only the canonical API host may be fetched.","triggerScenarios":"entry.careers_url hostname is 'www.solid.jobs', a regional subdomain, or an entirely different host that happens to serve a similar path. The bare apex solid.jobs is the only accepted value.","commonSituations":"A user copies a URL from the browser address bar that includes 'www.', or pastes a look-alike/aggregator domain. The provider intentionally does not allow subdomains.","solutions":["Use the bare apex: https://solid.jobs/public-api/offers/<division> (no www, no subdomain)","Remove any trailing port or userinfo from the URL","If you genuinely need a different SolidJobs host, it is unsupported — file an issue rather than widening ALLOWED_HOSTS without review"],"exampleFix":"# before\ncareers_url: https://www.solid.jobs/public-api/offers/it\n# after\ncareers_url: https://solid.jobs/public-api/offers/it","handlingStrategy":"validation","validationCode":"// Pin the solidjobs host before fetch.\nfunction isSolidJobsHost(v) {\n  try { return new URL(v).hostname === 'solid.jobs'; } catch { return false; }\n}\nif (!isSolidJobsHost(entry.careers_url)) {\n  console.warn(`${entry.name}: solidjobs requires hostname solid.jobs (no www/subdomain)`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the bare apex solid.jobs; never www.solid.jobs.","Lint provider: solidjobs entries for the exact hostname."],"tags":["ssrf-guard","url-validation","hostname","config","solidjobs","portals-yml"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}