{"record":{"id":"680f9583ad9c09b1","repo":"santifer/career-ops","slug":"oraclecloud-untrusted-hostname-parsed-hostname","errorCode":null,"errorMessage":"oraclecloud: untrusted hostname \"${parsed.hostname}\" — must match *.fa[.<region>][.ocs].oraclecloud[1-99].com","messagePattern":"oraclecloud: untrusted hostname \"(.+?)\" — must match \\*\\.fa\\[\\.<region>\\]\\[\\.ocs\\]\\.oraclecloud\\[1-99\\]\\.com","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/oraclecloud.mjs","lineNumber":70,"sourceCode":"const PAGE_SIZE = 200;\nconst MAX_PAGES = 25;             // safety cap (~5000 jobs); hard ceiling like workday\nconst RETRY_POLICY = { retries: 3 };\nconst INTER_PAGE_DELAY_MS = 150;  // WAF-aware spacing between same-host pages\n\n// facetsList is a fixed constant on the finder; %3B is the encoded ';' separator.\nconst FACETS_LIST = 'LOCATIONS%3BWORK_LOCATIONS%3BWORKPLACE_TYPES%3BTITLES%3BCATEGORIES%3BORGANIZATIONS%3BPOSTING_DATES%3BFLEX_FIELDS';\n\n/** @param {string} url */\nfunction assertOracleUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`oraclecloud: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`oraclecloud: URL must use HTTPS: ${url}`);\n  if (!ORACLE_HOST_RE.test(parsed.hostname)) {\n    throw new Error(`oraclecloud: untrusted hostname \"${parsed.hostname}\" — must match *.fa[.<region>][.ocs].oraclecloud[1-99].com`);\n  }\n  return url;\n}\n\n// NaN-safe Date.parse — `|| undefined` would also coerce a valid epoch 0.\n// (copied from greenhouse.mjs)\nfunction toEpochMs(value) {\n  if (!value) return undefined;\n  const parsed = Date.parse(value);\n  return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction sleep(ms, ctx) {\n  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/providers/oraclecloud.mjs#L52-L88","documentation":"Thrown by oraclecloud's assertOracleUrl() when the hostname doesn't match ORACLE_HOST_RE (/^[a-z0-9-]+\\.fa\\.(?:[a-z0-9-]+\\.)?(?:ocs\\.)?oraclecloud(?:[1-9][0-9]?)?\\.com$/i). Third SSRF gate: allows tenant subdomains of Oracle's FA (Fusion Applications) cloud (e.g. acme.fa.eu.oraclecloud.com, acme.fa.ocs.oraclecloud.com, acme.fa.oraclecloud1.com) while blocking everything else. The regex permits an optional region segment, an optional 'ocs.' segment, and an optional numeric suffix on 'oraclecloud'.","triggerScenarios":"The hostname is valid HTTPS but not an Oracle FA cloud host: e.g. 'oraclecloud.com' (bare, no tenant.fa. prefix), 'careers.oracle.com', 'acme.fa.oraclecloud.net' (wrong TLD), or an attacker-controlled domain. Common misforms: missing the '.fa.' segment, using '.oci.' instead of '.fa.', or a non-cloud Oracle domain.","commonSituations":"The entry points to a non-careers Oracle domain (oracle.com instead of oraclecloud.com). The tenant URL was copied incorrectly, missing the .fa. path segment. Oracle restructured their cloud URL format (e.g. new region or ocs subdomain) and the regex needs updating. A developer used an OCI (Oracle Cloud Infrastructure) console URL instead of the HCM career site URL.","solutions":["Verify the URL matches the expected pattern: <tenant>.fa.<region>.oraclecloud.com — confirm the .fa. segment is present.","Get the correct URL from the Oracle HCM career site: navigate to the company's career page and copy the full URL from the browser.","If Oracle introduced a new URL format (new region, new subdomain), update ORACLE_HOST_RE at providers/oraclecloud.mjs:50 to match.","Check for TLD confusion: must be .com, not .net or .cloud."],"exampleFix":"// before — wrong URL shape\ncareers_url: 'https://acme.oraclecloud.com/careers'\n// hostname 'acme.oraclecloud.com' fails: missing '.fa.' segment\n\n// after\ncareers_url: 'https://acme.fa.eu.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1'\n\n// OR if Oracle introduced a new subdomain pattern, update the regex:\n// const ORACLE_HOST_RE = /^[a-z0-9-]+\\.fa\\.(?:[a-z0-9-]+\\.)?(?:ocs\\.)?oraclecloud(?:[1-9][0-9]?)?\\.com$/i;","handlingStrategy":"validation","validationCode":"const ORACLE_HOST_RE = /^[a-z0-9-]+\\.fa\\.(?:[a-z0-9-]+\\.)?(?:ocs\\.)?oraclecloud(?:[1-9][0-9]?)?\\.com$/i;\n\n/** Check hostname matches Oracle FA cloud pattern. */\nfunction isOracleFaHost(url) {\n  try { return ORACLE_HOST_RE.test(new URL(url).hostname); } catch { return false; }\n}\n\nif (!isOracleFaHost(entry.careers_url)) {\n  console.warn(`oraclecloud entry ${entry.name} URL doesn't match *.fa.*.oraclecloud.com`);\n  continue;\n}","typeGuard":"/** @param {string} url @returns {boolean} */\nfunction isOracleCloudUrl(url) {\n  const ORACLE_HOST_RE = /^[a-z0-9-]+\\.fa\\.(?:[a-z0-9-]+\\.)?(?:ocs\\.)?oraclecloud(?:[1-9][0-9]?)?\\.com$/i;\n  try { return ORACLE_HOST_RE.test(new URL(url).hostname); } catch { return false; }\n}","tryCatchPattern":"try {\n  await oracleProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).includes('untrusted hostname')) {\n    console.warn(`oraclecloud entry ${entry.name} wrong host — needs *.fa.*.oraclecloud.com`);\n    continue;\n  }\n  throw err;\n}","preventionTips":["Verify Oracle URLs match <tenant>.fa.<region>.oraclecloud.com — the .fa. segment is mandatory.","Copy career site URLs directly from the browser when visiting the company's Oracle HCM career page.","Update ORACLE_HOST_RE if Oracle introduces new subdomain patterns.","Distinguish OCI console URLs from HCM career site URLs — only the latter are valid."],"tags":["url-validation","ssrf-guard","hostname-regex","oraclecloud","security"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}