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
- No action — subresource blocks do not affect the archived capture.
- If the archive fails outright, check whether the posting URL itself (the main frame) was blocked — that URL must be public.
- 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
- Only archive public http(s) posting URLs
- Expect these lines on older postings with dead trackers — they are guard telemetry, not capture failures
- Reuse installEgressGuard rather than writing a second guard, so blocks stay consistent across tools
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
- refusing to archive restricted destination: ${preGuard.reaso
- refusing to archive restricted destination after redirect: $
- Invalid or blocked URL after redirect: ${finalRejected.reaso
- Access denied: Egress guard blocked private target IP ${ip}
- Access denied: Localhost or internal domain target detected.
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/48709bbe0da5b1e1.
Report an issue: GitHub.