santifer/career-ops · error · Error

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

Error message

jobstreet: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_JOBSTREET_HOSTS].join(', ')}

What it means

Thrown by assertJobstreetUrl when entry.api's hostname is not in ALLOWED_JOBSTREET_HOSTS (id.jobstreet.com, www.jobstreet.com, www.jobstreet.co.id, jobstreet.com, jobstreet.co.id, sg.jobstreet.com, my.jobstreet.com, www.seek.com.au, www.seek.co.nz). This is the core SSRF allowlist; it fires when a user points api: at a Jobstreet/SEEK regional host the provider does not yet allow.

Source

Thrown at providers/jobstreet.mjs:60

  'www.seek.co.nz',
]);

// v5 API paths (the client-side JS on jobstreet uses these relative paths
// resolved against the current origin). We keep the allowlist for SSRF
// protection on the base URL, then build the v5 search path from it.
const V5_SEARCH_PATH = '/api/jobsearch/v5/search';

/** @param {string} url */
function assertJobstreetUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`jobstreet: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`jobstreet: URL must use HTTPS: ${url}`);
  if (!ALLOWED_JOBSTREET_HOSTS.has(parsed.hostname))
    throw new Error(`jobstreet: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_JOBSTREET_HOSTS].join(', ')}`);
  return url;
}

/**
 * Derive the origin from the API hostname.
 * e.g. id.jobstreet.com → https://id.jobstreet.com
 * @param {string} apiUrl
 * @returns {string}
 */
function deriveOrigin(apiUrl) {
  try {
    const parsed = new URL(apiUrl);
    return `${parsed.protocol}//${parsed.hostname}`;
  } catch {
    return 'https://id.jobstreet.com';
  }
}

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. If the regional host is legitimate and trusted, add it to ALLOWED_JOBSTREET_HOSTS in providers/jobstreet.mjs (and add a regression test).
  2. Otherwise switch api: to one of the allowed hosts, or omit api: to use DEFAULT_API (id.jobstreet.com).
  3. Double-check the hostname spelling against the allowlist in the error message.

Example fix

// before — regional host not in the allowlist
const ALLOWED_JOBSTREET_HOSTS = new Set(['id.jobstreet.com', /* ... */]);

// after — add the Philippine market
const ALLOWED_JOBSTREET_HOSTS = new Set(['id.jobstreet.com', 'ph.jobstreet.com', /* ... */]);
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
// Hard-code the same allowlist the provider uses, for a pre-scan lint.
const ALLOWED = new Set(['id.jobstreet.com','www.jobstreet.com','www.jobstreet.co.id','jobstreet.com','jobstreet.co.id','sg.jobstreet.com','my.jobstreet.com','www.seek.com.au','www.seek.co.nz']);
function assertJobstreetApiHost(apiUrl) {
  const u = new URL(apiUrl);
  if (!ALLOWED.has(u.hostname)) {
    throw new Error(`jobstreet: ${u.hostname} not allowlisted — add it to ALLOWED_JOBSTREET_HOSTS or use a supported host`);
  }
}

Type guard

/** True for a URL whose host is in the Jobstreet/SEEK allowlist. */
function isJobstreetHostAllowed(url) {
  try { return ALLOWED.has(new URL(url).hostname); } catch { return false; }
}

Try / catch

try {
  return await jobstreetProvider.fetch(entry, ctx);
} catch (err) {
  if (/untrusted hostname/.test(err.message)) {
    console.error(`config: ${entry.name} — ${err.message} (add the host to ALLOWED_JOBSTREET_HOSTS if legitimate)`);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Setting api: to a regional host not in the allowlist — e.g. ph.jobstreet.com (Philippines), th.jobstreet.com (Thailand), hk.jobstreet.com (Hong Kong), or a SEEK host other than the two Australian/NZ ones; pointing api: at a wholly different (non-Jobstreet) host.

Common situations: Targeting a Jobstreet market the provider was not configured for (PH/TH/HK are common omissions); assuming any *.jobstreet.com subdomain is accepted (it is not — the set is explicit); a typo in the hostname.

Related errors


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