santifer/career-ops · error · Error
personio: URL must use HTTPS: ${url}
Error message
personio: URL must use HTTPS: ${url} What it means
Thrown by personio's assertPersonioUrl() when the URL parses but protocol is not 'https:'. Second SSRF gate preventing plaintext HTTP fetches to Personio career sites. Combined with redirect:'error' in fetch(), this ensures the entire request chain stays encrypted and on-domain.
Source
Thrown at providers/personio.mjs:23
// `https://<slug>.jobs.personio.de/xml` (common across DACH/EU companies).
// Auto-detects from a `<slug>.jobs.personio.(de|com)` careers host like
// 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;View on GitHub (pinned to 9b17a8ac97)
Solutions
- Set careers_url to https:// in portals.yml.
- Use HTTPS for local mock servers in tests.
- Verify no intermediary downgrades the scheme.
Example fix
// before careers_url: 'http://acme.jobs.personio.de' // after careers_url: 'https://acme.jobs.personio.de'
Defensive patterns
Strategy: validation
Validate before calling
/** Normalize Personio URL to HTTPS. */
function ensureHttps(url) {
if (typeof url !== 'string') return null;
return url.replace(/^http:\/\//i, 'https://');
}
entry.careers_url = ensureHttps(entry.careers_url) || entry.careers_url; Type guard
/** @param {string} url @returns {boolean} */
function isHttpsUrl(url) {
try { return new URL(url).protocol === 'https:'; } catch { return false; }
} Try / catch
try {
await personioProvider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).includes('must use HTTPS')) {
entry.careers_url = (entry.careers_url || '').replace(/^http:/i, 'https:');
await personioProvider.fetch(entry, ctx);
} else throw err;
} Prevention
- Author Personio URLs with https:// in config.
- Lint config for http:// entries.
- Use HTTPS mock servers in tests.
When it happens
Trigger: A valid URL with http: scheme: entry.careers_url prefixed http://, or a test against http://localhost. Since fetch() constructs `https://${host}/xml` from resolveHost, the protocol is normally https by construction — this fires only if assertPersonioUrl is called with a directly-supplied http URL.
Common situations: Portals.yml authored with http://. A config tool defaulting to http. Local testing against a non-TLS server.
Related errors
- nodesk: URL must use HTTPS: ${url}
- nofluffjobs: URL must use HTTPS: ${url}
- oraclecloud: URL must use HTTPS: ${url}
- personio: invalid URL: ${url}
- pinpoint: URL must use HTTPS: ${url}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/8348050da63b91e4.
Report an issue: GitHub.