santifer/career-ops · error · Error
pinpoint: untrusted hostname
Error message
pinpoint: untrusted hostname "${parsed.hostname}" — must match <slug>.pinpointhq.com What it means
assertPinpointUrl only accepts hostnames matching PINPOINT_HOST_RE — <slug>.pinpointhq.com. The URL parsed and used HTTPS, but the hostname is outside that allowlist, so the provider refuses to send the request. This is the SSRF/trust guard: the hostname could be an attacker-controlled or simply wrong domain.
Solutions
- Replace careers_url in portals.yml with the tenant's real https://<slug>.pinpointhq.com host (find the slug from the company's careers page or Pinpoint feed link).
- If Pinpoint serves this tenant on a new host shape, update PINPOINT_HOST_RE at pinpoint.mjs line 22 and align the error message.
- Resolve the vanity domain once (follow the redirect manually) and hardcode the underlying pinpointhq.com tenant host.
- Do not bypass by enabling redirects in fetchText — the redirect:'error' flag plus this assertion is the SSRF guarantee.
Example fix
// before (portals.yml) careers_url: https://jobs.acme.com // after careers_url: https://acme.pinpointhq.com
Defensive patterns
Strategy: validation
Validate before calling
const PINPOINT_HOST_RE = /^[a-z0-9-]+\.pinpointhq\.com$/;
export function isPinpointUrl(u) {
try {
const parsed = new URL(u);
return parsed.protocol === 'https:' && PINPOINT_HOST_RE.test(parsed.hostname);
} catch { return false; }
}
if (!isPinpointUrl(entry.careers_url)) throw new Error(`pinpoint: careers_url for ${entry.name} not on Pinpoint allowlist`); Type guard
function isPinpointTenantUrl(u) {
if (typeof u !== 'string') return false;
try {
const parsed = new URL(u);
return parsed.protocol === 'https:' && /^[a-z0-9-]+\.pinpointhq\.com$/.test(parsed.hostname);
} catch { return false; }
} Try / catch
try {
await pinpointProvider.fetch(entry, ctx);
} catch (e) {
if (String(e.message).startsWith('pinpoint: untrusted hostname')) {
logger.warn({ entry: entry.name, host: (() => { try { return new URL(entry.careers_url).hostname; } catch { return '?'; } })() }, 'not a *.pinpointhq.com tenant — use the tenant subdomain, not a vanity domain');
return null;
}
throw e;
} Prevention
- Store the <slug>.pinpointhq.com tenant host in portals.yml, never the vanity careers domain.
- Validate all entries against PINPOINT_HOST_RE at config load or in CI.
- Treat this error as a potential security signal — confirm who edited the URL before 'fixing' it blindly.
- Keep redirect:'error' in fetchText; never bypass the hostname assertion with redirects.
When it happens
Trigger: Calling fetch or validation paths reaching assertPinpointUrl (pinpoint.mjs line 34) with a careers_url host like acme.pinpointhq.co (wrong TLD), jobs.acme.com (vanity domain), pinpointhq.com.evil.test, or an extra subdomain level.
Common situations: Company uses a vanity domain fronting Pinpoint instead of the tenant subdomain; TLD typo (.co vs .com); an entry actually belonging to a different ATS; malicious/mistaken edit of portals.yml pointing off-domain.
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
- comeet: URL path must be the careers-api endpoint
- flowxtra: URL must use HTTPS
- oraclecloud: untrusted hostname
- personio: untrusted hostname
- pinpoint: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/280485972fd101e4.
Report an issue: GitHub.
Appendix: source
Thrown at providers/pinpoint.mjs:34
// The tenant label must be a valid DNS label: it may contain hyphens but must
// not start or end with one (so `acme-.pinpointhq.com` is rejected). The
// optional trailing group keeps single-character labels (e.g. `a.pinpointhq.com`)
// valid. detect() and fetch() both route through this constant via
// resolveApiUrl()/assertPinpointUrl(), so the stricter check applies everywhere.
const PINPOINT_HOST_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.pinpointhq\.com$/;
/** @param {string} url */
function assertPinpointUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`pinpoint: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`pinpoint: URL must use HTTPS: ${url}`);
if (!PINPOINT_HOST_RE.test(parsed.hostname)) {
throw new Error(`pinpoint: untrusted hostname "${parsed.hostname}" — must match <slug>.pinpointhq.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 (!PINPOINT_HOST_RE.test(parsed.hostname)) return null;
return `https://${parsed.hostname}/postings.json`;
}
View on GitHub (pinned to aac998c7ed)