santifer/career-ops · info

Blocked request to restricted destination: ${requestUrl}

Error message

   Blocked request to restricted destination: ${requestUrl} (${verdict.reason})

What it means

installEgressGuard in archive-posting.mjs — the shared two-layer guard re-exported from liveness-browser.mjs — aborted a request made while archiving a JD. Layer one (rejectPrivateOrInvalid) matched a literal private or invalid host, or layer two (validateUrlSecurity) resolved the hostname and found it dead or resolving into private space. This is the archiver's SSRF protection operating as designed; the navigation continues without the blocked subresource.

Source

Thrown at archive-posting.mjs:289

/**
 * Register the egress guard on a Playwright context.
 *
 * Registered on the *context* rather than the page: a route bound to a single
 * page doesn't cover requests the flow makes outside it, and the context is
 * what owns the whole navigation. Both layers of the shared guard run here —
 * the literal-host check first (cheap, no network), then the DNS re-check that
 * catches a public hostname resolving into private space.
 *
 * @param {import('playwright').BrowserContext} context - Context to guard.
 */
export async function installEgressGuard(context) {
  await context.route('**/*', async (route) => {
    const requestUrl = route.request().url();

    const verdict = rejectPrivateOrInvalid(requestUrl);
    if (verdict) {
      console.warn(`   Blocked request to restricted destination: ${requestUrl} (${verdict.reason})`);
      return route.abort('blockedbyclient');
    }

    try {
      await validateUrlSecurity(requestUrl);
      return route.continue();
    } catch (err) {
      console.warn(`   Blocked request to restricted destination (DNS): ${requestUrl} - ${err.message}`);
      return route.abort('blockedbyclient');
    }
  });
}

export async function archiveUrl(browser, url, { company: companyHint, role: roleHint } = {}) {
  console.log(`\n🔗  ${url}`);

  // Refuse before launching any navigation, so an obviously-internal target
  // never reaches Playwright at all.

View on GitHub (pinned to 60398d6549)

Solutions

  1. No action — subresource blocks do not affect the archived capture.
  2. If the archive fails outright, check whether the posting URL itself (the main frame) was blocked — that URL must be public.
  3. Keep the guard; these lines are audit telemetry of exactly what was refused.
Defensive patterns

Strategy: validation

Validate before calling

import { rejectPrivateOrInvalid, validateUrlSecurity } from './liveness-browser.mjs';
const preflight =
  rejectPrivateOrInvalid(url) ??
  (await validateUrlSecurity(url).then(() => null, (e) => e));
if (preflight) throw new Error(`Refusing non-public archive URL up front: ${url}`);

Prevention

When it happens

Trigger: The archived posting page requests trackers or scripts on localhost, RFC1918/link-local IPs, .local hosts, or non-http(s) schemes; a public hostname resolves to a private IP (DNS rebinding); a dead analytics host fails DNS resolution.

Common situations: Archiving older postings whose trackers shut down long ago; pages referencing internal-only analytics collectors; corporate DNS wildcards resolving external names to internal IPs.

Related errors


AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20). Data as JSON: /api/errors/48709bbe0da5b1e1. Report an issue: GitHub.