{"record":{"id":"5d4d7f7c9d6d122e","repo":"santifer/career-ops","slug":"invalid-or-blocked-url-rejected-reason","errorCode":null,"errorMessage":"Invalid or blocked URL: ${rejected.reason}","messagePattern":"Invalid or blocked URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"batch-evaluate-gemini.mjs","lineNumber":172,"sourceCode":"═══════════════════════════════════════════════════════\n1. You do NOT have access to WebSearch, Playwright, or file writing tools.\n2. Generate Blocks A through G in full, in English.\n3. Output a machine-readable summary block in this exact format:\n\n---SCORE_SUMMARY---\nCOMPANY: <company name>\nROLE: <role title>\nSCORE: <global score as decimal, e.g. 3.8>\nARCHETYPE: <detected archetype>\nLEGITIMACY: <High Confidence | Proceed with Caution | Suspicious>\n---END_SUMMARY---\n`;\n}\n\nasync function scrapeUrl(browser, url) {\n  const rejected = rejectPrivateOrInvalid(url);\n  if (rejected) {\n    throw new Error(`Invalid or blocked URL: ${rejected.reason}`);\n  }\n  \n  const page = await browser.newPage();\n  try {\n    await page.route('**/*', (route) => {\n      const targetUrl = route.request().url();\n      const interceptedRejected = rejectPrivateOrInvalid(targetUrl);\n      if (interceptedRejected) {\n        return route.abort('accessdenied');\n      }\n      return route.continue();\n    });\n\n    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });\n    \n    const finalRejected = rejectPrivateOrInvalid(page.url());\n    if (finalRejected) {\n      throw new Error(`Invalid or blocked URL after redirect: ${finalRejected.reason}`);","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/santifer/career-ops/blob/60398d6549a46f5266929538af21cfab94badc75/batch-evaluate-gemini.mjs#L154-L190","documentation":"Thrown by scrapeUrl() in batch-evaluate-gemini.mjs, the batch worker's Playwright scraper, when rejectPrivateOrInvalid() (the shared SSRF/egress guard from liveness-browser.mjs) rejects the URL before navigation: unparseable URL, non-http(s) protocol, or a private/loopback host (localhost, 127/8, 10/8, 172.16-31, 192.168/16, 169.254/16, ::1, ::, fc00::/7, fe80::, ::ffff: mapped forms). The same guard is also installed per-request so subresource and redirect requests to private ranges are aborted with 'accessdenied'.","triggerScenarios":"A batch input/queue containing `http://localhost:8080/jobs`, an intranet ATS URL, `ftp://…`, or a scheme-less string like 'company.com/careers/123'. Each URL in the batch file is scraped in turn, so one bad entry aborts that entry's evaluation.","commonSituations":"Bulk queue assembled from mixed sources including dev/staging links; a teammate's exported list with internal URLs; entries pasted without the https:// scheme; retrying a batch originally captured on a VPN-only network.","solutions":["Remove or fix the offending entries in the batch input: public https URLs only","Re-add the scheme to bare-domain entries: https://company.com/careers/123","For internal-only postings, archive them manually into jds/ and evaluate from the capture instead of scraping","Pre-filter the list before the run: node -e with rejectPrivateOrInvalid() from liveness-browser.mjs to report all offenders at once"],"exampleFix":"# before (batch input entry)\nhttp://10.1.2.3/ats/job/9317\n\n# after (batch input entry)\nhttps://ats.acme-public.com/job/9317","handlingStrategy":"validation","validationCode":"import { rejectPrivateOrInvalid } from './liveness-browser.mjs';\nfunction partitionUrls(urls) {\n  const ok = [], blocked = [];\n  for (const u of urls) {\n    (rejectPrivateOrInvalid(u) === null ? ok : blocked).push(u);\n  }\n  return { ok, blocked };\n}\n// run the batch with ok; surface blocked to the user instead of failing mid-batch","typeGuard":"function isScrapableUrl(url) {\n  try { return rejectPrivateOrInvalid(url) === null; } catch { return false; }\n}","tryCatchPattern":"try {\n  await scrapeUrl(browser, url);\n} catch (e) {\n  if (/Invalid or blocked URL/.test(e.message)) {\n    results.push({ url, error: 'blocked-or-invalid-url', skipped: true });\n    continue; // keep the batch going\n  }\n  throw e;\n}","preventionTips":["Curate batch queues to public https URLs only; strip internal/staging entries before the run","Use the exported rejectPrivateOrInvalid() to pre-screen the whole list and fix entries in one pass","Internal postings: archive manually into jds/ and evaluate from the local capture — the guard is by design"],"tags":["security","ssrf","url-validation","batch","network","gemini-eval"],"backgroundTag":"ssrf-url-blocked","analyzedSha":"60398d6549a46f5266929538af21cfab94badc75","analyzedAt":"2026-08-20T23:00:06.764Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}