
foxcage — Updated!
Run Firefox in a rootless Podman container with dropped capabilities, isolated networking, and ephemeral storage to contain sandbox escapes and prevent host compromise.
foxcage
Run Firefox in a rootless Podman container for security isolation. Your browser runs with almost no Linux capabilities, in its own user and network namespace, isolated from the host — while still having full GPU acceleration, audio, and DRM support.
Why foxcage?
Firefox already has a multi-process sandbox that isolates web content renderers using Linux namespaces and seccomp-bpf. For most threats, this is effective. foxcage adds a second wall: if an attacker exploits a vulnerability that escapes Firefox's sandbox (which happens — there are CVEs for this), they land inside a locked-down container instead of your full user session.
What foxcage protects against
- Post-exploit file access. A sandbox escape on bare Firefox gives access to everything your user can read:
~/.ssh,~/.gnupg, browser profiles for other browsers, password manager databases, documents, source code. In foxcage, the attacker sees only what you've explicitly mounted in. - On-disk tracking residue. The
@tmpephemeral cage leaves zero trace on disk after the window closes — including extensions, HSTS state, TLS session cache, and DNS cache that Firefox's Private Browsing still persists. Multiple@tmpcages run concurrently without interfering with each other. - Persistence. On bare Firefox, malware can write to
~/.config/autostart,~/.bashrc, cron, or anywhere else to survive a reboot. foxcage's ephemeral container (--rm) means nothing persists unless you've bind-mounted it. - Lateral network movement. By default, the container can't probe services on
localhost. On bare Firefox, a sandbox escape has full network access. (Use[network] mode = "host"if a cage needs localhost access, e.g. for local development — but see the caveat under "Networking": host mode also exposes the host's abstract Unix sockets.) - Privilege escalation. The container drops all Linux capabilities except
CAP_SYS_CHROOTand blocks new privilege acquisition. Setuid binaries, kernel exploits via obscure syscalls, and similar escalation paths are cut off.
What foxcage does not protect against
- Browser-level attacks. Phishing, malicious extensions, and anything that operates within Firefox's normal functionality is unaffected — foxcage isolates the container from the host, not the user from the browser.
- Bind-mounted directories. Anything you mount in (
profile,downloads_dir, extra bind mounts) is fully accessible to a compromised browser. If you mount a host profile directory, an attacker can tamper with it just as on bare Firefox. - Audio capture via PulseAudio. The PulseAudio socket is bind-mounted into the container. Although it is mounted read-only at the filesystem level, Unix domain sockets are bidirectional — a compromised process can still send recording requests through the socket. A browser sandbox escape could potentially record audio from the host microphone.
- Wayland compositor exploits. The Wayland socket is passed through. Wayland compositors isolate clients from each other by design, but a vulnerability in the compositor itself would be reachable.
Security configuration
The container runs with:
- All Linux capabilities dropped (only
CAP_SYS_CHROOTadded back for Firefox's content sandbox;CAP_SETUID/CAP_SETGIDadded temporarily wheninit.rootis configured) no-new-privilegesto prevent privilege escalation- Rootless user namespace (
--userns keep-id) - Private
/dev/shm(not shared with host) — configurable size viashm_size - Isolated networking via pasta with host loopback blocked by default
- DNS uses host DNS by default (configurable via
network.dns) - Only specific sockets from
XDG_RUNTIME_DIRare bind-mounted in (Wayland, PulseAudio, PipeWire, and the filtered D-Bus proxy) — the full host runtime directory is never exposed - Access to the host's D-Bus session bus is always mediated by a filtered
xdg-dbus-proxyrunning on the host. Onlyorg.freedesktop.Notifications,org.freedesktop.portal.Desktop,org.mozilla.*, and (for forks) the fork's own namespace (e.g.org.librewolf.*) are reachable — session services like the keyring and the SSH/GPG agent are blocked - Portal access is broad.
org.freedesktop.portal.Desktopis allowed as a whole, because that is how the file picker, "open link in another app", and screen sharing work. It also exposesRemoteDesktop(synthetic keyboard/mouse for the whole session),CameraandLocation. Those are gated by your desktop's own approval dialogs rather than by foxcage — and theRemoteDesktopprompt resembles the screen-share prompt, so read approval dialogs before accepting them.xdg-dbus-proxyhas no "deny one interface" rule, so narrowing this means enumerating every interface Firefox needs; seedocs/DESIGN.mdfor why that is not done by default - All bind mounts (
profile,downloads_dir, extra[mounts] bind) usenosuid,noexec - Browser download verified against GPG signatures: Firefox against Mozilla's signed SHA-512 checksums, LibreWolf against the LibreWolf Maintainers detached signature plus sibling SHA-256. Verification is stricter than
gpg --verify, which exits 0 for a signature made by a revoked key and for any key in the keyring. foxcage additionally requires that the signature chains to the pinned primary key, and refuses any release signed by a subkey its owner revoked as compromised — see Revoked signing keys - Ephemeral container (
--rm) — filesystem writes are lost on exit - No host devices (webcam, security keys, printers) passed through unless explicitly enabled
Each [network] and [mounts] option you enable trades some isolation for convenience. The defaults are the most restrictive configuration that still gives you a usable browser.
Requirements
- Python 3.11+
- Podman (rootless)
- Wayland compositor (X11 is not supported)
- pasta (
sudo apt install passt) — unlessnetwork.mode = "host" - xdg-dbus-proxy (
sudo apt install xdg-dbus-proxy) - PulseAudio or PipeWire with PulseAudio compatibility (for audio)
- GPU with DRI support — optional; without
/dev/drifoxcage warns and Firefox renders in software. VA-API drivers for Intel, AMD and nouveau are installed in the image, so hardware video decoding works without host driver packages — see Hardware video decoding
Run foxcage as your normal desktop user, not as root or via sudo — the sandbox maps your user into the container, and running as root removes the isolation foxcage exists to provide. It refuses to start as root.
Tested environment: Debian 13 (Trixie) with GNOME 3. Other Linux distributions and Wayland compositors may work but have not been tested.
Installation
foxcage is a single Python script with no dependencies outside the Python standard library. Copy it to a directory in your PATH:
sudo cp foxcage /usr/local/bin/foxcage
Or for a user-local install:
cp foxcage ~/.local/bin/foxcage
Make sure the script is executable (chmod +x foxcage).
Check which revision you have with foxcage --version — useful when reporting a problem, since foxcage is installed by copying a single file.
Usage
./foxcage
On first run the script builds the container image (downloads Firefox from Mozilla, installs minimal Debian dependencies) and then starts Firefox. On subsequent runs, foxcage checks for Firefox updates and rebuilds the image automatically when a new version is available. The image is also rebuilt periodically (every 7 days by default) to pick up system package updates. If the update check fails (network error, timeout), a warning is logged and the existing image is used — startup is never blocked.