santifer/career-ops · error
flowxtra: untrusted hostname
Error message
flowxtra: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_ENDPOINT_HOST} What it means
This is the hostname allowlist check inside assertFlowxtraEndpointUrl in providers/flowxtra.mjs. After verifying the URL parses and uses HTTPS, the function requires parsed.hostname to exactly equal TRUSTED_ENDPOINT_HOST; any other host is rejected. This pins the Flowxtra integration to a single known endpoint so a misconfigured or attacker-supplied URL cannot redirect API traffic elsewhere.
Solutions
- Set the endpoint host back to the trusted host the provider expects (the TRUSTED_ENDPOINT_HOST constant value).
- Verify the hostname is not a typo (subdomain spelling, TLD).
- If you truly need a different endpoint, update the TRUSTED_ENDPOINT_HOST constant in the provider as a deliberate code change, not via config.
- Route through a proxy that terminates on the trusted host if network restrictions are the reason for the alternate URL.
Example fix
// before endpoint: "https://jobs.flowxtra-cdn.example.com/api/jobs" // after endpoint: "https://flowxtra.example.com/api/jobs" // exact TRUSTED_ENDPOINT_HOST
Defensive patterns
Strategy: validation
Validate before calling
const TRUSTED = 'flowxtra.example.com'; // keep in sync with the provider
function isTrustedEndpoint(u) {
try { return new URL(u).hostname === TRUSTED; } catch { return false; }
} Type guard
const isTrustedHost = (u, trusted) => { try { return new URL(u).hostname === trusted; } catch { return false; } }; Try / catch
try {
await provider.fetch(entry);
} catch (e) {
if (e.message.includes('untrusted hostname')) {
console.error(`Entry "${entry.name}" points at a non-allowlisted host — restore ${TRUSTED}`);
return;
}
throw e;
} Prevention
- Copy endpoint URLs only from the provider's official documentation, not from browser address bars of mirrors.
- Keep a single source of truth for the endpoint in config instead of per-entry overrides.
- Diff portals.yml changes for URL edits during review.
When it happens
Trigger: Calling the provider with an entry whose endpoint URL parses as HTTPS but whose hostname differs from TRUSTED_ENDPOINT_HOST — e.g. a mirror domain, a typo'd subdomain (flowxtra2.example.com), a lookalike domain, or a custom endpoint injected via config.
Common situations: Copy-pasting a staging or vanity URL from a browser; a company migrating ATS vendors leaves a stale custom host in portals.yml; typo-squat domains in scraped or third-party config; attempting to point the provider at a self-hosted proxy.
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
- torre: untrusted hostname
- breezy: untrusted hostname
- builtin: untrusted hostname
- careerviet: untrusted hostname
- flowxtra: URL must use HTTPS
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/3a8ab29ede5dbcf7.
Report an issue: GitHub.
Appendix: source
Thrown at providers/flowxtra.mjs:35
const JOBS_ENDPOINT = 'https://app.flowxtra.com/api/central/jobs';
const TRUSTED_ENDPOINT_HOST = 'app.flowxtra.com';
const TRUSTED_APPLY_HOST = 'flowxtra.com';
const PER_PAGE = 100;
const DEFAULT_MAX_PAGES = 3;
const MAX_PAGES_CAP = 50;
/** @param {string} url */
function assertFlowxtraEndpointUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`flowxtra: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`flowxtra: URL must use HTTPS: ${url}`);
if (parsed.hostname !== TRUSTED_ENDPOINT_HOST) {
throw new Error(`flowxtra: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_ENDPOINT_HOST}`);
}
return url;
}
/** Resolve the page cap: a positive integer `max_pages` on the entry, capped. */
function resolveMaxPages(entry) {
const v = entry?.max_pages;
if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES_CAP);
return DEFAULT_MAX_PAGES;
}
// NaN-safe Date.parse — `|| undefined` would also coerce a valid epoch 0.
function toEpochMs(value) {
if (!value) return undefined;
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? undefined : parsed;
}
View on GitHub (pinned to aac998c7ed)