{"record":{"id":"a21261888fc2e568","repo":"santifer/career-ops","slug":"eightfold-invalid-url-url","errorCode":null,"errorMessage":"eightfold: invalid URL: ${url}","messagePattern":"eightfold: invalid URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"providers/eightfold.mjs","lineNumber":69,"sourceCode":"const MAX_PAGES_CAP = 1000;\n// Same-host pacing between pages inside one tenant's own pagination loop.\n// Eightfold's edge rate-limits bursts, and a 616-job board is 62 requests.\nconst INTER_PAGE_DELAY_MS = 250;\n\nconst RETRY_POLICY = { retries: 3, baseDelayMs: 500, maxDelayMs: 8_000 };\n\n/**\n * SSRF guard — every request URL passes through here before it is fetched.\n *\n * @param {string} url\n * @returns {string} the same URL, when it is a trusted Eightfold endpoint.\n */\nfunction assertEightfoldUrl(url) {\n  let parsed;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error(`eightfold: invalid URL: ${url}`);\n  }\n  if (parsed.protocol !== 'https:') throw new Error(`eightfold: URL must use HTTPS: ${url}`);\n  if (!EIGHTFOLD_HOST_RE.test(parsed.hostname)) {\n    throw new Error(`eightfold: untrusted hostname \"${parsed.hostname}\" — must match *.eightfold.ai`);\n  }\n  return url;\n}\n\n/** @param {number} ms @param {any} ctx */\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/**\n * Eightfold reports timestamps as epoch SECONDS (`t_create`, `t_update`), not\n * the ISO strings every other provider gets. Converted here; anything\n * non-finite or non-positive is dropped rather than guessed at.","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/eightfold.mjs#L51-L87","documentation":"assertEightfoldUrl() is the provider's SSRF guard: every request URL must parse as a URL, use HTTPS, and have a hostname matching *.eightfold.ai before any fetch happens. This specific throw fires when `new URL(url)` itself throws — the input is not a syntactically valid absolute URL (missing scheme, spaces, empty string, etc.). It is a fail-fast validation so a malformed configured URL never reaches the network layer.","triggerScenarios":"assertEightfoldUrl is called with a value that cannot be parsed by the WHATWG URL constructor: an empty string, a bare tenant name like 'bayer.eightfold.ai' with no scheme, a URL with invalid characters, or a config value that is undefined/null coerced into the call path.","commonSituations":"Portals.yml entry with `api:` set to 'bayer.eightfold.ai/careers' (missing https://); an env-var or CLI substitution that expanded to an empty string; a copy-pasted URL containing stray whitespace or a newline; a template placeholder like {tenant} that was never filled in.","solutions":["Add the scheme to the configured URL: use https://<tenant>.eightfold.ai/careers, not a bare hostname.","Print/inspect the actual offending value from the error message — it is interpolated verbatim, so an empty string means an unset config field.","Trim whitespace and re-check for typos or unexpanded placeholders in the portals.yml entry.","Validate the URL with `new URL(value)` in a quick Node snippet before putting it into config."],"exampleFix":"// before (portals.yml)\n- name: bayer\n  api: bayer.eightfold.ai/careers\n// after\n- name: bayer\n  api: https://bayer.eightfold.ai/careers","handlingStrategy":"validation","validationCode":"function assertUsableEightfoldUrl(url) {\n  if (typeof url !== 'string' || !url.trim()) throw new Error('eightfold URL is empty/missing');\n  const u = new URL(url); // throws SyntaxError on malformed input, same condition as the provider\n  return u;\n}\nassertUsableEightfoldUrl(entry.api || entry.careers_url);","typeGuard":"function isAbsoluteHttpUrl(v) {\n  if (typeof v !== 'string') return false;\n  try { new URL(v); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  await provider.fetch(entry, ctx);\n} catch (e) {\n  if (e.message.startsWith('eightfold: invalid URL:')) {\n    // value was not parseable — log the exact configured value and stop, don't retry\n    console.error(`Bad url in entry ${entry.name}: ${JSON.stringify(e.message)}`);\n  } else throw e;\n}","preventionTips":["Always write tenant URLs with an explicit https:// scheme in portals.yml.","Lint config for empty or placeholder values ({tenant}, TODO) before running scans.","Test each new URL with `new URL(v)` in a one-liner before committing it.","Watch for shell/env substitution silently producing empty strings in config."],"tags":["url","validation","config","ssrf-guard"],"backgroundTag":"invalid-url-format","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}