santifer/career-ops · error · Error

flowxtra: untrusted hostname "${parsed.hostname}" — must be

Error message

flowxtra: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_ENDPOINT_HOST}

What it means

flowxtra.mjs throws this inside assertFlowxtraEndpointUrl(), a host-allowlist guard run before every ctx.fetchJson against the Flowxtra central jobs API. It compares parsed.hostname to the compile-time constant TRUSTED_ENDPOINT_HOST ('app.flowxtra.com') and aborts if they differ, so the request never leaves the process. The guard is an SSRF defense: combined with redirect:'error' it guarantees the only host ever contacted is app.flowxtra.com.

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 9b17a8ac97)

Solutions

  1. If you intended to point at app.flowxtra.com, leave JOBS_ENDPOINT and TRUSTED_ENDPOINT_HOST at their shipped values and remove any local override.
  2. If you genuinely need a different host (mirror/proxy), update BOTH JOBS_ENDPOINT and TRUSTED_ENDPOINT_HOST to the same hostname in the same edit.
  3. If this surfaced in a test, pass a URL whose hostname is app.flowxtra.com (e.g. https://app.flowxtra.com/api/central/jobs?page=1) rather than example.com.
  4. Search the repo for any code path that calls assertFlowxtraEndpointUrl() with a non-constant argument and make it derive from JOBS_ENDPOINT instead.

Example fix

// before
const JOBS_ENDPOINT = 'https://staging.flowxtra.com/api/central/jobs';
const TRUSTED_ENDPOINT_HOST = 'app.flowxtra.com'; // mismatch -> throws

// after
const JOBS_ENDPOINT = 'https://app.flowxtra.com/api/central/jobs';
const TRUSTED_ENDPOINT_HOST = 'app.flowxtra.com';
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: assert the Flowxtra endpoint constant is intact before scanning.
function checkFlowxtraEndpoint() {
  const u = new URL('https://app.flowxtra.com/api/central/jobs'); // expected literal
  if (u.hostname !== 'app.flowxtra.com') throw new Error('flowxtra endpoint host drifted');
  if (u.protocol !== 'https:') throw new Error('flowxtra endpoint not HTTPS');
}
// Run once at startup; abort the scan if it throws.

Try / catch

// At the scan-runner boundary: isolate one provider's failure so the whole scan continues.
try {
  const jobs = await flowxtraProvider.fetch(entry, ctx);
} catch (err) {
  if (/flowxtra: untrusted hostname/.test(err.message)) {
    console.error(`Skipping ${entry.name}: ${err.message} (constant drift — fix in flowxtra.mjs)`);
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: Fires only when the URL passed to assertFlowxtraEndpointUrl() resolves to a hostname other than app.flowxtra.com. In production that URL is built from the module constant JOBS_ENDPOINT ('https://app.flowxtra.com/api/central/jobs'), so a live throw means someone edited JOBS_ENDPOINT/TRUSTED_ENDPOINT_HOST to disagree, a test injected a foreign URL, or the constant was accidentally set to an http/staging host.

Common situations: A contributor redirects the provider at a mirror/staging host but changes only JOBS_ENDPOINT and forgets TRUSTED_ENDPOINT_HOST; a unit test calls assertFlowxtraEndpointUrl() directly with a throwaway URL; an env-injection or config-merge accidentally rewrites the constant to a different domain.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/3a8ab29ede5dbcf7. Report an issue: GitHub.