santifer/career-ops · error · Error
bamboohr: invalid URL: ${url}
Error message
bamboohr: invalid URL: ${url} What it means
BambooHR validates URLs via `assertBambooHRUrl`. The 'invalid URL' variant fires when `new URL(url)` throws — the string is not a parseable absolute URL. First of three sequential guards before any fetch.
Source
Thrown at providers/bamboohr.mjs:23
// Auto-detects from careers_url pattern `https://<tenant>.bamboohr.com[/...]`.
// Per-tenant subdomains are the variable part, so SSRF defence uses a regex
// match on `<safe-tenant>.bamboohr.com` rather than a static allowlist
// (same approach as the recruitee provider).
//
// The list endpoint (`/careers/list`) returns lightweight metadata — enough for
// the Job contract (title, url, location) at zero token cost. The full JD lives
// behind a second `/careers/<id>/detail` request, which the scanner deliberately
// skips to stay zero-token (so `description`/`postedAt` are omitted).
const BAMBOOHR_HOST_RE = /^[a-z0-9][a-z0-9-]*\.bamboohr\.com$/;
/** @param {string} url */
function assertBambooHRUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`bamboohr: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`bamboohr: URL must use HTTPS: ${url}`);
if (!BAMBOOHR_HOST_RE.test(parsed.hostname)) {
throw new Error(`bamboohr: untrusted hostname "${parsed.hostname}" — must match <tenant>.bamboohr.com`);
}
return url;
}
/**
* Resolve the tenant origin (`https://<tenant>.bamboohr.com`) from an entry.
* Honours an explicit `api:` URL, else parses `careers_url`.
* @param {import('./_types.js').PortalEntry} entry
* @returns {string | null}
*/
function resolveOrigin(entry) {
const rawApi = typeof entry.api === 'string' ? entry.api : '';
const rawCareers = typeof entry.careers_url === 'string' ? entry.careers_url : '';
const raw = (rawApi || rawCareers).trim();View on GitHub (pinned to 9b17a8ac97)
Solutions
- Provide a full `https://<tenant>.bamboohr.com` URL in `api:` or `careers_url`.
- Validate entry URLs at config load so the error points at the offending row.
- Strip accidental whitespace from templated URL fields.
Example fix
# before - name: Acme api: acme.bamboohr.com # after - name: Acme api: https://acme.bamboohr.com
Defensive patterns
Strategy: validation
Validate before calling
function isValidAbsoluteUrl(u) {
try { new URL(u); return true; } catch { return false; }
}
for (const f of ['api', 'careers_url']) {
if (entry[f] && !isValidAbsoluteUrl(entry[f])) {
throw new Error(`bamboohr: entry ${entry.name} has malformed ${f}: ${entry[f]}`);
}
} Prevention
- Require fully-qualified URLs in config entries.
- Run `new URL()` over every api/careers_url at startup.
- Use detect() so malformed entries are skipped rather than thrown.
When it happens
Trigger: `assertBambooHRUrl(url)` is called (typically on the derived `${origin}/careers/list` URL or an `api:` field) with a value `new URL()` cannot parse: schemeless host, protocol-relative, empty, or illegal characters.
Common situations: An `api:` field set without `https://`, a careers_url with a stray space or broken template, or config injection that mangles the scheme.
Related errors
- arbeitnow: invalid URL: ${url}
- ashby: invalid URL: ${url}
- bamboohr: URL must use HTTPS: ${url}
- breezy: invalid URL: ${url}
- jobstreet: invalid URL: ${url}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/686fd092a5ba9a5d.
Report an issue: GitHub.