{"record":{"id":"706a33e383a14ad8","repo":"santifer/career-ops","slug":"lever-url-must-use-https-url","errorCode":null,"errorMessage":"lever: URL must use HTTPS: ${url}","messagePattern":"lever: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/lever.mjs","lineNumber":24,"sourceCode":"// Handles both explicit `api:` URLs and auto-detection from `careers_url`.\n\nconst ALLOWED_LEVER_HOSTS = new Set(['api.lever.co', 'api.eu.lever.co']);\n\n// The v0 postings endpoint returns the whole board in one response, with every\n// description inlined, so a large board outgrows _http.mjs's 10s default:\n// jobgether is 42.8 MB and aborted at 10s on its own (#4177). Same value and\n// reasoning as ASHBY_TIMEOUT_MS, the other one-response board-wide ATS feed.\nconst LEVER_TIMEOUT_MS = 30_000;\n\n/** @param {string} url */\nfunction assertLeverUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`lever: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`lever: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_LEVER_HOSTS.has(parsed.hostname))\n    throw new Error(`lever: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_LEVER_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://www.coalfire.com/careers) while still pinning\n  // the Lever postings board (mirrors greenhouse's api: precedence).\n  if (entry.api) {\n    assertLeverUrl(entry.api);\n    return entry.api;\n  }\n  let url;\n  try {\n    url = new URL(entry.careers_url || '');\n  } catch {","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/providers/lever.mjs#L6-L42","documentation":"assertLeverUrl rejects any parsed URL whose protocol is not 'https:'. This enforces TLS for all Lever API traffic, protecting postings data and request metadata from plaintext interception or downgrade.","triggerScenarios":"Passing a http:// (or ftp:, file:, etc.) URL to assertLeverUrl or the lever provider fetch, e.g. 'http://api.lever.co/v0/postings/acme'.","commonSituations":"Older config with http:// endpoints, copied example snippets using http, or a local test harness URL written with http://localhost that reaches the production validator.","solutions":["Use https:// in the URL/config entry.","Search portals.yml and caller code for 'http://' and upgrade each to 'https://'.","For local testing, use a mock that speaks https or inject a test double for fetch rather than bypassing the assertion.","Confirm with new URL(u).protocol === 'https:' before the call."],"exampleFix":"// before\nassertLeverUrl('http://api.lever.co/v0/postings/acme');\n// after\nassertLeverUrl('https://api.lever.co/v0/postings/acme');","handlingStrategy":"validation","validationCode":"function isHttpsUrl(u) { try { return new URL(u).protocol === 'https:'; } catch { return false; } }\nif (!isHttpsUrl(url)) throw new Error(`lever endpoint must be https: ${url}`);","typeGuard":"function isHttpsUrlString(v) { if (typeof v !== 'string') return false; try { return new URL(v).protocol === 'https:'; } catch { return false; } }","tryCatchPattern":"try {\n  provider.fetch(entry, ctx);\n} catch (e) {\n  if (e.message.startsWith('lever: URL must use HTTPS')) {\n    console.warn(`Upgrading to https: ${e.message}`);\n    return provider.fetch({ ...entry, url: entry.url.replace(/^http:/, 'https:') }, ctx);\n  }\n  throw e;\n}","preventionTips":["Always use https:// for api.lever.co endpoints","CI-check config for http:// URLs","Test with https mocks rather than weakening the validator","Copy example URLs from current docs, not old snippets"],"tags":["url-validation","https","security"],"backgroundTag":"invalid-url","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}