santifer/career-ops · error · Error
recruitee: untrusted hostname "${parsed.hostname}" — must ma
Error message
recruitee: untrusted hostname "${parsed.hostname}" — must match <slug>.recruitee.com What it means
assertRecruiteeUrl throws when parsed.hostname fails RECRUITEE_HOST_RE (/^[a-z0-9][a-z0-9-]*\.recruitee\.com$/). The hostname must be exactly <slug>.recruitee.com — slug starts alphanumeric, then any mix of lowercase alphanumerics and hyphens. This SSRF guard prevents a crafted entry from pointing the fetch at an arbitrary host while still allowing the per-tenant slug to vary.
Source
Thrown at providers/recruitee.mjs:21
// Recruitee provider — hits the public per-tenant offers API.
// Auto-detects from careers_url pattern `https://<slug>.recruitee.com`.
// Per-tenant subdomains are the variable part — SSRF defence uses a
// regex match on `<safe-slug>.recruitee.com` rather than a static
// allowlist.
const RECRUITEE_HOST_RE = /^[a-z0-9][a-z0-9-]*\.recruitee\.com$/;
function assertRecruiteeUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`recruitee: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`recruitee: URL must use HTTPS: ${url}`);
if (!RECRUITEE_HOST_RE.test(parsed.hostname)) {
throw new Error(`recruitee: untrusted hostname "${parsed.hostname}" — must match <slug>.recruitee.com`);
}
return url;
}
function resolveApiUrl(entry) {
const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
if (!raw) return null;
let parsed;
try {
parsed = new URL(raw);
} catch {
return null;
}
if (parsed.protocol !== 'https:') return null;
if (!RECRUITEE_HOST_RE.test(parsed.hostname)) return null;
return `https://${parsed.hostname}/api/offers/`;
}
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Use the canonical https://<slug>.recruitee.com subdomain where <slug> is lowercase alphanumeric plus hyphens.
- If the board only has a branded domain, the recruitee provider cannot auto-derive it — supply api: pointing to the recruitee.com subdomain or use the correct provider.
- Lowercase the hostname and confirm it is a single-label subdomain of recruitee.com.
- Check for typos in the TLD (.org, .net vs .com).
Example fix
// before
{ careers_url: 'https://jobs.acme.com' }
// after
{ careers_url: 'https://acme.recruitee.com' } Defensive patterns
Strategy: validation
Validate before calling
const RECRUITEE_HOST_RE = /^[a-z0-9][a-z0-9-]*\.recruitee\.com$/;
function isRecruiteeUrl(url) {
try {
const p = new URL(url);
return p.protocol === 'https:' && RECRUITEE_HOST_RE.test(p.hostname);
} catch { return false; }
}
if (!isRecruiteeUrl(entry.careers_url)) {
console.warn(`skip ${entry.name}: not a *.recruitee.com URL`);
} Type guard
null
Try / catch
try {
await provider.fetch(entry, ctx);
} catch (e) {
if (/untrusted hostname/.test(e.message)) {
console.warn(`[skip] ${entry.name}: ${e.message}`);
} else throw e;
} Prevention
- Use the canonical <slug>.recruitee.com subdomain — branded domains are not auto-derivable.
- Lowercase hostnames before validation.
- Validate at config-load time against the regex.
When it happens
Trigger: A hostname like jobs.acme.com (branded, not recruitee.com); acme.RECRUITEE.com (uppercase); acme.recruitee.org (wrong TLD); recruitee.com with no slug; a multi-level subdomain like a.b.recruitee.com (the regex requires exactly one label before .recruitee.com).
Common situations: A tenant uses a Recruitee vanity/branded domain that drops the recruitee.com suffix; an entry was misclassified as recruitee when it belongs to a different provider; the slug was typed with an uppercase letter.
Related errors
- pinpoint: untrusted hostname "${parsed.hostname}" — must mat
- remotli: untrusted hostname "${parsed.hostname}" — must be r
- rippling: untrusted hostname "${parsed.hostname}" — must be
- recruitee: URL must use HTTPS: ${url}
- Refusing non-HTTP(S) URL: ${url}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/94a4625f7a9fa49d.
Report an issue: GitHub.