{"record":{"id":"0023ca7b811f9fe6","repo":"santifer/career-ops","slug":"ashby-url-must-use-https-url","errorCode":null,"errorMessage":"ashby: URL must use HTTPS: ${url}","messagePattern":"ashby: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/ashby.mjs","lineNumber":88,"sourceCode":"  const resolvedMax = /** @type {number} */ (max ?? min);\n  return {\n    min: Math.min(resolvedMin, resolvedMax),\n    max: Math.max(resolvedMin, resolvedMax),\n    currency: currency.toUpperCase(),\n  };\n}\n\nconst ALLOWED_ASHBY_HOSTS = new Set(['api.ashbyhq.com']);\n\n/** @param {string} url */\nfunction assertAshbyUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`ashby: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`ashby: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_ASHBY_HOSTS.has(parsed.hostname))\n    throw new Error(`ashby: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_ASHBY_HOSTS].join(', ')}`);\n  return url;\n}\n\n/** @param {import('./_types.js').PortalEntry} entry */\nfunction resolveApiUrl(entry) {\n  // Explicit api: wins — lets an entry keep a human-facing corporate\n  // careers_url (e.g. https://openai.com/careers) while still pinning the\n  // Ashby posting-api board (mirrors greenhouse's api: precedence).\n  if (entry.api) {\n    assertAshbyUrl(entry.api);\n    return entry.api;\n  }\n  const url = entry.careers_url || '';\n  const match = url.match(/jobs\\.ashbyhq\\.com\\/([^/?#]+)/);\n  if (!match) return null;\n  return `https://api.ashbyhq.com/posting-api/job-board/${match[1]}?includeCompensation=true`;","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/ashby.mjs#L70-L106","documentation":"The HTTPS-enforcement step of ashby's SSRF guard. After the URL parses, any scheme other than `https:` is rejected. This blocks `http://` downgrade attacks and ensures transport security to the Ashby API.","triggerScenarios":"`parsed.protocol !== 'https:'` for a URL that otherwise parses. Commonly an `http://api.ashbyhq.com/...` value in `entry.api` or a derived URL.","commonSituations":"An `api:` config value copied from an insecure source, a local/dev proxy URL left in config, or a tool that stripped the scheme to http.","solutions":["Use `https://api.ashbyhq.com/...` for the `api:` field.","Remove any http:// overrides from config or environment.","If testing through a local TLS proxy, terminate TLS on the proxy rather than disabling the check."],"exampleFix":"# before\n- name: Acme\n  api: http://api.ashbyhq.com/posting-api?compId=acme\n\n# after\n- name: Acme\n  api: https://api.ashbyhq.com/posting-api?compId=acme","handlingStrategy":"validation","validationCode":"function ensureHttpsApi(entry) {\n  if (!entry.api) return;\n  const p = new URL(entry.api);\n  if (p.protocol !== 'https:') throw new Error(`ashby: api must be https: ${entry.api}`);\n}\nensureHttpsApi(entry);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize all provider URLs to https at config load.","Reject http:// values with a startup error naming the entry.","Never disable the scheme check to work around a proxy — terminate TLS instead."],"tags":["ashby","url-validation","https","ssrf"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}