{"record":{"id":"d1ff1deddf8eef1e","repo":"santifer/career-ops","slug":"remotli-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"remotli: untrusted hostname \"${parsed.hostname}\" — must be remotli.ch","messagePattern":"remotli: untrusted hostname \"(.+?)\" — must be remotli\\.ch","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/remotli.mjs","lineNumber":242,"sourceCode":"  if (postedAt !== undefined) out.postedAt = postedAt;\n\n  const salary = resolveSalary(job);\n  if (salary) out.salary = salary;\n\n  return out;\n}\n\n/** Guard the API URL: HTTPS + remotli.ch only. */\nfunction assertRemotliUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`remotli: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`remotli: URL must use HTTPS: ${url}`);\n  if (!HOST_RE.test(parsed.hostname))\n    throw new Error(`remotli: untrusted hostname \"${parsed.hostname}\" — must be remotli.ch`);\n  return url;\n}\n\n/** @type {Provider} */\nexport default {\n  id: 'remotli',\n\n  detect(entry) {\n    const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';\n    if (!raw) return null;\n    let parsed;\n    try {\n      parsed = new URL(raw);\n    } catch {\n      return null;\n    }\n    if (parsed.protocol !== 'https:') return null;\n    if (!HOST_RE.test(parsed.hostname)) return null;","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/remotli.mjs#L224-L260","documentation":"assertRemotliUrl throws when parsed.hostname fails HOST_RE — the host must be exactly 'remotli.ch' (no subdomains, no branded variants). This is the strictest hostname guard in the set: remotli is a single fixed host, not a per-tenant subdomain provider. Any deviation is treated as an SSRF attempt.","triggerScenarios":"The hostname is www.remotli.ch, api.remotli.ch, a branded domain, or any host other than exactly remotli.ch; the URL points to a look-alike domain.","commonSituations":"Someone added a www. or api. prefix to the ORIGIN constant; a config value was copied from a browser address bar that included a subdomain; the entry was misrouted to remotli when it belongs elsewhere.","solutions":["Set ORIGIN to 'https://remotli.ch' with no subdomain prefix.","Strip any leading subdomain label from the hostname before validation.","Confirm the entry should use the remotli provider — if the board is on a different host, choose the right provider."],"exampleFix":"// before\nconst ORIGIN = 'https://www.remotli.ch';\n// after\nconst ORIGIN = 'https://remotli.ch';","handlingStrategy":"validation","validationCode":"const REMOTLI_HOST = 'remotli.ch';\nfunction isRemotliHost(url) {\n  try { return new URL(url).hostname === REMOTLI_HOST; } catch { return false; }\n}\nif (!isRemotliHost(someUrl)) {\n  console.warn(`skip: not remotli.ch — got ${someUrl}`);\n}","typeGuard":"null","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (e) {\n  if (/untrusted hostname/.test(e.message)) {\n    console.error('[bug] remotli ORIGIN has a subdomain — must be exactly remotli.ch');\n  } else throw e;\n}","preventionTips":["Use exactly 'remotli.ch' with no www. or api. prefix.","Treat a hostname-guard failure as a code/config bug, not user data.","Unit-test the ORIGIN constant against the hostname check."],"tags":["ssrf","url-validation","hostname-allowlist","provider","remotli","security"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}