santifer/career-ops · error · Error
teamtailor: untrusted hostname
Error message
teamtailor: untrusted hostname "${parsed.hostname}" — must be <slug>.teamtailor.com (or set "provider: teamtailor" to use a branded careers domain) What it means
teamtailor only trusts feeds on <slug>.teamtailor.com by default; other hostnames are refused to prevent SSRF. The guard is bypassed by explicitly setting provider: teamtailor on the entry, which signals 'I know this is a branded careers domain'. The error's message says exactly that.
Solutions
- Add provider: teamtailor to the entry to opt into the branded domain
- Verify the branded host actually serves the /jobs.rss feed (curl it) before opting in
- If the company is no longer on TeamTailor, reassign the correct provider
Example fix
// before - name: Acme careers_url: 'https://jobs.acme.com/jobs.rss' // after - name: Acme provider: teamtailor careers_url: 'https://jobs.acme.com/jobs.rss'
Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(entry.careers_url);
const isTT = /^[a-z0-9-]+\.teamtailor\.com$/i.test(u.hostname);
if (!isTT && entry.provider !== 'teamtailor') {
throw new Error(`${entry.name}: non-teamtailor.com host needs provider: teamtailor to opt in`);
} Type guard
null
Try / catch
try {
offers = await provider.fetch(entry, ctx);
} catch (e) {
if (e.message.includes('untrusted hostname')) {
console.warn(`${entry.name}: branded domain — add provider: teamtailor to the entry`);
return [];
}
throw e;
} Prevention
- Set provider: teamtailor explicitly whenever using a branded careers domain
- Verify the branded host serves /jobs.rss before opting in
- Re-check hosts after company ATS migrations
When it happens
Trigger: careers_url points at a branded domain like jobs.acme.com while the entry lacks provider: teamtailor; TEAMTAILOR_HOST_RE fails on subdomains like acme.teamtailor.co.uk or a non-teamtailor ATS host misassigned to this provider.
Common situations: A company using TeamTailor's custom-domain feature; a moved board (company left TeamTailor, domain now serves another ATS); copy-pasting the public careers homepage URL instead of the feed host.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- himalayas: untrusted hostname
- itviec: untrusted hostname
- jobbankca: invalid URL
- jobspresso: invalid URL
- jobstreet: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/bc59ba21b44b7a4c.
Report an issue: GitHub.
Appendix: source
Thrown at providers/teamtailor.mjs:43
const TEAMTAILOR_HOST_RE = /^([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\.teamtailor\.com$/i;
/**
* Validate a feed URL before fetching. Always HTTPS-only. The hostname is
* pinned to `*.teamtailor.com` for auto-detected entries; an explicit
* `provider: teamtailor` entry may use its configured branded host.
* @param {string} url
* @param {{ explicit?: boolean }} [opts]
*/
function assertFeedUrl(url, { explicit = false } = {}) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`teamtailor: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`teamtailor: URL must use HTTPS: ${url}`);
if (!explicit && !TEAMTAILOR_HOST_RE.test(parsed.hostname)) {
throw new Error(`teamtailor: untrusted hostname "${parsed.hostname}" — must be <slug>.teamtailor.com (or set "provider: teamtailor" to use a branded careers domain)`);
}
return url;
}
// Derive the RSS feed URL from a tracked_companies entry by normalizing any
// path on the configured host to /jobs.rss. Auto-detection (explicit=false)
// only claims *.teamtailor.com hosts; an explicit `provider: teamtailor` entry
// (explicit=true) may use a branded careers host. Returns null otherwise.
/**
* @param {import('./_types.js').PortalEntry} entry
* @param {{ explicit?: boolean }} [opts]
*/
function resolveFeedUrl(entry, { explicit = false } = {}) {
const raw = entry?.api || entry?.careers_url || '';
if (typeof raw !== 'string' || !raw) return null;
let parsed;
try {
parsed = new URL(raw);View on GitHub (pinned to aac998c7ed)