# How Lightpanda Implements Request Interception with Authentication Handling

> Discover how Lightpanda handles authentication with request interception. Learn how it pauses HTTP requests, dispatches events, and enables remote clients to manage credentials via CDP Fetch.

- Repository: [Lightpanda/browser](https://github.com/lightpanda-io/browser)
- Tags: deep-dive
- Published: 2026-03-14

---

**Lightpanda implements request interception with authentication handling by injecting a pause mechanism into its HTTP client that dispatches events through a notification system to the CDP Fetch domain, allowing remote clients to modify, fulfill, abort, or provide credentials for requests via standard Chrome DevTools Protocol commands.**

The Lightpanda browser engine provides a comprehensive request interception system that operates at the CDP (Chrome DevTools Protocol) level. This architecture enables fine-grained control over network traffic, including the ability to handle HTTP authentication challenges programmatically. Understanding how Lightpanda implements request interception with authentication handling requires examining the interaction between its low-level network stack, event notification system, and CDP domain implementations.

## Core Architecture of Request Interception and Authentication

### HttpClient: The Network Entry Point

In `src/browser/HttpClient.zig`, the `processRequest` function serves as the interception gateway. When creating a new **Transfer** for an outgoing request, the client immediately fires two notification events:

```zig
transfer.req.notification.dispatch(.http_request_start, &.{ .transfer = transfer });
var wait_for_interception = false;
transfer.req.notification.dispatch(.http_request_intercept, &.{ .transfer = transfer,
                                                            .wait_for_interception = &wait_for_interception });

```

The **wait_for_interception** boolean flag determines whether the request proceeds normally or enters a paused state. If no interceptor is registered, the flag remains `false` and the request continues via `self.process(transfer)`. When interception is active, the flag sets to `true`, causing Lightpanda to increment the `self.intercepted` counter, mark the transfer with `._intercept_state = .pending`, and either return early for non-blocking requests or block until the client calls `continueTransfer` or `abortTransfer`.

For authentication challenges, the same pattern applies using a distinct notification type dispatched in `src/browser/HttpClient.zig` (lines 809-818):

```zig
transfer.req.notification.dispatch(.http_request_auth_required,
    &.{ .transfer = transfer, .wait_for_interception = &wait_for_interception });

```

This design allows the network layer to remain agnostic about interception logic while providing clear extension points for authentication handling.

### Notification System: Event Dispatch

The **Notification** type in `src/Notification.zig` implements a lightweight publish/subscribe pattern that decouples the network stack from CDP handling. Listeners register for specific event types using the `notification.register` method:

```zig
try self.notification.register(.http_request_intercept, self, onHttpRequestIntercept);

```

When an event dispatches, the system calls every registered listener with the appropriate payload, transporting **Transfer** references from the network layer to the CDP domain without direct coupling.

### CDP Fetch Domain: Protocol Bridge

The `src/cdp/domains/fetch.zig` file contains the CDP Fetch domain implementation that translates internal network events into standard CDP protocol events. When `Fetch.enable` is called with `handleAuthRequests: true`, the domain registers listeners for both request interception and authentication challenges:

```zig
pub fn fetchEnable(self: *Self, authRequests: bool) !void {
    try self.notification.register(.http_request_intercept, self, onHttpRequestIntercept);
    if (authRequests) {
        try self.notification.register(.http_request_auth_required, self, onHttpRequestAuthRequired);
    }
}

```

This registration occurs in `src/cdp/cdp.zig` (lines 560-665), establishing the connection between network events and protocol responses.

## Request Lifecycle and CDP Control Commands

When the network stack signals an interception, the CDP domain creates a paused event via `onHttpRequestIntercept`. The handler generates a **Fetch.requestPaused** event containing the request ID, frame ID, resource type, and network ID:

```zig
try bc.cdp.sendEvent("Fetch.requestPaused", .{
    .requestId   = &id.toInterceptId(transfer.id),
    .frameId     = &id.toFrameId(transfer.req.frame_id),
    .request     = network.TransferAsRequestWriter.init(transfer),
    .resourceType = switch (transfer.req.resource_type) { … },
    .networkId    = &id.toRequestId(transfer.id),
}, .{ .session_id = session_id });

```

The request enters a per-session **InterceptState** map that correlates request IDs to **Transfer** instances. The client can then issue specific CDP commands to control the paused request:

- **`Fetch.continueRequest`**: Handler `continueRequest` (lines 226-269) updates URL, method, headers, or body before calling `http_client.continueTransfer`.
- **`Fetch.failRequest`**: Handler calls `http_client.abortTransfer` to abort the request.
- **`Fetch.fulfillRequest`**: Handler `fulfillRequest` sends a custom response directly to the client without hitting the network.

The `continueRequest` handler extracts the stored transfer from the **InterceptState** map and applies modifications before resuming:

```zig
const transfer = intercept_state.remove(request_id) orelse return error.RequestNotFound;
…
try bc.cdp.browser.http_client.continueTransfer(transfer);

```

## Handling HTTP Authentication Challenges

When a server issues an HTTP authentication challenge, the network layer dispatches `.http_request_auth_required` via the same notification mechanism. The CDP domain responds by sending a **Fetch.authRequired** event containing challenge details including the scheme (basic or digest), realm, and source (server or proxy):

```zig
try bc.cdp.sendEvent("Fetch.authRequired", .{
    .requestId = &id.toInterceptId(transfer.id),
    .frameId   = &id.toFrameId(transfer.req.frame_id),
    .request   = network.TransferAsRequestWriter.init(transfer),
    .authChallenge = .{
        .origin = "",
        .source = if (challenge.source) |s| (if (s == .server) "Server" else "Proxy") else "",
        .scheme = if (challenge.scheme) |s| (if (s == .digest) "digest" else "basic") else "",
        .realm  = challenge.realm orelse "",
    },
    .networkId = &id.toRequestId(transfer.id),
}, .{ .session_id = session_id });

```

The client must respond with `Fetch.continueWithAuth`. The handler in `src/cdp/domains/fetch.zig` (lines 124-169) processes this response by either canceling the challenge or providing credentials:

```zig
if (params.authChallengeResponse.response != .ProvideCredentials) {
    transfer.abortAuthChallenge();
    return cmd.sendResult(null, .{});
}
…
transfer.updateCredentials(…);
transfer.reset();
try bc.cdp.browser.http_client.continueTransfer(transfer);

```

For custom authentication schemes, Lightpanda provides `WebBotAuth.signRequest` in `src/network/WebBotAuth.zig` (lines 90-154), which adds Ed25519 signature headers (`Signature-Agent`, `Signature-Input`, `Signature`) to outgoing requests before they reach the network layer.

## Practical Implementation Example

The following Zig example demonstrates enabling request interception with authentication handling, modifying requests with custom headers, and responding to authentication challenges:

```zig
const std = @import("std");
const cdp = @import("cdp");

// Enable fetch with auth handling
pub fn enableFetch(session: *cdp.Session) !void {
    try session.fetchEnable(true); // true enables auth challenge listening
}

// Handle paused requests
pub fn onRequestPaused(params: cdp.Fetch.RequestPaused) !void {
    // Example: Add WebBotAuth signature headers
    const auth = try cdp.WebBotAuth.fromConfig(std.heap.page_allocator, &myConfig);
    defer auth.deinit(std.heap.page_allocator);
    try auth.signRequest(std.heap.page_allocator, &params.request.headers, params.request.url);
    
    // Continue with modified headers
    try session.continueRequest(.{
        .requestId = params.requestId,
        .headers = params.request.headers,
    });
}

// Handle authentication challenges
pub fn onAuthRequired(params: cdp.Fetch.AuthRequired) !void {
    try session.continueWithAuth(.{
        .requestId = params.requestId,
        .authChallengeResponse = .{
            .response = .ProvideCredentials,
            .username = "user",
            .password = "pass",
        },
    });
}

// Setup registration
pub fn setup(session: *cdp.Session) !void {
    try enableFetch(session);
    try session.notification.register(.http_request_intercept, session, onRequestPaused);
    try session.notification.register(.http_request_auth_required, session, onAuthRequired);
}

```

This implementation enables full programmatic control over network traffic, from request modification to automated credential provision.

## Summary

- **Lightpanda intercepts requests at the HTTP client level** by dispatching notification events from `src/browser/HttpClient.zig` that pause transfers via the `wait_for_interception` flag.
- **A pub/sub notification system** in `src/Notification.zig` decouples network events from CDP handling, allowing multiple listeners to react to interception and authentication events.
- **The CDP Fetch domain** in `src/cdp/domains/fetch.zig` translates internal events into standard `Fetch.requestPaused` and `Fetch.authRequired` protocol events.
- **Clients control paused requests** through CDP commands like `continueRequest`, `continueWithAuth`, and `fulfillRequest`, which resume or abort transfers stored in the per-session **InterceptState** map.
- **Authentication challenges** follow the same interception pattern but use distinct notification types and handlers that support both standard HTTP auth and custom **WebBotAuth** Ed25519 signatures.

## Frequently Asked Questions

### How does Lightpanda decide whether to pause a request for interception?

Lightpanda checks the `wait_for_interception` boolean flag after dispatching the `.http_request_intercept` notification in `src/browser/HttpClient.zig` (lines 95-110). If a registered CDP listener sets this flag to `true`, the request enters a `pending` intercept state and blocks until the client issues a continuation command. If no listener modifies the flag, the request proceeds immediately to the network layer.

### What CDP commands does Lightpanda support for controlling intercepted requests?

According to `src/cdp/domains/fetch.zig`, Lightpanda implements `Fetch.continueRequest` for modifying request parameters, `Fetch.continueWithAuth` for providing authentication credentials, `Fetch.fulfillRequest` for returning mock responses, and `Fetch.failRequest` for aborting requests. Each command retrieves the stored **Transfer** from the **InterceptState** hash map and either resumes or terminates the network operation.

### Can Lightpanda handle both Basic and Digest authentication schemes?

Yes. The `Fetch.authRequired` event in `src/cdp/domains/fetch.zig` (lines 85-124) includes an `authChallenge` object that specifies the scheme as either "basic" or "digest", along with the realm and source (server or proxy). The client responds via `continueWithAuth` with the appropriate credentials, which the handler applies through `transfer.updateCredentials()` before resetting and continuing the transfer.

### Where does custom request signing fit into the interception flow?

The `WebBotAuth.signRequest` function in `src/network/WebBotAuth.zig` operates before the standard interception flow, adding Ed25519 signature headers to requests during the modification phase of `continueRequest`. This allows clients to implement custom authentication schemes alongside or in place of standard HTTP authentication, with signatures applied programmatically before the request resumes through `http_client.continueTransfer`.