santifer/career-ops · error · Error
personio: untrusted hostname "${parsed.hostname}" — must mat
Error message
personio: untrusted hostname "${parsed.hostname}" — must match <slug>.jobs.personio.(de|com) What it means
Thrown by personio's assertPersonioUrl() when the hostname fails PERSONIO_HOST_RE (/^[a-z0-9][a-z0-9-]*\.jobs\.personio\.(de|com)$/). Third SSRF gate: enforces the tenant-subdomain pattern <slug>.jobs.personio.de or <slug>.jobs.personio.com. The regex requires a non-empty slug starting with alphanumeric, allowing hyphens in the middle. Rejects bare 'jobs.personio.de', 'www.jobs.personio.de', and non-Personio domains.
Source
Thrown at providers/personio.mjs:25
// workable/recruitee. Per-tenant subdomains are the variable part, so the
// SSRF defence is an anchored host regex rather than a static allowlist.
//
// The feed is a flat, well-defined XML document, so it is parsed in-process
// with a tiny tag extractor (no new dependency — the repo ships none for XML).
const PERSONIO_HOST_RE = /^[a-z0-9][a-z0-9-]*\.jobs\.personio\.(de|com)$/;
/** @param {string} url */
function assertPersonioUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`personio: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`personio: URL must use HTTPS: ${url}`);
if (!PERSONIO_HOST_RE.test(parsed.hostname))
throw new Error(`personio: untrusted hostname "${parsed.hostname}" — must match <slug>.jobs.personio.(de|com)`);
return url;
}
/**
* Resolve the tenant host (e.g. `acme.jobs.personio.de`) from a careers_url.
* Returns null for non-Personio or malformed URLs.
* @param {import('./_types.js').PortalEntry} entry
*/
function resolveHost(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;View on GitHub (pinned to 9b17a8ac97)
Solutions
- Find the company's Personio career URL — it must be in the form https://<company-slug>.jobs.personio.de or .com.
- Remove 'www.' and any extra subdomain labels — only one slug label before .jobs.personio is allowed.
- If Personio added a new domain variant, update PERSONIO_HOST_RE to accept it.
- Verify the provider field is 'personio' and matches the URL — a lever/greenhouse URL won't pass this check.
Example fix
// before — wrong hostname shape careers_url: 'https://jobs.personio.de' // no tenant slug careers_url: 'https://acme.personio.de' // missing .jobs. segment // after careers_url: 'https://acme.jobs.personio.de'
Defensive patterns
Strategy: validation
Validate before calling
const PERSONIO_HOST_RE = /^[a-z0-9][a-z0-9-]*\.jobs\.personio\.(de|com)$/;
/** Check hostname matches Personio tenant pattern. */
function isPersonioHost(url) {
try { return PERSONIO_HOST_RE.test(new URL(url).hostname); } catch { return false; }
}
if (!isPersonioHost(entry.careers_url)) {
console.warn(`personio entry ${entry.name} URL doesn't match <slug>.jobs.personio.(de|com)`);
continue;
} Type guard
/** @param {string} url @returns {boolean} */
function isPersonioUrl(url) {
const RE = /^[a-z0-9][a-z0-9-]*\.jobs\.personio\.(de|com)$/;
try { return RE.test(new URL(url).hostname); } catch { return false; }
} Try / catch
try {
await personioProvider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).includes('untrusted hostname')) {
console.warn(`personio entry ${entry.name} wrong host — needs <slug>.jobs.personio.(de|com)`);
continue;
}
throw err;
} Prevention
- Ensure Personio URLs follow <slug>.jobs.personio.de or .com — no www., no bare personio.de.
- Copy the career board URL from the browser, not the company homepage.
- Update PERSONIO_HOST_RE if Personio adds new TLDs or subdomain patterns.
When it happens
Trigger: Valid HTTPS URL with wrong hostname: 'jobs.personio.de' (no tenant slug), 'personio.de' (company homepage, not career site), 'careers.acme.com' (non-Personio), or 'acme.personio.de' (missing the .jobs. segment). A common form is using the company's main domain instead of their Personio subdomain.
Common situations: The entry points to the company's own website instead of their Personio board. A copy-paste from a different provider entry without updating the domain. Personio introduced a new TLD or subdomain pattern not covered by the regex. The entry uses 'www.jobs.personio.de' which the slug regex rejects.
Related errors
- oraclecloud: untrusted hostname "${parsed.hostname}" — must
- nodesk: untrusted hostname "${parsed.hostname}" - must be ${
- nofluffjobs: untrusted hostname "${parsed.hostname}" — must
- personio: invalid URL: ${url}
- personio: URL must use HTTPS: ${url}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/4a775510f1b2e8c0.
Report an issue: GitHub.