{"record":{"id":"72d229e4267b734c","repo":"santifer/career-ops","slug":"bamboohr-url-must-use-https-url","errorCode":null,"errorMessage":"bamboohr: URL must use HTTPS: ${url}","messagePattern":"bamboohr: URL must use HTTPS: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/bamboohr.mjs","lineNumber":27,"sourceCode":"// match on `<safe-tenant>.bamboohr.com` rather than a static allowlist\n// (same approach as the recruitee provider).\n//\n// The list endpoint (`/careers/list`) returns lightweight metadata — enough for\n// the Job contract (title, url, location) at zero token cost. The full JD lives\n// behind a second `/careers/<id>/detail` request, which the scanner deliberately\n// skips to stay zero-token (so `description`/`postedAt` are omitted).\n\nconst BAMBOOHR_HOST_RE = /^[a-z0-9][a-z0-9-]*\\.bamboohr\\.com$/;\n\n/** @param {string} url */\nfunction assertBambooHRUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`bamboohr: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`bamboohr: URL must use HTTPS: ${url}`);\n  if (!BAMBOOHR_HOST_RE.test(parsed.hostname)) {\n    throw new Error(`bamboohr: untrusted hostname \"${parsed.hostname}\" — must match <tenant>.bamboohr.com`);\n  }\n  return url;\n}\n\n/**\n * Resolve the tenant origin (`https://<tenant>.bamboohr.com`) from an entry.\n * Honours an explicit `api:` URL, else parses `careers_url`.\n * @param {import('./_types.js').PortalEntry} entry\n * @returns {string | null}\n */\nfunction resolveOrigin(entry) {\n  const rawApi = typeof entry.api === 'string' ? entry.api : '';\n  const rawCareers = typeof entry.careers_url === 'string' ? entry.careers_url : '';\n  const raw = (rawApi || rawCareers).trim();\n  if (!raw) return null;\n  let parsed;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/bamboohr.mjs#L9-L45","documentation":"assertBambooHRUrl rejects any parsed URL whose protocol is not https. BambooHR endpoints carry board/tenant data, so the provider enforces HTTPS to prevent plaintext transport and scheme-downgrade tricks.","triggerScenarios":"Calling assertBambooHRUrl with `http://mycompany.bamboohr.com/careers/list` or any other non-https scheme that still parses (e.g. a `file://` or custom-scheme URL).","commonSituations":"Hand-written config using http because the tenant page once redirected, or internal test/stub URLs like `http://localhost` accidentally left in portals.yml.","solutions":["Switch the URL to `https://` — all real BambooHR tenants serve HTTPS.","Remove leftover localhost/http test URLs from portals.yml before running scans.","Normalize scheme at config load: upgrade `http://` to `https://` for known-good hosts only.","Check for concatenation bugs that dropped the 's' (e.g. building from a scheme constant)."],"exampleFix":"// before\nassertBambooHRUrl('http://mycompany.bamboohr.com/careers/list'); // throws\n\n// after\nassertBambooHRUrl('https://mycompany.bamboohr.com/careers/list'); // ok","handlingStrategy":"validation","validationCode":"function isHttpsBambooUrl(url) {\n  try { return new URL(url).protocol === 'https:'; } catch { return false; }\n}","typeGuard":"function asHttpsBambooOrigin(value) {\n  const u = new URL(value);\n  return u.protocol === 'https:' ? u.origin : null;\n}","tryCatchPattern":"try {\n  assertBambooHRUrl(apiUrl);\n} catch (err) {\n  if (/must use HTTPS/.test(err.message)) {\n    apiUrl = apiUrl.replace(/^http:/, 'https:');\n    assertBambooHRUrl(apiUrl);\n  } else throw err;\n}","preventionTips":["All BambooHR tenants serve HTTPS — always write https:// origins.","Remove localhost/http stub URLs from production config.","Lint config for non-https URLs at startup.","Check scheme constants used when building URLs programmatically."],"tags":["url","https","security","bamboohr"],"backgroundTag":"invalid-url","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}