Obscura Request Interception — Block APIs, Mock Responses, Inject Scripts

Three request interception approaches: CDP Fetch.enable, Rust Library Interception API, built-in Stealth Tracker Blocklist. Block ads, mock APIs, inject preload scripts.

16Yun Engineering TeamJul 7, 20264 min read

Control Every Request

Pages make many network requests — HTML, CSS, JS, images, analytics, API calls. For scraping and automation, you rarely need all of them. Obscura provides three levels of request interception:

  1. CDP Fetch.enable — intercept from Puppeteer/Playwright
  2. Rust Library Interception API — intercept from Rust code
  3. Built-in Stealth Tracker Blocklist — automatic net-layer blocking

Choosing the right level is mostly about where your code already lives: if you drive Obscura from a Node script, CDP interception drops straight in; if you embed the engine in Rust, the library API gives you the same power without a WebSocket round-trip; and if you just want trackers gone with zero code, Stealth handles it.

Level 1: CDP Fetch.enable (Puppeteer/Playwright)

Block by Resource Type

// Puppeteer
await page.setRequestInterception(true);
page.on('request', req => {
  if (['image', 'media', 'font'].includes(req.resourceType())) {
    req.abort();
  } else {
    req.continue();
  }
});
// Playwright
await page.route('**/*', route => {
  if (['image', 'media', 'font'].includes(route.request().resourceType())) {
    route.abort();
  } else {
    route.continue();
  }
});

Blocking images and fonts is one of the highest-leverage optimizations for text-oriented scraping: it cuts page weight and load time dramatically, and for a scraper that only reads the DOM text, those resources are pure waste.

Block by URL Pattern

// Puppeteer
page.on('request', req => {
  if (req.url().includes('google-analytics.com')) {
    req.abort();
  } else {
    req.continue();
  }
});
// Playwright: regex support
await page.route(/google-analytics\.com/, route => route.abort());

Beyond ad blocking, URL-pattern interception is how you neutralize sites that fingerprint based on which third-party scripts load. A page that expects recaptcha__en.js to arrive and finds it blocked may take a different code path — useful when you are mapping a site's anti-bot behavior.

Mock API Responses

Mocking is how you bypass front-end gating or speed up tests against a known response shape:

// Puppeteer
page.on('request', req => {
  if (req.url().endsWith('/api/feature-flags')) {
    req.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ newDashboard: true }),
    });
  } else {
    req.continue();
  }
});
// Playwright
await page.route('**/api/feature-flags', route => {
  route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ newDashboard: true }),
  });
});

Modify Request Headers

// Puppeteer
page.on('request', req => {
  req.continue({
    headers: { ...req.headers(), 'X-Custom-Header': 'value' },
  });
});

Header injection is often used to add an Authorization token captured from a prior login, or to set an Accept-Language that matches the locale your Stealth profile claims.

Level 2: Stealth Built-in Blocklist

Zero-config tracker blocking with Stealth mode:

obscura fetch https://example.com --stealth

Peter Lowe's blocklist — 3500+ domains blocked at the network layer, before the request ever reaches V8 or the DOM. Supports exact and subdomain wildcard matching.

The advantage over per-script CDP blocking is twofold: it needs no code in your client, and it runs in the engine's net layer so blocked requests never consume a CDP round-trip or a JS execution slot. For most scraping work, turning on Stealth is enough on its own.

Level 3: Rust Library Interception API

When you embed the engine with the obscura crate, you get the same interception capability as a native Rust API, without any WebSocket serialization.

enable_interception Channel

enable_interception() returns a receiver channel for every JS fetch()/XHR request. Resolve each through its resolver:

use obscura::{Browser, InterceptResolution};
 
let mut page = browser.new_page().await?;
let mut rx = page.enable_interception();
 
tokio::spawn(async move {
    while let Some(req) = rx.recv().await {
        let action = if req.url.contains("/ads") {
            InterceptResolution::Fail { reason: "blocked".into() }
        } else if req.url.ends_with("/api/flags") {
            InterceptResolution::Fulfill {
                status: 200,
                headers: Default::default(),
                body: r#"{"newDashboard":true}"#.into(),
            }
        } else {
            InterceptResolution::Continue {
                url: None, method: None, headers: None, body: None,
            }
        };
        let _ = req.resolver.send(action);
    }
});

Three resolutions:

ResolutionMeaning
ContinuePass through, optionally rewrite url/method/headers/body
FulfillReturn a fake response
FailBlock with an error reason

A Continue with url: Some(...) rewrites the target. The new URL is re-checked against the SSRF gate, so a rewrite cannot reach an internal address that would otherwise require --allow-private-network.

Preload Script

Run code before the page's own <script> tags (the CDP Page.addScriptToEvaluateOnNewDocument contract):

page.add_preload_script("window.__patched = true;");
page.goto("https://example.com").await?;

Use it to override global functions, plant monitoring hooks, pin Math.random or Date.now to deterministic values, or mark the page so your own logic can detect it.

Passive Callbacks

When you only need to observe, not block, use passive callbacks:

page.on_request(Arc::new(|info| {
    println!("Request: {} {}", info.method, info.url);
}));
 
page.on_response(Arc::new(|info, resp| {
    if info.resource_type == ResourceType::Fetch {
        println!("API: {} -> {} bytes", info.url, resp.body.len());
    }
}));

on_response is the main path for capturing the JSON an SPA loads asynchronously — often more reliable than parsing the rendered DOM, because you get the raw API payload directly.

Comparison

LevelLayerUse caseCode needed
CDP Fetch.enableCDP protocolExisting Puppeteer/PlaywrightJavaScript
Stealth BlocklistNetworkZero-configNone
Rust Library APIRust crateDeep integrationRust

The three layers compose: Stealth Blocklist mutes generic trackers, CDP interception applies your business rules, and the Rust API handles logic that lives inside your own service.

Security

URL rewrites in Continue re-validate against the SSRF gate, so interception cannot be used to bypass --allow-private-network. This is intentional — interception is powerful, but it does not get to punch a hole in the engine's network safety boundary.

Summary

Obscura provides complete request control from the network layer through CDP to the Rust library. In everyday use, Stealth mode's built-in blocklist handles most tracker blocking automatically; when you need fine-grained control, CDP interception and the Rust library API give you full flexibility at whichever layer your code already lives.

Need an enterprise proxy plan?

We can tailor architecture to your target domains, concurrency, and reliability goals.