santifer/career-ops · error · Error
breezy: untrusted hostname "${parsed.hostname}" — must match
Error message
breezy: untrusted hostname "${parsed.hostname}" — must match <tenant>.breezy.hr What it means
The host-allowlist step of Breezy's SSRF guard. The hostname must match `/^[a-z0-9][a-z0-9-]*\.breezy\.hr$/` — a single lowercase tenant label (alphanumeric + hyphens, not starting with a hyphen) followed by `.breezy.hr`. Uppercase, multi-label tenants, or other domains are rejected.
Source
Thrown at providers/breezy.mjs:28
// Breezy boards expose every published position as a public JSON array at
// `<tenant>.breezy.hr/json` — title, absolute url, location, and a published
// date, all in the list payload at zero token cost (no per-job request, so the
// scanner stays zero-token). Breezy's authenticated REST API (api.breezy.hr) is
// intentionally NOT used; only the public board feed.
const BREEZY_HOST_RE = /^[a-z0-9][a-z0-9-]*\.breezy\.hr$/;
/** @param {string} url */
function assertBreezyUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`breezy: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`breezy: URL must use HTTPS: ${url}`);
if (!BREEZY_HOST_RE.test(parsed.hostname)) {
throw new Error(`breezy: untrusted hostname "${parsed.hostname}" — must match <tenant>.breezy.hr`);
}
return url;
}
/**
* Resolve the tenant origin (`https://<tenant>.breezy.hr`) 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();
if (!raw) return null;
let parsed;
try {
parsed = new URL(raw);View on GitHub (pinned to 9b17a8ac97)
Solutions
- Use a single lowercase tenant label: `https://acme.breezy.hr`.
- If a multi-label tenant is legitimate, lower-case the host and widen the regex only if you trust the labels.
- Confirm the host is actually a Breezy tenant before keeping the provider.
Example fix
# before (rejected — uppercase + path-style host) - name: Acme api: https://Jobs.Acme.breezy.hr # after - name: Acme api: https://acme.breezy.hr
Defensive patterns
Strategy: validation
Validate before calling
const RE = /^[a-z0-9][a-z0-9-]*\.breezy\.hr$/;
function assertBreezyHost(u) {
const h = new URL(u).hostname;
if (!RE.test(h)) throw new Error(`breezy: host ${h} must match <tenant>.breezy.hr (lowercase, single label)`);
}
if (entry.api) assertBreezyHost(entry.api);
if (entry.careers_url) assertBreezyHost(entry.careers_url); Prevention
- Lowercase tenant names in config before validation.
- Reject multi-label or uppercase hosts at config lint time.
- Confirm the host is a real Breezy tenant before assigning the provider.
When it happens
Trigger: `BREEZY_HOST_RE.test(parsed.hostname)` is false: hostname is uppercase (`Acme.breezy.hr`), has a multi-part tenant (`jobs.acme.breezy.hr`), uses a different domain, or is the apex (`breezy.hr`).
Common situations: An uppercase tenant name, a deep subdomain, a domain typo, or a URL pointing at a non-Breezy host.
Related errors
- bamboohr: untrusted hostname "${parsed.hostname}" — must mat
- arbeitnow: untrusted hostname "${parsed.hostname}" — must be
- ashby: untrusted hostname "${parsed.hostname}" — must be one
- larajobs: untrusted hostname "${parsed.hostname}" - must be
- lever: untrusted hostname "${parsed.hostname}" — must be one
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/4b05f01049371a13.
Report an issue: GitHub.