santifer/career-ops · error · Error

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

Error message

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

What it means

assertJobBankUrl restricts requests to a single trusted host (TRUSTED_HOST, jobbank.canada.ca). Even a valid HTTPS URL pointing at another hostname is rejected with this error. This is an SSRF/supply-chain guard: the provider will never fetch from an arbitrary domain.

Source

Thrown at providers/jobbankca.mjs:102

}

/** @param {string} keyword @param {number} page */
export function buildFeedUrl(keyword, page) {
  const params = new URLSearchParams({ searchstring: keyword, locationstring: '', page: String(page) });
  return `${FEED_URL}?${params.toString()}`;
}

/** @param {string} url */
export function assertJobBankUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`jobbankca: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`jobbankca: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== TRUSTED_HOST) {
    throw new Error(`jobbankca: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
  }
  return url;
}

// Resolve an Atom element's inner text: unwrap CDATA, else decode entities.
function extractText(inner) {
  const cdata = inner.match(/^\s*<!\[CDATA\[([\s\S]*?)\]\]>\s*$/);
  if (cdata) return cdata[1].trim();
  return decodeEntities(inner).trim();
}

function tagText(block, tag) {
  const m = block.match(new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)</${tag}>`, 'i'));
  return m ? extractText(m[1]) : '';
}

// <link rel="alternate" type="text/html" href="..."/> — an attribute here,
// not inner text (Atom), unlike an RSS <link>text</link>. An Atom entry may

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Point the URL back at the official trusted host (jobbank.canada.ca)
  2. For local testing, intercept at the HTTP client level (e.g. nock/undici mock) instead of changing the URL hostname
  3. If you genuinely need another domain, that belongs in a different provider module, not jobbankca
  4. Check the hostname for subtle typos or appended paths that make the host differ (host with port or subdomain won't match)

Example fix

// before
const url = 'https://job-bank.canada.ca/atom.xml';
// after
const url = 'https://jobbank.canada.ca/atom.xml';
Defensive patterns

Strategy: validation

Validate before calling

const TRUSTED = 'jobbank.canada.ca';
function isTrustedHost(u) { try { return new URL(u).hostname === TRUSTED; } catch { return false; } }
if (!isTrustedHost(cfg.url)) throw new Error(`config: host not allowed for jobbankca: ${cfg.url}`);

Type guard

function isTrustedJobBankUrl(v) {
  try { return new URL(v).hostname === 'jobbank.canada.ca'; } catch { return false; }
}

Try / catch

try {
  assertJobBankUrl(url);
} catch (e) {
  if (e.message.includes('untrusted hostname')) {
    console.error(`jobbankca only fetches from jobbank.canada.ca; got: ${url}`);
    return null; // skip entry
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling assertJobBankUrl with an https URL whose hostname is not the trusted host — e.g. a mirror domain, a typo like jobbank.canada.ca.evil.io, job-bank.canada.ca, or a test stub URL like https://localhost:3000/atom.xml.

Common situations: Using a proxy or mock server URL in dev, typo'd host in portals.yml, or intentionally pointing the provider at a local caching mirror expecting it to work.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/40a14a401389a61b. Report an issue: GitHub.