santifer/career-ops · error
refusing to archive restricted destination after redirect: $
Error message
refusing to archive restricted destination after redirect: ${postGuard.reason} What it means
Thrown by archiveUrl() after page.goto() settles: it re-runs rejectPrivateOrInvalid() on page.url(), the URL actually landed on. This is defence-in-depth behind the per-request route guard (which aborts private redirect hops as 'blockedbyclient') — a public first-hop URL that ends up on a private/loopback/invalid destination is rejected before any content is saved.
Source
Thrown at archive-posting.mjs:327
if (preGuard) {
throw new Error(`refusing to archive restricted destination: ${preGuard.reason}`);
}
const context = await browser.newContext();
await installEgressGuard(context);
const page = await context.newPage();
try {
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
const httpStatus = response?.status() ?? 0;
// Re-check where we actually landed. The route guard already inspects every
// redirect hop, so this is defence-in-depth: a first-hop-only check is the
// classic miss here, and asserting on the settled URL costs nothing.
const landedUrl = page.url();
const postGuard = rejectPrivateOrInvalid(landedUrl);
if (postGuard) {
throw new Error(`refusing to archive restricted destination after redirect: ${postGuard.reason}`);
}
// Give SPAs (Ashby, Lever, Workday) time to hydrate
await page.waitForTimeout(2000);
const pageTitle = await page.title();
const h1Text = await page.$eval('h1', el => el.innerText.trim()).catch(() => '');
const urlCompany = extractCompanyFromUrl(url);
// Parse page title first — it usually has "Role | Company" or "Company | Role".
// Fall back to h1 for the role when the page title doesn't yield one cleanly.
const detected = parsePageTitle(pageTitle);
const resolvedCompany = overrideCompany || companyHint || detected.company || urlCompany || 'unknown';
const resolvedRole = overrideRole || roleHint || detected.role || h1Text || 'job';
// Strip noisy prefixes common on Greenhouse/Lever ("Job Application for …")
const company = resolvedCompany.replace(/^job\s+application\s+for\s+/i, '').trim();
const role = resolvedRole.replace(/^job\s+application\s+for\s+/i, '').trim();View on GitHub (pinned to 60398d6549)
Solutions
- Trace the chain yourself (curl -sIL <url>) and pass the final public URL directly to archive-posting
- If the final destination is legitimately internal, archive it manually into jds/ — the guard is intentional and not bypassable via this tool
- If a public posting unexpectedly redirects to a private host, treat the link as broken/untrusted, drop it from the pipeline, and note it in the tracker
Example fix
# before node archive-posting.mjs https://short.example.com/j/9317 # 302 -> http://10.2.3.4/ats/9317 # after curl -sIL https://short.example.com/j/9317 | grep -i '^location' node archive-posting.mjs https://ats.acme-public.com/careers/9317 # archive the real public destination
Defensive patterns
Strategy: try-catch
Try / catch
try {
await archiveUrl(browser, url);
} catch (e) {
if (/restricted destination after redirect/.test(e.message)) {
// redirect target unknowable beforehand: resolve the chain out-of-band,
// archive the final PUBLIC url, or fall back to a manual capture.
console.warn(`redirect guard: ${url} -> private destination; skipping`);
continue; // next queue entry
}
throw e;
} Prevention
- Pre-resolve shortlinks with curl -sIL before queueing so redirects to private space are filtered up front
- Watch for SSO-wrapped application links — they are the usual source of private redirect targets
- Log blocked-after-redirect URLs separately; they often indicate a dead or tampered posting worth dropping from the pipeline
When it happens
Trigger: A public job URL that 30x-redirects to an internal host (open redirect, vendor SSO bouncing to an intranet FQDN that matches a private pattern, or a misconfigured shortlink); any case where the settled URL differs from the requested one and lands on a blocked host.
Common situations: ATS links that bounce through an SSO domain resolving to private space; a posting service redirecting to an internal staging host; tampered or rotting shortlinks in an old pipeline.md queue.
Related errors
- refusing to archive restricted destination: ${preGuard.reaso
- Invalid or blocked URL: ${rejected.reason}
- Access denied: Egress guard blocked private target IP ${ip}
- plugin egress to ${hostname} is blocked (private/loopback/me
- plugin egress: ${hostname} resolves to a blocked address (${
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/88d131c2a38875db.
Report an issue: GitHub.