santifer/career-ops · error · Error
consider: ${entry.name} needs an https careers_url on a publ
Error message
consider: ${entry.name} needs an https careers_url on a public host What it means
Thrown by the consider provider's fetch() when resolveOrigin(entry) returns null. resolveOrigin parses entry.careers_url and requires https, plus a public, non-internal hostname: it rejects IP literals (IPv4/IPv6), localhost, .local/.internal suffixes, and single-label hosts. Consider boards are always real registrable domains (jobs.founderful.com, etc.), so the guard both validates config and prevents an SSRF via a crafted careers_url aiming the POST at a private/metadata host.
Source
Thrown at providers/consider.mjs:92
}
if (Array.isArray(job.normalizedLocations) && job.normalizedLocations.length) {
return job.normalizedLocations.map(l => l?.label || l?.value).filter(Boolean).join(', ');
}
return job.remote ? 'Remote' : '';
}
/** @type {Provider} */
export default {
id: 'consider',
detect(entry) {
const origin = resolveOrigin(entry);
return entry.consider_board && origin ? { url: origin + ENDPOINT_PATH } : null;
},
async fetch(entry, ctx) {
const origin = resolveOrigin(entry);
if (!origin) throw new Error(`consider: ${entry.name} needs an https careers_url on a public host`);
if (!entry.consider_board) throw new Error(`consider: ${entry.name} needs a 'consider_board' id in portals.yml`);
const size = Number.isInteger(entry.consider_size) && entry.consider_size > 0 ? entry.consider_size : DEFAULT_SIZE;
const json = await ctx.fetchJson(origin + ENDPOINT_PATH, {
method: 'POST',
// redirect:'error' so a 3xx from the (config-driven) board host can't be
// followed to a private/metadata IP — the host guard above pins the first hop.
redirect: 'error',
headers: { 'content-type': 'application/json', accept: 'application/json', referer: origin + '/jobs' },
body: JSON.stringify({
meta: { size },
board: { id: String(entry.consider_board), isParent: true },
query: { promoteFeatured: true },
}),
});
const jobs = Array.isArray(json?.jobs) ? json.jobs : [];
return jobsView on GitHub (pinned to 9b17a8ac97)
Solutions
- Set careers_url to the board's public https URL, e.g. https://jobs.founderful.com/jobs.
- If you need a staging board, host it on a real public domain — the guard deliberately rejects internal hosts.
- Gate with provider.detect(entry) (returns null when consider_board is missing OR origin fails) before calling fetch().
Example fix
# before — internal host, rejected by SSRF guard - name: Founderful provider: consider consider_board: wingman careers_url: https://founderful.internal/jobs # after — public https host - name: Founderful provider: consider consider_board: wingman careers_url: https://jobs.founderful.com/jobs
Defensive patterns
Strategy: validation
Validate before calling
import consider from './providers/consider.mjs';
// detect() requires BOTH a public https origin AND a consider_board id.
if (!consider.detect(entry)) {
// origin failed (this error) OR consider_board missing — check careers_url first
} Type guard
/** True when careers_url is a public https host (not IP/localhost/internal). */
function hasPublicHttpsOrigin(entry) {
const raw = typeof entry?.careers_url === 'string' ? entry.careers_url : '';
if (!raw) return false;
let host;
try { host = new URL(raw).hostname.toLowerCase(); } catch { return false; }
if (new URL(raw).protocol !== 'https:') return false;
if (host.endsWith('.')) host = host.slice(0, -1);
if (host.startsWith('[') || host.includes(':')) return false;
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return false;
if (host === 'localhost' || host === 'localhost.localdomain') return false;
if (host.endsWith('.local') || host.endsWith('.internal')) return false;
return host.includes('.');
} Try / catch
try { await consider.fetch(entry, ctx); }
catch (e) {
if (/^consider: .* needs an https careers_url/.test(e.message)) {
// SSRF/config guard — fix careers_url to a public https host; do not retry as-is
} else throw e;
} Prevention
- Use the board's real public domain (jobs.founderful.com), never an internal host.
- The SSRF guard intentionally rejects IP literals, localhost, and .local/.internal — do not try to bypass it.
- Run detect() before fetch().
When it happens
Trigger: entry.careers_url is missing, unparseable, non-https, an IP literal, 'localhost', a .local/.internal host, or a single-label (no dot) host. Note this is checked before consider_board, so a missing consider_board surfaces as a different error (line 93) once the origin resolves.
Common situations: Omitting careers_url; using an http:// URL; pointing at an internal staging host (*.internal) that the SSRF guard intentionally blocks; a malformed URL pasted from a rich-text source.
Related errors
- breezy: cannot derive API URL for ${entry.name}
- comeet: cannot derive API URL for ${entry.name} (set api: to
- csod: cannot resolve careersite URL for ${entry.name}
- eightfold: cannot derive API URL for ${entry.name}
- deutschebahn: cannot resolve db.jobs search id for ${entry.n
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/fa4d28b05f4b0c5a.
Report an issue: GitHub.