# How Lightpanda Handles Security Origins and Secure Contexts: Implementation Deep Dive

> Discover how Lightpanda implements security origins and secure contexts for robust browser security. Learn about origin tracking and enforcement via Chrome DevTools Protocol.

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

---

**Lightpanda tracks security origins and secure contexts through the `BrowserContext` struct in `src/cdp/cdp.zig`, extracting scheme-host-port tuples via `URL.getOrigin` in `src/browser/URL.zig` and exposing them via the Chrome DevTools Protocol to enforce same-origin policies.**

Lightpanda is a lightweight, headless browser engine written in Zig that implements the Chrome DevTools Protocol (CDP) for automation and debugging. Understanding how Lightpanda handles security origins and secure contexts is essential for developers working with cross-origin requests, iframe sandboxing, and secure context requirements for modern web APIs. This analysis examines the source code implementation in the `lightpanda-io/browser` repository.

## Core Concepts: Security Origins and Secure Contexts

Lightpanda maintains two distinct but related security properties for every browsing context within the `BrowserContext` struct defined in `src/cdp/cdp.zig`.

### Security Origin Storage and Initialization

The **security origin** represents the scheme-host-port tuple that defines the same-origin policy boundary for a page. In `src/cdp/cdp.zig` (lines 66-69), the `BrowserContext` stores this as the `security_origin` field.

When initializing a fresh browser context, Lightpanda sets the origin to a built-in URL constant `URL_BASE` (`chrome://newtab/`). However, when a target (frame) is created, the origin is overwritten to a minimal placeholder `"://"` (lines 417-419). This placeholder forces any subsequent navigation to compute a real origin from the actual page URL rather than inheriting the initial value.

### Secure Context Type Classification

The **secure context type** indicates whether the browsing context satisfies the requirements for secure context APIs (such as geolocation or encrypted media). Stored in the `secure_context_type` field of `BrowserContext`, this value is initialized to `"Secure"` for fresh contexts.

When a target is created and the origin downgraded to the placeholder `"://"`, the context type is simultaneously set to `"InsecureScheme"` because the placeholder does not correspond to a cryptographically secure scheme. This prevents secure APIs from being available until a valid HTTPS origin is established.

## Origin Extraction and Normalization

All origin computation funnels through the `getOrigin` helper function implemented in `src/browser/URL.zig` (lines 87-95 and 100-149).

```zig
pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 {
    const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null;
    const protocol = raw[0 .. scheme_end + 1];
    if (!std.mem.eql(u8, protocol, "http:") and !std.mem.eql(u8, protocol, "https:")) {
        return null;               // non‑HTTP schemes have no origin
    }
    // … strip user‑info, default ports, etc. …
    return raw[0..authority_end];
}

```

This implementation exhibits three critical behaviors:

- **Scheme restriction**: Only `http:` and `https:` URLs yield valid origins. The function returns **null** for schemes like `chrome://`, `about:`, or `blob:`—treating them as **opaque origins**.
- **Port normalization**: Default ports (`:80` for HTTP, `:443` for HTTPS) are stripped, ensuring `https://example.com:443` and `https://example.com` share the same origin.
- **User-info removal**: Authentication credentials embedded in URLs are excluded from the origin string.

## CDP Integration and Frame Tree Reporting

Lightpanda exposes security metadata to debugging tools through the Chrome DevTools Protocol. The `getFrameTree` function in `src/cdp/domains/page.zig` (lines 81-90) packages the stored context values into CDP responses:

```zig
fn getFrameTree(cmd: anytype) !void {
    const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
    const target_id = bc.target_id orelse return error.TargetNotLoaded;
    try cmd.sendResult(.{
        .frameTree = .{
            .frame = .{
                .id = &target_id,
                .securityOrigin = bc.security_origin,
                .secureContextType = bc.secure_context_type,
                // …
            },
        },
    }, .{});
}

```

The `securityOrigin` field contains the string stored in `bc.security_origin`, while `secureContextType` mirrors `bc.secure_context_type`. This allows DevTools clients to inspect the exact origin and security status of any page frame.

## Same-Origin Policy Enforcement

Network requests undergo origin verification in `src/browser/webapi/net/Fetch.zig` (lines 125-141) to determine if a response qualifies as **basic** (same-origin) or requires **cors** handling.

```zig
// src/browser/webapi/net/Fetch.zig
const page_origin = URL.getOrigin(arena, self._page.url) catch null;
const response_origin = URL.getOrigin(arena, res._url) catch null;

if (page_origin) |po| {
    if (response_origin) |ro| {
        if (std.mem.eql(u8, po, ro)) {
            res._type = .basic;   // Same‑origin
        } else {
            res._type = .cors;    // Cross‑origin (CORS assumed passed)
        }
    } else {
        res._type = .basic;
    }
} else {
    res._type = .basic;
}

```

