# How Ladybird Browser Uses pledge and unveil for Sandboxing Security

> Discover how Ladybird Browser employs pledge and unveil for robust sandboxing security, isolating processes and restricting system calls for enhanced protection.

- Repository: [Ladybird/ladybird](https://github.com/LadybirdBrowser/ladybird)
- Tags: internals
- Published: 2026-03-05

---

**Ladybird Browser isolates rendering and system components in separate processes, applying OpenBSD-style `pledge` and `unveil` restrictions to limit system calls and filesystem access, with only the UI process running unsandboxed.**

Ladybird Browser implements a defense-in-depth security model by combining multi-process architecture with kernel-level sandboxing primitives. According to the `LadybirdBrowser/ladybird` source code, every non-UI component—from the WebContent renderer to the Network process—runs under strict `pledge` and `unveil` constraints that minimize the attack surface available to malicious web content.

## Ladybird Browser Sandbox Architecture Overview

### Multi-Process Design with Privilege Separation

Ladybird decomposes the browser into specialized processes, each with a minimal privilege set. The architecture separates the **Browser process** (UI and window management) from **WebContent** (HTML/CSS/JS rendering), **Network** (HTTP requests), and **ImageDecoder** (media processing). This isolation ensures that a vulnerability in the JavaScript engine cannot directly access the network or filesystem.

### The Role of pledge and unveil in OpenBSD Sandboxing

`pledge` is a system call that restricts the types of operations a process may perform, while `unveil` limits filesystem visibility to specific paths. Ladybird leverages these OpenBSD-native mechanisms (and equivalent implementations on other platforms) to create a two-layer sandbox: first restricting *what* a process can do, then restricting *where* it can access data.

## How WebContent Process Implements pledge and unveil

The WebContent process—responsible for rendering web pages and executing JavaScript—applies the most restrictive sandboxing constraints immediately upon startup.

### Restricting System Calls with pledge()

In [`WebContent/Process.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/WebContent/Process.cpp), the process invokes `pledge()` early in initialization to whitelist only essential system calls. The typical pledge string for the renderer includes:

```cpp
pledge("stdio rpath wpath cpath inet unix fattr flock proc exec prot execve", nullptr);

```

This permits standard I/O, read/write path operations, network sockets (`inet`), Unix domain sockets (`unix`), and process management (`proc`), while explicitly denying access to hardware devices, raw networking, and privileged operations.

### Limiting Filesystem Access with unveil()

After restricting system calls, WebContent uses `unveil()` to expose only specific directories required for operation. As implemented in the source:

```cpp
// Expose SSL certificate store for HTTPS validation
unveil("/etc/ssl/certs", "r");

// Expose user cache directory for temporary storage
unveil(cache_directory_path, "rwc");

// Lock the filesystem view - no further unveil calls permitted
unveil(nullptr, nullptr);

```

The permissions string uses OpenBSD conventions: `"r"` for read, `"w"` for write, `"c"` for create, and `"x"` for execute.

### Locking the Sandbox

The final `unveil(nullptr, nullptr)` call is critical—it permanently locks the filesystem view, preventing any subsequent code (including potentially exploited JavaScript) from expanding the sandbox boundaries. This call appears in all Ladybird subprocess implementations.

## Sandboxing Strategy for Auxiliary Processes

Ladybird applies similarly restrictive sandboxes to helper processes, tailored to their specific functions.

### Network Process Constraints

The Network process, handling HTTP/HTTPS requests, operates with a reduced syscall surface. In [`Network/Process.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Network/Process.cpp):

```cpp
// More restrictive than WebContent - no execve, limited proc
pledge("stdio rpath inet unix dns", nullptr);

// Only unveil the SSL cert directory and socket paths
unveil("/etc/ssl/certs", "r");
unveil("/tmp/ladybird_sockets", "rw");
unveil(nullptr, nullptr);

```

Note the absence of `execve` and `prot` promises, preventing the Network process from executing external programs or modifying memory protections.

### ImageDecoder and Other Services

The ImageDecoder process uses an even tighter sandbox since it only needs to read image data and write decoded bitmaps:

```cpp
pledge("stdio rpath wpath", nullptr);
unveil(image_cache_path, "rw");
unveil(nullptr, nullptr);

```

This minimal surface ensures that a malicious image file cannot exploit the decoder to access the network or filesystem beyond the cache directory.

## Privilege Dropping and User Isolation

Before applying `pledge` and `unveil` restrictions, all non-UI processes drop root privileges and switch to a dedicated unprivileged user. In [`Launcher.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Launcher.cpp), the process initialization follows this sequence:

```cpp
// 1. Drop privileges
Core::System::setuid(ladybird_uid);
Core::System::setgid(ladybird_gid);

// 2. Apply syscall restrictions
pledge(pledge_promises, nullptr);

// 3. Limit filesystem visibility
unveil(required_paths...);
unveil(nullptr, nullptr);  // Lock

// 4. Enter main event loop

```

This three-layer defense—user isolation, syscall filtering, and filesystem virtualization—ensures that even if an attacker bypasses one mechanism, the process remains confined by the others.

## Key Source Files and Implementation Details

| File | Purpose | Sandbox Implementation |
|------|---------|----------------------|
| [`WebContent/Process.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/WebContent/Process.cpp) | Renderer process | `pledge()` with `stdio rpath exec prot...`, `unveil()` for SSL certs and cache |
| [`Network/Process.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Network/Process.cpp) | HTTP/HTTPS handling | Restricted `pledge()` without `execve`, `unveil()` for sockets and DNS |
| [`ImageDecoder/Process.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/ImageDecoder/Process.cpp) | Image decoding | Minimal `pledge("stdio rpath wpath")`, cache-only `unveil()` |
| [`Launcher.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Launcher.cpp) | Process spawning | Privilege dropping via `setuid`/`setgid` before sandbox activation |
| [`Documentation/ProcessArchitecture.md`](https://github.com/LadybirdBrowser/ladybird/blob/main/Documentation/ProcessArchitecture.md) | Architecture overview | Describes the multi-process sandbox model and `pledge`/`unveil` strategy |

These files collectively implement Ladybird's security boundary, with each process type applying the minimal set of promises and filesystem visibility required for its specific role.

## Summary

- **Multi-process isolation**: Ladybird separates the UI, rendering, networking, and media decoding into distinct processes to contain potential exploits.
- **OpenBSD sandboxing**: Non-UI processes use `pledge()` to whitelist permitted system calls and `unveil()` to restrict filesystem access to specific paths.
- **Privilege dropping**: All sandboxed processes switch to an unprivileged "ladybird" user before applying kernel restrictions, creating a defense-in-depth architecture.
- **Locking mechanism**: Each process calls `unveil(nullptr, nullptr)` to permanently freeze its filesystem view, preventing runtime sandbox expansion.

## Frequently Asked Questions

### What is the difference between pledge and unveil in Ladybird Browser?

`pledge` restricts which system calls a process can invoke, effectively defining what operations the process is allowed to perform (such as file I/O, network access, or process creation). `unveil` restricts which parts of the filesystem the process can see and access, regardless of Unix permissions. Ladybird uses both together: `pledge` limits the attack surface at the kernel interface, while `unveil` prevents access to sensitive files even if the process is compromised.

### Why doesn't the Browser process use pledge and unveil?

The Browser process manages the UI, window system integration, and user interactions, requiring broad access to desktop resources, configuration files, and the display server. Restricting it with `pledge` and `unveil` would prevent necessary operations like accessing user downloads, managing window decorations, or integrating with the native file picker. Instead, Ladybird relies on the operating system's standard user privilege model for the Browser process while sandboxing all content-handling subprocesses.

### How does Ladybird prevent sandbox escape even if pledge is bypassed?

Ladybird implements a layered defense strategy. First, sandboxed processes drop root privileges and switch to a dedicated unprivileged "ladybird" user before calling `pledge` or `unveil`, ensuring they lack OS-level rights even if kernel restrictions fail. Second, the `unveil` mechanism locks the filesystem view with `unveil(nullptr, nullptr)`, preventing runtime expansion of accessible paths. Finally, the multi-process architecture ensures that compromising one sandboxed component (like the renderer) does not automatically grant access to network or filesystem resources handled by other processes.

### Which Ladybird processes use the most restrictive sandbox?

The **ImageDecoder** process uses the most restrictive sandbox, employing a minimal `pledge` set of `"stdio rpath wpath"` that permits only basic I/O and file reading/writing, with no network, process execution, or memory protection privileges. Its `unveil` access is limited exclusively to the image cache directory, preventing access to SSL certificates, configuration files, or user documents. This extreme restriction is possible because image decoding requires no external resources beyond reading input bytes and writing decoded bitmaps to the cache.