santifer/career-ops · error · Error
pinpoint: invalid URL: ${url}
Error message
pinpoint: invalid URL: ${url} What it means
Thrown by pinpoint's assertPinpointUrl() when new URL(url) throws — the URL is syntactically invalid. First of three SSRF gates (valid URL → HTTPS → trusted hostname regex) for Pinpoint ATS boards. The hostname regex PINPOINT_HOST_RE enforces the <slug>.pinpointhq.com pattern, requiring a slug that starts and ends with alphanumeric (allowing internal hyphens).
Source
Thrown at providers/pinpoint.mjs:30
//
// Per-tenant subdomains are the variable part — SSRF defence uses a regex
// match on `<safe-slug>.pinpointhq.com` rather than a static allowlist, the
// same approach as the recruitee provider.
// 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;View on GitHub (pinned to 9b17a8ac97)
Solutions
- Log the url argument to assertPinpointUrl to identify the malformed value.
- Set careers_url in the pinpoint portals.yml entry to https://<slug>.pinpointhq.com.
- If calling resolveApiUrl() directly, validate the entry has a non-empty careers_url first.
Example fix
// before — missing scheme careers_url: 'acme.pinpointhq.com' // after careers_url: 'https://acme.pinpointhq.com'
Defensive patterns
Strategy: validation
Validate before calling
/** Validate URL string is parseable before passing to assertPinpointUrl. */
function isValidUrlString(url) {
return typeof url === 'string'
&& url.length > 0
&& (() => { try { new URL(url); return true; } catch { return false; } })();
}
if (!isValidUrlString(entry.careers_url)) {
console.warn(`pinpoint entry ${entry.name} has invalid URL`);
continue;
} Type guard
/** @param {unknown} url @returns {url is string} */
function isParseableUrl(url) {
if (typeof url !== 'string' || !url) return false;
try { new URL(url); return true; } catch { return false; }
} Try / catch
try {
await pinpointProvider.fetch(entry, ctx);
} catch (err) {
if (String(err.message).startsWith('pinpoint: invalid URL')) {
console.warn(`skipping pinpoint entry ${entry.name}: malformed URL`);
continue;
}
throw err;
} Prevention
- Validate URL fields in portals.yml at config-load time.
- Ensure Pinpoint URLs include https:// and follow the <slug>.pinpointhq.com pattern.
- Call resolveApiUrl(entry) or detect(entry) before fetch() and skip entries returning null.
When it happens
Trigger: Called with an unparseable URL: undefined, empty string, spaces, or a schemeless path. The guard is called from resolveApiUrl() and fetch(), both routing through assertPinpointUrl. A typical trigger is a careers_url with a typo or missing scheme that reaches assertPinpointUrl past detect()'s null-return guard.
Common situations: Portals.yml pinpoint entry with careers_url missing or malformed. A programmatic entry without the URL field. A URL copied without the https:// scheme. Testing with a relative fixture path.
Related errors
- pinpoint: URL must use HTTPS: ${url}
- flowxtra: untrusted hostname "${parsed.hostname}" — must be
- gem: invalid URL: ${url}
- gem: URL must use HTTPS: ${url}
- gem: untrusted hostname "${parsed.hostname}" — must be one o
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/e2d4580ad10a6ef4.
Report an issue: GitHub.