santifer/career-ops · error · Error
jobstreet: untrusted hostname
Error message
jobstreet: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_JOBSTREET_HOSTS].join(', ')} What it means
assertJobstreetUrl() only accepts hostnames in ALLOWED_JOBSTREET_HOSTS (id/www.jobstreet.com, jobstreet.com/.co.id, sg/my.jobstreet.com, hk.jobsdb.com, www.seek.com.au, www.seek.co.nz). A parsed URL whose hostname is not in that set throws this error, blocking SSRF via the configurable `api:` URL.
Solutions
- Set `api:` to one of the allowlisted hosts — e.g. https://id.jobstreet.com or https://www.seek.com.au depending on the market.
- Pick the matching siteKey for that market (ID-Main, SG-Main, MY-Main, HK-Main) instead of changing the host.
- If you genuinely need another SEEK-platform host, add it to ALLOWED_JOBSTREET_HOSTS in providers/jobstreet.mjs after confirming it is the real SEEK infrastructure.
Example fix
// before (portals.yml) provider: jobstreet api: https://jobstreet.com.au/api/jobsearch/v5/search // after provider: jobstreet api: https://www.seek.com.au/api/jobsearch/v5/search siteKey: AUD-Main
Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = ['id.jobstreet.com','www.jobstreet.com','www.jobstreet.co.id','jobstreet.com','jobstreet.co.id','sg.jobstreet.com','my.jobstreet.com','hk.jobsdb.com','www.seek.com.au','www.seek.co.nz'];
if (!ALLOWED.includes(new URL(entry.api).hostname)) throw new Error('hostname not allowlisted for jobstreet'); Type guard
const isAllowedJobstreetHost = (s) => { try { return ALLOWED_JOBSTREET_HOSTS.has(new URL(s).hostname); } catch { return false; } }; Try / catch
try {
await provider.fetch(entry, ctx);
} catch (e) {
if (e.message.includes('untrusted hostname')) {
console.error(`Fix ${entry.name}: use an allowlisted SEEK/Jobstreet host`);
}
} Prevention
- Pick the host from the documented market table (ID-Main/SG-Main/MY-Main/HK-Main) rather than typing a URL by hand.
- Remember Hong Kong is hk.jobsdb.com and Australia/NZ are seek.com.au / seek.co.nz, not jobstreet domains.
- Never point api: at proxies or mirror hosts; they will always be rejected.
When it happens
Trigger: `provider: jobstreet` with `api:` pointing at a different host — e.g. a company's own domain, a proxy like localhost, a typo like jobstreet.com.au, or x.jobstreet.com.evil.io.
Common situations: Typing a regional variant not on the allowlist (au.jobstreet.com, th.jobsdb.com); pointing api: at a corporate gateway; a typo squatted hostname after a copy-paste.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- torre: untrusted hostname
- breezy: untrusted hostname
- builtin: untrusted hostname
- careerviet: untrusted hostname
- flowxtra: untrusted hostname
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16).
Data as JSON: /api/errors/f49a7c840832d032.
Report an issue: GitHub.
Appendix: source
Thrown at providers/jobstreet.mjs:86
/** @param {string} origin — scheme + hostname */
function jobDetailPath(origin) {
let host = '';
try { host = new URL(origin).hostname; } catch { /* fall through to the common path */ }
return ID_LOCALE_HOSTS.has(host) ? '/id/job/' : '/job/';
}
/** @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 e7abd431fc)