{"record":{"id":"5f6c85165ae48efe","repo":"santifer/career-ops","slug":"gem-url-must-use-https-url","errorCode":null,"errorMessage":"gem: URL must use HTTPS: ${url}","messagePattern":"gem: URL must use HTTPS: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/gem.mjs","lineNumber":113,"sourceCode":"function buildJobDescriptionText(posting) {\n  const intro = htmlToText(posting?.jobPostSectionHtml?.introHtml);\n  const body = htmlToText(posting?.descriptionHtml);\n  const outro = htmlToText(posting?.jobPostSectionHtml?.outroHtml);\n  const compensation = htmlToText(posting?.compensationHtml);\n\n  const text = [intro, body, outro].filter(Boolean).join('\\n\\n');\n  return compensation ? [text, `Compensation: ${compensation}`].filter(Boolean).join('\\n\\n') : text;\n}\n\n/** @param {string} url */\nfunction assertGemUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`gem: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`gem: URL must use HTTPS: ${url}`);\n  if (!ALLOWED_GEM_HOSTS.has(parsed.hostname))\n    throw new Error(`gem: untrusted hostname \"${parsed.hostname}\" — must be one of: ${[...ALLOWED_GEM_HOSTS].join(', ')}`);\n  return url;\n}\n\n/** @param {import('./_types.js').PortalEntry} entry */\nfunction resolveBoardId(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.hostname !== 'jobs.gem.com') return null;\n  const match = parsed.pathname.match(/^\\/([^/?#]+)/);\n  return match ? match[1] : null;","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/gem.mjs#L95-L131","documentation":"gem.mjs throws this in assertGemUrl() after the URL parses successfully but its protocol is not 'https:'. It enforces TLS for the Gem GraphQL endpoint and rejects http:/file:/data: schemes. Since the validated value is the constant GEM_API_URL, a live throw indicates that constant was set to an http:// (or other-scheme) URL.","triggerScenarios":"GEM_API_URL was changed to 'http://jobs.gem.com/...' (e.g. for local debugging through a non-TLS proxy); a test calls assertGemUrl('http://jobs.gem.com/api/public/graphql/batch').","commonSituations":"A developer disables TLS locally to intercept traffic with mitmproxy on http://localhost and repoints GEM_API_URL; a stale fork still references the pre-HTTPS endpoint; a CI env var overrode the constant with an http URL.","solutions":["Keep GEM_API_URL on https:// (the public endpoint is TLS-only).","If you need local interception, point your proxy at the HTTPS endpoint and trust the proxy's CA instead of downgrading the scheme.","Remove any test that passes an http URL unless it is explicitly asserting this error."],"exampleFix":"// before\nconst GEM_API_URL = 'http://jobs.gem.com/api/public/graphql/batch';\n\n// after\nconst GEM_API_URL = 'https://jobs.gem.com/api/public/graphql/batch';","handlingStrategy":"validation","validationCode":"// Confirm the Gem endpoint scheme at startup.\nfunction checkGemScheme() {\n  const u = new URL('https://jobs.gem.com/api/public/graphql/batch');\n  if (u.protocol !== 'https:') throw new Error('gem endpoint must be HTTPS');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never downgrade GEM_API_URL to http for local debugging; use a trusted proxy CA instead.","Add a lint rule / test forbidding http: in provider endpoint constants.","Document the HTTPS-only policy in the provider header comment."],"tags":["url-validation","tls","ssrf-guard","gem","config"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}