The comparison performs simple byte-wise equality of the two origin strings. If either the page or the response lacks an origin (returning null), Lightpanda treats the request as same-origin. This matches the HTML specification’s handling of opaque origins in certain contexts.

## Secure Context API Access

APIs requiring secure contexts query the stored `secure_context_type` or derive origins directly from the page location. For example, `postMessage` implementations in `src/browser/webapi/Window.zig` (line 376) obtain the caller’s origin via:

```zig
const origin = try self._location.getOrigin(page);

```

The `Location.getOrigin` method ultimately delegates to `URL.getOrigin`, ensuring consistent validation rules across all security-critical code paths.

## Practical Implementation Examples

### Retrieving a Page Origin in Zig

To extract a normalized origin from any page URL, use the `URL.getOrigin` function directly:

```zig
const origin = try URL.getOrigin(allocator, page.url);
if (origin) |o| {
    std.debug.print("Page origin: {s}\n", .{o});
} else {
    std.debug.print("Page has no origin (opaque scheme)\n", .{});
}

```

This mirrors the internal logic used throughout the browser engine.

### Checking Same-Origin Status Programmatically

When implementing custom resource loading logic, replicate the Fetch module’s origin comparison:

```zig
fn isSameOrigin(page: *Page, responseUrl: []const u8) !bool {
    const pageOrigin = try URL.getOrigin(page.arena, page.url);
    const respOrigin = try URL.getOrigin(page.arena, responseUrl);
    if (pageOrigin) |po| {
        if (respOrigin) |ro| {
            return std.mem.eql(u8, po, ro);
        }
    }
    // If either side has no origin, treat as same-origin per Lightpanda policy
    return true;
}

```

This function returns `true` for opaque origins, matching Lightpanda’s network layer behavior.

### Inspecting Security Context via CDP

When connected to Lightpanda’s CDP server, the `Page.getFrameTree` command returns JSON structured as follows:

```json
{
  "frame": {
    "id": "…",
    "securityOrigin": "https://example.com",
    "secureContextType": "Secure"
  }
}

```

This output reflects the internal state maintained in `src/cdp/cdp.zig` and transmitted through `src/cdp/domains/page.zig`.

## Summary

- **Lightpanda stores security metadata** in the `BrowserContext` struct (`src/cdp/cdp.zig`), tracking both the origin string and secure context type for every browsing context.
- **Origin computation** is centralized in `URL.getOrigin` (`src/browser/URL.zig`), which normalizes HTTP and HTTPS URLs while treating all other schemes as opaque (null origins).
- **CDP exposure** allows debugging tools to inspect `securityOrigin` and `secureContextType` through the frame tree API implemented in `src/cdp/domains/page.zig`.
- **Same-origin enforcement** occurs in the network layer (`src/browser/webapi/net/Fetch.zig`) using byte-wise string comparison, with opaque origins defaulting to same-origin treatment.
- **Secure context validation** uses the stored type field to gate access to sensitive APIs, initialized as `"Secure"` but downgraded to `"InsecureScheme"` when placeholder origins are active.

## Frequently Asked Questions

### How does Lightpanda determine if a URL has a valid security origin?

Lightpanda uses the `URL.getOrigin` function in `src/browser/URL.zig` to parse URLs. It only returns a non-null origin for URLs using the `http:` or `https:` schemes. All other schemes—including `chrome://`, `about:`, and `blob:`—return null and are treated as opaque origins with no defined security boundary.

### What is the difference between security_origin and secure_context_type in Lightpanda?

The `security_origin` field stores the serialized scheme-host-port tuple used for same-origin comparisons, while `secure_context_type` is a descriptive string (`"Secure"` or `"InsecureScheme"`) indicating whether the context meets the requirements for secure context APIs. A page might have a valid origin but still be classified as insecure if loaded over HTTP rather than HTTPS.

### How does Lightpanda handle opaque origins like about:blank or chrome:// URLs?

Opaque origins return null from `URL.getOrigin` and are handled specially in same-origin checks. According to the implementation in `src/browser/webapi/net/Fetch.zig`, if either the requesting page or the response URL lacks an origin (returns null), Lightpanda treats the request as same-origin rather than cross-origin.

### Where does Lightpanda enforce same-origin checks for network requests?

Same-origin verification occurs in `src/browser/webapi/net/Fetch.zig` (lines 125-141) during the fetch response classification. The code compares the page’s origin against the response URL’s origin using `std.mem.eql`. If the byte strings match, the response type is set to `.basic`; otherwise, it is classified as `.cors` for cross-origin handling.