santifer/career-ops · error · Error
oraclecloud: untrusted hostname
Error message
oraclecloud: untrusted hostname "${parsed.hostname}" — must match *.fa[.<region>][.ocs].oraclecloud[1-99].com What it means
assertOracleUrl validates a careers URL before any network request is made to Oracle Recruiting (Fusion) boards. The URL parsed fine and used HTTPS, but its hostname does not match the allowlist regex ORACLE_HOSTRE (/^[a-z0-9-]+\.fa\.(?:[a-z0-9-]+\.)?(?:ocs\.)?oraclecloud(?:[1-9][0-9]?)?\.com$/i), so the provider refuses to send a request to a host it does not recognize. This is an SSRF/trust guard, not a network failure — the request was never attempted.
Solutions
- Open portals.yml and fix the entry's careers_url to the real Fusion board host matching <tenant>.fa[.<region>][.ocs].oraclecloud[1-99].com (e.g. https://acme.fa.ocs.oraclecloud.com).
- Verify the hostname with the regex: node -e "console.log(/^[a-z0-9-]+\\.fa\\.(?:[a-z0-9-]+\\.)?(?:ocs\\.)?oraclecloud(?:[1-9][0-9]?)?\\.com$/i.test('acme.fa.ocs.oraclecloud.com'))".
- If Oracle genuinely serves this tenant on a new host shape, extend ORACLE_HOST_RE in providers/oraclecloud.mjs (line 50) and update the error message to match.
- Point the entry at the tenant's API base URL rather than a vanity domain; the provider derives API calls from the allowlisted host.
Example fix
// before (portals.yml) careers_url: https://careers.acme.com/jobs // after careers_url: https://acme.fa.ocs.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1
Defensive patterns
Strategy: validation
Validate before calling
const ORACLE_HOST_RE = /^[a-z0-9-]+\.fa\.(?:[a-z0-9-]+\.)?(?:ocs\.)?oraclecloud(?:[1-9][0-9]?)?\.com$/i;
export function isOracleUrl(url) {
try {
const u = new URL(url);
return u.protocol === 'https:' && ORACLE_HOST_RE.test(u.hostname);
} catch { return false; }
}
if (!isOracleUrl(entry.careers_url)) throw new Error(`oraclecloud: untrusted or invalid careers_url for ${entry.name}`); Type guard
function isOracleUrl(u) {
if (typeof u !== 'string') return false;
try {
const parsed = new URL(u);
return parsed.protocol === 'https:' &&
/^[a-z0-9-]+\.fa\.(?:[a-z0-9-]+\.)?(?:ocs\.)?oraclecloud(?:[1-9][0-9]?)?\.com$/i.test(parsed.hostname);
} catch { return false; }
} Try / catch
try {
await oracleProvider.fetch(entry, ctx);
} catch (e) {
if (String(e.message).startsWith('oraclecloud: untrusted hostname')) {
logger.warn({ entry: entry.name, url: entry.careers_url }, 'careers_url not on Oracle allowlist — check portals.yml');
return null; // skip entry, keep scanning
}
throw e;
} Prevention
- Validate every portals.yml careers_url against the provider's host regex at config load time, before any scan starts.
- Run audit-portals.mjs after editing portals.yml to catch entries no provider claims.
- Never point careers_url at vanity/redirect domains; use the tenant's ATS-native hostname.
- Add a CI check that runs the provider's assert function against all entries in portals.yml.
- Copy URLs from the address bar including the https:// scheme.
When it happens
Trigger: Calling providers/oraclecloud.mjs fetch (directly or via scan.mjs/verify-portals.mjs) with an entry whose careers_url points at a hostname outside the Oracle Fusion pattern, e.g. a custom vanity domain, a non-FA Oracle host (myhost.oraclecloud.com without .fa), a region misspelled, or a subdomain typo like acme.fa..oraclecloud.com.
Common situations: portals.yml misconfiguration: someone pasted the marketing careers page URL instead of the Fusion board URL; a company migrated to a vanity CNAME (careers.acme.com); the tenant uses a new regional host shape the regex doesn't yet allow; or a trailing/extra dot in the hostname.
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
- comeet: URL path must be the careers-api endpoint
- flowxtra: URL must use HTTPS
- personio: untrusted hostname
- pinpoint: untrusted hostname
- breezy: invalid URL
AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16).
Data as JSON: /api/errors/680f9583ad9c09b1.
Report an issue: GitHub.
Appendix: source
Thrown at providers/oraclecloud.mjs:70
const PAGE_SIZE = 200;
const MAX_PAGES = 25; // safety cap (~5000 jobs); hard ceiling like workday
const RETRY_POLICY = { retries: 3 };
const INTER_PAGE_DELAY_MS = 250; // WAF-aware spacing between same-host pages
// facetsList is a fixed constant on the finder; %3B is the encoded ';' separator.
const FACETS_LIST = 'LOCATIONS%3BWORK_LOCATIONS%3BWORKPLACE_TYPES%3BTITLES%3BCATEGORIES%3BORGANIZATIONS%3BPOSTING_DATES%3BFLEX_FIELDS';
/** @param {string} url */
function assertOracleUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`oraclecloud: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`oraclecloud: URL must use HTTPS: ${url}`);
if (!ORACLE_HOST_RE.test(parsed.hostname)) {
throw new Error(`oraclecloud: untrusted hostname "${parsed.hostname}" — must match *.fa[.<region>][.ocs].oraclecloud[1-99].com`);
}
return url;
}
// NaN-safe Date.parse — `|| undefined` would also coerce a valid epoch 0.
// (copied from greenhouse.mjs)
function toEpochMs(value) {
if (!value) return undefined;
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? undefined : parsed;
}
function sleep(ms, ctx) {
if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**View on GitHub (pinned to e7abd431fc)