santifer/career-ops · error · Error
solidjobs: untrusted hostname
Error message
solidjobs: untrusted hostname "${parsed.hostname}" — must be solid.jobs What it means
assertUrl pins the hostname to ALLOWED_HOSTS (only solid.jobs): a valid HTTPS URL on any other host — www.solid.jobs, a mirror, a branded domain — is rejected. This is a host-pinning/SSRF guard ensuring the provider only ever fetches the official SolidJobs public API with its /public-api/offers/ path prefix.
Solutions
- Use exactly https://solid.jobs/public-api/offers/<division> — no www, no alternate TLD
- Rely on detect(), which auto-accepts only solid.jobs + /public-api/offers/ URLs, rather than hand-built URLs
- Fix hostname typos in portals.yml and re-run
- If SolidJobs ever serves the API from a new official host, update ALLOWED_HOSTS in providers/solidjobs.mjs
Example fix
// before careers_url: 'https://www.solid.jobs/public-api/offers/it' // www not allowed // after careers_url: 'https://solid.jobs/public-api/offers/it'
Defensive patterns
Strategy: validation
Validate before calling
function isTrustedSolidjobsUrl(url) {
try {
const u = new URL(url);
return u.protocol === 'https:' &&
u.hostname === 'solid.jobs' &&
u.pathname.startsWith('/public-api/offers/');
} catch { return false; }
}
if (!isTrustedSolidjobsUrl(entry.careers_url)) throw new Error('must be https://solid.jobs/public-api/offers/<division>'); Type guard
const isSolidjobsApiUrl = (url) => {
try {
const u = new URL(url);
return u.hostname === 'solid.jobs' && u.pathname.startsWith('/public-api/offers/');
} catch { return false; }
}; Try / catch
try {
await solidjobsProvider.fetch(entry, ctx);
} catch (e) {
if (String(e.message).includes('untrusted hostname')) {
console.error(`Entry ${entry.name}: host must be exactly solid.jobs (no www, no TLD variants)`);
} else throw e;
} Prevention
- Use the exact host solid.jobs — the www variant fails the exact-match check
- Ensure the path starts with /public-api/offers/ (the host check alone is not enough; the next check rejects wrong paths)
- Copy URLs from the documented API form rather than the marketing site
- Run detect() against the entry first — it only matches fully valid solid.jobs API URLs
When it happens
Trigger: careers_url pointing at https://www.solid.jobs/... (www fails the exact-host check), a typo'd host (solidjob.com, solid.jobs.eu), or a proxy/rewrite host; calling assertUrl with a non-API page on solid.jobs would pass the host check but then fail the path check instead.
Common situations: Adding www. to the domain out of habit; copying a marketing-site URL (solid.jobs landing page) instead of the API URL; typos; confusing the public careers site with the public-api endpoint.
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
- smartrecruiters: untrusted hostname
- senjob: untrusted hostname
- workable: untrusted hostname
- collage: untrusted hostname
- comeet: URL path must be the careers-api endpoint
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/95705e26b2157680.
Report an issue: GitHub.
Appendix: source
Thrown at providers/solidjobs.mjs:29
/**
* Validates that the provided URL is a trusted SolidJobs API endpoint.
* Enforces HTTPS protocol, strict hostname matching, and required path prefix.
*
* @param {string} url - The URL string to validate.
* @returns {string} The validated URL string.
* @throws {Error} If the URL is malformed, uses non-HTTPS, has an untrusted host, or wrong path.
*/
function assertUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`solidjobs: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`solidjobs: URL must use HTTPS: ${url}`);
if (!ALLOWED_HOSTS.has(parsed.hostname))
throw new Error(`solidjobs: untrusted hostname "${parsed.hostname}" — must be solid.jobs`);
if (!parsed.pathname.startsWith('/public-api/offers/'))
throw new Error(`solidjobs: URL path must start with /public-api/offers/: ${url}`);
return url;
}
/** @type {Provider} */
export default {
id: 'solidjobs',
/**
* Attempts to detect if the provider can handle the given entry by checking the careers_url.
* * @param {{ careers_url?: string, name?: string }} entry - The configuration entry.
* @returns {{url: string} | null} An object with the matched URL, or null if not matched.
*/
detect(entry) {
const url = entry.careers_url || '';
try {
const parsed = new URL(url);View on GitHub (pinned to aac998c7ed)