
foxcage — Actualizado!
Ejecuta Firefox en un contenedor Podman sin root, con capacidades retiradas, red aislada y almacenamiento efímero para contener fugas del sandbox y evitar el compromiso del host.
foxcage
Ejecuta Firefox en un contenedor Podman sin root para aislamiento de seguridad. Tu navegador se ejecuta con casi ninguna capacidad de Linux, en su propio espacio de nombres de usuario y red, aislado del host — mientras conserva aceleración GPU completa, audio y soporte DRM.
¿Por qué foxcage?
Firefox ya tiene un sandbox multiproceso que aísla los renderizadores de contenido web mediante namespaces de Linux y seccomp-bpf. Para la mayoría de las amenazas, esto es efectivo. foxcage añade una segunda barrera: si un atacante explota una vulnerabilidad que escapa del sandbox de Firefox (lo cual ocurre — existen CVEs para esto), termina dentro de un contenedor endurecido en lugar de en tu sesión de usuario completa.
Contra qué protege foxcage
- Acceso a archivos tras la explotación. Una fuga del sandbox en Firefox sin aislar da acceso a todo lo que tu usuario puede leer:
~/.ssh,~/.gnupg, perfiles de navegador de otros navegadores, bases de datos de gestores de contraseñas, documentos, código fuente. En foxcage, el atacante solo ve lo que has montado explícitamente. - Residuos de rastreo en disco. La jaula efímera
@tmpno deja rastro en disco tras cerrar la ventana, incluidos los complementos, el estado HSTS, la caché de sesiones TLS y la caché DNS que la Navegación Privada de Firefox todavía persiste. Varias jaulas@tmpse ejecutan simultáneamente sin interferir entre sí. - Persistencia. En Firefox sin aislar, el malware puede escribir en
~/.config/autostart,~/.bashrc, cron o cualquier otro lugar para sobrevivir a un reinicio. El contenedor efímero de foxcage (--rm) implica que nada persiste a menos que lo hayas montado con bind. - Movimiento lateral en la red. Por defecto, el contenedor no puede sondear servicios en
localhost. En Firefox sin aislar, una fuga del sandbox tiene acceso completo a la red. (Usa[network] mode = "host"si una jaula necesita acceso a localhost, p. ej. para desarrollo local, pero consulta la advertencia en "Networking": el modo host también expone los sockets Unix abstractos del host.) - Escalada de privilegios. El contenedor elimina todas las capacidades de Linux excepto
CAP_SYS_CHROOTy bloquea la adquisición de nuevos privilegios. Los binarios setuid, las explotaciones del kernel mediante syscalls poco conocidas y rutas de escalada similares quedan cortadas.
Contra qué no protege foxcage
- Ataques a nivel del navegador. El phishing, las extensiones maliciosas y cualquier cosa que opere dentro de la funcionalidad normal de Firefox no se ven afectados — foxcage aísla el contenedor del host, no al usuario del navegador.
- Directorios montados con bind. Todo lo que montes (
profile,downloads_dir, montajes bind adicionales) es totalmente accesible para un navegador comprometido. Si montas un directorio de perfil del host, un atacante puede manipularlo igual que en Firefox sin aislar. - Captura de audio mediante PulseAudio. El socket de PulseAudio está montado con bind en el contenedor. Aunque está montado como solo lectura a nivel del sistema de archivos, los sockets de dominio Unix son bidireccionales — un proceso comprometido aún puede enviar solicitudes de grabación a través del socket. Una fuga del sandbox del navegador podría potencialmente grabar audio del micrófono del host.
- Explotaciones del compositor Wayland. El socket de Wayland se pasa al contenedor. Los compositores Wayland aíslan a los clientes entre sí por diseño, pero una vulnerabilidad en el propio compositor sería alcanzable.
Configuración de seguridad
El contenedor se ejecuta con:
- Todas las capacidades de Linux eliminadas (solo se añade de nuevo
CAP_SYS_CHROOTpara el sandbox de contenido de Firefox;CAP_SETUID/CAP_SETGIDse añaden temporalmente cuando se configurainit.root) no-new-privilegespara prevenir la escalada de privilegios- Espacio de nombres de usuario sin root (
--userns keep-id) /dev/shmprivado (no compartido con el host) — tamaño configurable medianteshm_size- Red aislada mediante pasta con el loopback del host bloqueado por defecto
- El DNS usa el DNS del host por defecto (configurable mediante
network.dns) - Solo se montan con bind sockets específicos de
XDG_RUNTIME_DIR(Wayland, PulseAudio, PipeWire y el proxy D-Bus filtrado) — el directorio de runtime completo del host nunca se expone - El acceso al bus de sesión D-Bus del host siempre está mediado por un
xdg-dbus-proxyfiltrado que se ejecuta en el host. Soloorg.freedesktop.Notifications,org.freedesktop.portal.Desktop,org.mozilla.*y (para forks) el propio espacio de nombres del fork (p. ej.org.librewolf.*) son accesibles — los servicios de sesión como el llavero y el agente SSH/GPG están bloqueados - El acceso al portal es amplio. Se permite
org.freedesktop.portal.Desktopen su totalidad, porque así funcionan el selector de archivos, "abrir enlace en otra aplicación" y el uso compartido de pantalla. También exponeRemoteDesktop(teclado/ratón sintéticos para toda la sesión),CamerayLocation. Están controlados por los diálogos de aprobación de tu propio escritorio, no por foxcage — y el aviso deRemoteDesktopse parece al aviso de compartir pantalla, así que lee los diálogos de aprobación antes de aceptarlos.xdg-dbus-proxyno tiene una regla de "denegar una interfaz", por lo que restringir esto implica enumerar todas las interfaces que Firefox necesita; consultadocs/DESIGN.mdpara saber por qué no se hace por defecto - Todos los montajes bind (
profile,downloads_dir,[mounts] bindadicionales) usannosuid,noexec - La descarga del navegador se verifica contra firmas GPG: Firefox contra las sumas SHA-512 firmadas por Mozilla, LibreWolf contra la firma separada de los Mantenedores de LibreWolf más la SHA-256 del archivo adyacente
- Contenedor efímero (
--rm) — las escrituras en el sistema de archivos se pierden al salir - No se pasan dispositivos del host (cámara web, llaves de seguridad, impresoras) a menos que se habiliten explícitamente
Cada opción de [network] y [mounts] que habilites intercambia algo de aislamiento por conveniencia. Los valores predeterminados son la configuración más restrictiva que aún te ofrece un navegador utilizable.
Requisitos
- Python 3.11+
- Podman (sin root)
- Compositor Wayland (X11 no es compatible)
- pasta (
sudo apt install passt) — a menos quenetwork.mode = "host" - xdg-dbus-proxy (
sudo apt install xdg-dbus-proxy) - PulseAudio o PipeWire con compatibilidad con PulseAudio (para audio)
- GPU con soporte DRI — opcional; sin
/dev/dri, foxcage avisa y Firefox renderiza por software
Ejecuta foxcage como tu usuario normal de escritorio, no como root ni mediante sudo — el sandbox mapea tu usuario dentro del contenedor, y ejecutarlo como root elimina el aislamiento que foxcage existe para proporcionar. Se niega a iniciarse como root.
Entorno probado: Debian 13 (Trixie) con GNOME 3. Otras distribuciones de Linux y compositores Wayland pueden funcionar, pero no se han probado.
Instalación
foxcage es un único script de Python sin dependencias fuera de la biblioteca estándar de Python. Cópialo a un directorio en tu PATH:```sh
sudo cp foxcage /usr/local/bin/foxcage
O para una instalación local de usuario:```sh
cp foxcage ~/.local/bin/foxcage
Asegúrate de que el script sea ejecutable (chmod +x foxcage).
Comprueba qué revisión tienes con foxcage --version — útil al informar de un problema, ya que foxcage se instala copiando un único archivo.
Uso```sh
./foxcage
En la primera ejecución, el script crea la imagen del contenedor (descarga Firefox de Mozilla, instala dependencias mínimas de Debian) y luego inicia Firefox. En ejecuciones posteriores, foxcage comprueba si hay actualizaciones de Firefox y reconstruye la imagen automáticamente cuando hay una nueva versión disponible. La imagen también se reconstruye periódicamente (cada 7 días por defecto) para incorporar actualizaciones de paquetes del sistema. Si la comprobación de actualización falla (error de red, tiempo de espera), se registra una advertencia y se utiliza la imagen existente: el inicio nunca se bloquea.
Pasa argumentos a Firefox:```sh
./foxcage https://example.com
Combina una jaula con nombre con las banderas de Firefox:```sh ./foxcage @work --kiosk https://example.com
Si una cage ya está en ejecución, la URL se abre en una nueva pestaña del navegador existente en lugar de iniciar un segundo contenedor. Ejecutar `foxcage` (o `foxcage @cage`) sin URL contra una cage en ejecución termina limpiamente con un mensaje de "cage is already running" — foxcage no puede traer al frente una ventana Wayland existente desde fuera del contenedor, así que no lo intenta.
Las opciones por lanzamiento **no** se aplican cuando una cage ya está en ejecución. `--dns`, `--ipv4-only`, `--lifetime`, `--color` y `--fork` se consumen al arrancar el contenedor, y los ajustes de un contenedor en ejecución no pueden cambiarse desde fuera, por lo que se ignoran con una advertencia. Cierra la cage y vuelve a ejecutarla para aplicarlos.
> Usa la clave de configuración `private_browsing` para sesiones de modo privado — *no* la opción CLI `--private-window` de Firefox. La clave de configuración activa el modo privado para toda la sesión (`browser.privatebrowsing.autostart`), de modo que las invocaciones posteriores de `foxcage @cage URL` pueden reabrirse en pestañas. `--private-window` como pase directo a Firefox haría que solo la primera ventana fuese privada y rompería el comportamiento de reapertura en pestañas descrito arriba.
>
> **Aviso:** las sesiones habilitadas mediante `private_browsing = true` no muestran las señales visuales habituales de ventana privada de Firefox (barra de acento púrpura, icono de máscara, "(Private Browsing)" en el título). Esto se debe a que cada ventana de la sesión es privada, por lo que Firefox no tiene ninguna ventana no privada con la que contrastar visualmente — suprime el indicador. La sesión *es* genuinamente privada; verifícalo si quieres visitando `about:privatebrowsing` dentro de la cage (muestra la página de información estándar de Navegación Privada) o `about:config` y comprobando que `browser.privatebrowsing.autostart = true`.
### Navegación efímera con `@tmp`
Para enlaces puntuales que no deban dejar rastro, usa la cage reservada `tmp`:```sh
./foxcage @tmp https://somewhere-suspicious.example
Cada lanzamiento @tmp es un Firefox nuevo y desechable sin perfil persistente. Cuando la ventana se cierra, todo desaparece: cookies, caché, historial, extensiones, estado HSTS, caché de sesión TLS, caché DNS, estado de pestañas guardado. Esto va más allá de la Navegación Privada de Firefox, que todavía conserva extensiones y una buena cantidad de estado en disco.
Varias jaulas @tmp se ejecutan simultáneamente, cada una aislada de las demás. La barra de menú muestra FoxCage - tmp (<short id>) para que puedas distinguir ventanas efímeras concurrentes.
Las jaulas efímeras abren una página en blanco al inicio y pestañas nuevas en blanco — la página de inicio predeterminada de Firefox y el contenido de pestaña nueva (sitios principales, recomendaciones de Pocket, flujo de actividad) son puro ruido en un perfil nuevo que está a punto de desecharse, por lo que se suprimen. Las jaulas persistentes mantienen los valores predeterminados de Firefox.
Jaulas efímeras con nombre
Si quieres un nombre significativo en una sesión desechable (por ejemplo, un agujero de conejo de investigación que querrás reabrir en una pestaña nueva), usa @tmp-<name>:```sh
./foxcage @tmp-research https://example.com # first call → new window
./foxcage @tmp-research https://another.example # second call → new tab in the existing window
`@tmp-<name>` sigue siendo efímero — cuando cierras la ventana, todo desaparece. La diferencia con `@tmp` a secas es que los lanzamientos posteriores con el mismo nombre **reutilizan la ventana existente** (igual que las jaulas persistentes), por lo que puedes añadir más pestañas más tarde sin iniciar una copia paralela. El `@tmp` a secas conserva su comportamiento de "cada lanzamiento es una nueva jaula desechable".
La etiqueta de la barra de menú muestra el nombre que elegiste (`FoxCage - tmp-research`) para que la ventana tenga una etiqueta significativa.
#### Personalización de los valores predeterminados efímeros
Crea `~/.config/foxcage/tmp.toml` para establecer los valores predeterminados de todas las jaulas efímeras (tanto el `@tmp` a secas como cada `@tmp-<name>`). Por ejemplo:```toml
private_browsing = true
lifetime = "30m"
[network]
dns = "cloudflare"
Cada lanzamiento efímero ahora obtiene una ventana privada, DoH de Cloudflare, y se cierra automáticamente después de 30 minutos — con la efimeridad completa intacta. Los efímeros con nombre heredan tmp.toml de forma predeterminada; si quieres anularlo por nombre, crea ~/.config/foxcage/tmp-<name>.toml. Ese archivo se aplica entonces en lugar de tmp.toml — sin fusión, el archivo más específico gana directamente. Copia los valores predeterminados compartidos en él si los quieres.
Todo lo que puedes establecer en la configuración de una jaula normal funciona aquí, excepto la clave que derrotaría a la propia efimeridad:
profile— error grave.
Apunta a un directorio de perfil persistente en el host, lo que contradice directamente el propósito de @tmp. Si quieres una jaula aislada con un perfil persistente, usa una jaula con nombre regular (@work, @research, etc.) que no empiece por tmp-.
Anulación de DNS por lanzamiento
La opción --dns (y la clave de configuración equivalente network.dns) acepta tres formas:```sh
./foxcage @tmp --dns 1.1.1.1 https://example.com # IP
./foxcage @tmp --dns cloudflare https://example.com # alias
./foxcage @tmp --dns https://dns.nextdns.io/ # custom DoH URI
**Cuando el valor coincide con un proveedor conocido (por alias o por IP), foxcage habilita automáticamente el DNS sobre HTTPS forzado hacia ese proveedor.** El TRR de Firefox se ajusta al modo 3 (estricto, sin respaldo en texto claro) con la dirección de bootstrap completada, de modo que no haya ninguna fuga de resolución sin cifrar al inicio. Verás un aviso de una línea en stderr como `Enabling DNS over HTTPS via Cloudflare`.
Alias integrados:
| Alias | IP | Filtrado |
|-------|------|-----------|
| `cloudflare` | 1.1.1.1 | ninguno |
| `cloudflare-security` | 1.1.1.2 | bloquea malware |
| `cloudflare-family` | 1.1.1.3 | bloquea malware + adulto |
| `google` | 8.8.8.8 | ninguno |
| `quad9` | 9.9.9.9 | bloquea malware (por defecto de Quad9) |
| `quad9-unfiltered` | 9.9.9.10 | ninguno |
| `adguard` | 94.140.14.14 | bloquea anuncios + rastreadores |
| `adguard-family` | 94.140.14.15 | anuncios + rastreadores + adulto |
| `opendns` | 208.67.222.222 | parcial |
Una IP que no esté en la tabla (p. ej., el Pi-hole de tu LAN) permanece solo en texto claro — no se habilita DoH, ya que foxcage no conoce el endpoint DoH correspondiente. Usa la forma URI para eso: `--dns https://pi.hole/dns-query` (con un certificado válido) habilita DoH y deja la DNS del contenedor intacta.
La forma URI omite configurar la DNS en texto claro del contenedor, así que cualquier cosa dentro del contenedor que no sea Firefox seguirá usando la DNS del host. Esto es deliberado — `--dns URI` significa "haz que Firefox use este resolutor DoH", y punto.
`--dns` es incompatible con `network.mode = "host"`, que ya tiene acceso completo a la red del host.
### Identificación visual de jaulas
Cada jaula con nombre recibe un color de acento en la barra de menú para que puedas distinguir las ventanas de un vistazo. **No necesitas configurar nada** — el color se deriva de forma determinista del nombre de la jaula (hash SHA256 convertido en un tono, con saturación y luminosidad fijas). `@banking`, `@work`, `@personal`, `@tmp-research` reciben todos colores distintos y estables sin que muevas un dedo.
La jaula predeterminada (anónima) conserva el naranja incorporado.
Si quieres anular el color derivado automáticamente, establécelo explícitamente:```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626" # red — overrides the auto-derived colour
Uso de Docker
También proporcionamos una configuración containerizada en docker-compose.yml.
Puede iniciar el entorno con
docker compose up -d
Ahora puede acceder tanto a la interfaz de usuario (http://localhost:10100) como a la API (http://localhost:10071).
Hay un script auxiliar en smuggle-scanner.sh que puede usarse como punto de entrada para el escaneo por línea de comandos.```sh
./foxcage @experiment --color "#10b981" https://example.com # teal, one-off
Acepta CSS hex estándar: `#rgb`, `#rrggbb` o `#rrggbbaa` (con alfa). Los colores derivados automáticamente están ajustados para ser visibles tanto en barras de menú claras como oscuras (luminosidad fijada al 55%, saturación al 75%), por lo que no deberías necesitar sobreescribirlos por razones de tema.
### Cages con límite de tiempo
La opción `--lifetime` (y la clave de configuración equivalente `lifetime`) cierra automáticamente una cage después de una duración determinada. El formato es `<number><unit>` con unidad `s`, `m` u `h`:```sh
./foxcage @tmp --lifetime 10m https://example.com
./foxcage @work --lifetime 2h
La cuenta atrás comienza cuando Firefox se lanza realmente dentro de la jaula — el tiempo de arranque del contenedor y de construcción de la imagen no consumen tu presupuesto. La etiqueta de la barra de menú de la jaula muestra la cuenta atrás junto con la identidad de la jaula — p. ej. FoxCage - tmp (a3f2b1) | 9m — actualizándose una vez por minuto mientras quede más de un minuto, y una vez por segundo en el último minuto. Cuando la cuenta atrás llega a cero, Firefox se cierra por sí mismo y el contenedor sale. Si cierras Firefox tú mismo antes de que se cumpla el tiempo de vida, no ocurre nada inusual.
Establece un tiempo de vida predeterminado por jaula en su configuración:```toml
~/.config/foxcage/tmp.toml — every @tmp launch auto-closes after 15 minutes
lifetime = "15m" private_browsing = true
`--lifetime` en la línea de comandos prevalece sobre cualquier valor de configuración.
Fuerza una reconstrucción completa de la imagen (vuelve a descargar Firefox y todos los paquetes del sistema):```sh
./foxcage --rebuild
Un contenedor en ejecución conserva la imagen desde la que se inició, incluso después de que foxcage reconstruya la etiqueta de la imagen. Si intentas abrir una pestaña en una cage cuya imagen se ha actualizado desde entonces (mediante --rebuild, una actualización de Firefox o la reconstrucción programada), foxcage se niega con un error (también mostrado como notificación de escritorio) y te pide que cierres Firefox y lo vuelvas a abrir, lo que inicia un contenedor nuevo con la imagen actual. Con --rebuild y una cage activa, foxcage avisa de antemano, hace la compilación y luego aplica la misma comprobación.
Actualizaciones
foxcage comprueba si hay nuevas versiones del navegador en cada inicio: la API de versiones de Mozilla para Firefox, el endpoint de releases de GitLab para LibreWolf. Si hay una actualización disponible, la imagen del contenedor se reconstruye automáticamente. La imagen también se reconstruye periódicamente (cada 7 días por defecto) para incluir las actualizaciones de seguridad de Debian. El actualizador automático integrado del navegador está desactivado, ya que las actualizaciones se gestionan a nivel de imagen.
Si la comprobación de actualizaciones falla (sin red, tiempo de espera de la API), se imprime una advertencia y se usa la imagen existente — siempre puedes navegar.
La cadencia de actualización se define en el nivel superior de la configuración; la fijación de versión y canal se define en la sección por fork:```toml rebuild_days = 14 # rebuild for base-image updates every 14 days (0 to disable)
[firefox] channel = "beta" # track the beta channel instead of stable (firefox only) version = "149" # pin to Firefox 149.x (latest patch release)
**Fijar una versión ESR también requiere el canal.** El índice de versiones de Mozilla lista las versiones ESR sin el sufijo `esr` que llevan sus descargas, por lo que un simple `version = "140"` en el canal predeterminado se resuelve a una versión que no existe. Establece ambos:```toml
[firefox]
channel = "esr"
version = "140" # → 140.13.0esr
Un pin que no coincide con ninguna versión ahora es un error que nombra el pin, en lugar de recurrir silenciosamente a la última versión. Una falla temporal al alcanzar la API de Mozilla aún advierte y continúa con la imagen existente, por lo que una red inestable nunca bloquea el inicio.
Los pines con sufijo deben estar completamente especificados — "140.13.0esr" y "150.0b9" funcionan, "140esr" y "150b9" se rechazan al cargar la configuración porque ninguna versión puede coincidir con ellos. Lo mismo aplica para las revisiones de LibreWolf: "146.0.1-1" funciona, "146-1" no.
Para forzar una reconstrucción completa inmediata: ./foxcage --rebuild
Forks de Firefox (LibreWolf)
foxcage puede ejecutar un fork de Firefox orientado a la privacidad en lugar del Firefox original:```toml fork = "librewolf" # default is "firefox"
[librewolf] version = "146.0.1-1" # optional pin; partial pins ("146", "146.0.1") also work
O por lanzamiento mediante CLI:```sh
foxcage @tmp --fork librewolf https://example.com
LibreWolf: bifurcación de Firefox endurecida para la privacidad: protección estricta contra el rastreo, DoH, RFP, telemetría desactivada por defecto. Tarball Linux firmado desde GitLab (librewolf-community/browser/bsys6), verificado con GPG contra la clave de los Mantenedores de LibreWolf 662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16 con una verificación cruzada del .sha256sum asociado. El librewolf.cfg incluido de LibreWolf se conserva; foxcage añade sus propias preferencias encima en lugar de sobrescribirlo.
El canal es solo para Firefox: firefox.channel = "beta" | "esr" se rechaza cuando fork es cualquier valor distinto de "firefox". LibreWolf tiene una única vía de lanzamiento.
Cambiar fork (mediante config o --fork) cambia el hash del Containerfile, lo que desencadena una reconstrucción en el próximo lanzamiento — no se necesita --rebuild manual.
Compatibilidad de perfiles
Usa un perfil dedicado por fork. La opción más segura por defecto es dejar que foxcage provea su propio perfil (omite
profileen la configuración), o apuntaprofilea un directorio que tampoco abras desde el host.
- LibreWolf: normalmente es seguro compartirlo con tu perfil de Firefox del host — LibreWolf sigue las versiones de Firefox en pocos días, por lo que los conflictos de esquema de
compatibility.inison raros. Riesgos: (1) solo es seguro el uso secuencial (el archivo de bloqueo de Firefox impide aperturas concurrentes); (2) en la breve ventana posterior a una versión estable de Firefox, ejecutar Firefox primero y luego LibreWolf puede provocar un diálogo de migración de "usado por una versión más reciente"; (3) las funciones que LibreWolf elimina (Sync, Pocket, cuenta de Mozilla) simplemente no funcionan, pero no corrompen datos.
Jaulas con nombre
Ejecuta instancias de sandbox separadas con su propia configuración y perfil de Firefox:```sh ./foxcage @work
Esto carga `~/.config/foxcage/work.toml` y usa una imagen separada (`foxcage-work`), un contenedor (`foxcage-work`) y un volumen (`foxcage-work-profile`). El archivo de configuración debe existir para las jaulas con nombre. Los nombres de las jaulas solo pueden contener letras, dígitos, guiones y guiones bajos.
## Configuración
Los archivos de configuración se encuentran en `$XDG_CONFIG_HOME/foxcage/` (por defecto en `~/.config/foxcage/`).
- `config.toml` — jaula predeterminada (opcional, con valores predeterminados sensatos sin él)
- `<name>.toml` — jaula con nombre, se carga con `@<name>` (obligatorio)
Las claves de configuración desconocidas se rechazan con un error. Consulta `config.toml.example` para ver todas las opciones disponibles con sus valores predeterminados.
### Ejemplo de config.toml```toml
# Bind-mount a host Firefox profile directory into the cage
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"
# Allow downloading files to ~/Downloads
downloads_dir = "~/Downloads"
# Shared memory size for Firefox IPC (default: 256m)
# shm_size = "256m"
# Pass through webcam devices (/dev/video*)
# webcam = true
# Pass through host CUPS socket for locally-connected printers (e.g. USB)
# local_printers = true
# Pass through FIDO2/U2F security key devices (/dev/hidraw*)
# security_keys = true
# Always open Firefox in private browsing mode
# private_browsing = true
# Auto-close the cage after a duration (<int> with unit s, m, or h)
# lifetime = "30m"
# Accent colour for the menu-bar label. Named cages get a colour derived
# from the name automatically; set this to override it.
# color = "#4a90e2"
# Browser fork: "firefox" (default) or "librewolf"
# fork = "librewolf"
# Full image rebuild interval in days for base-image updates (default: 7, 0 to disable)
# rebuild_days = 7
[firefox]
# Firefox release channel: "release" (default), "beta", "esr".
# Only valid when fork = "firefox".
# channel = "release"
# Pin to a specific Firefox version (overrides channel).
# Partial versions like "149" or "149.0" resolve to the latest patch release.
# Suffixed versions must be fully qualified ("140.13.0esr", "150.0b9"); to
# follow the ESR line by major version, pair a numeric pin with
# channel = "esr" above.
# version = "149.0.2"
[librewolf]
# Pin to a specific LibreWolf version. Tags are "<firefox-version>-<rev>",
# e.g. "146.0.1-1". Partial pins like "146" or "146.0.1" also work.
# version = "146.0.1-1"
[network]
# "host" for full host networking (needed if the cage has to reach services
# on the host's localhost), or omit for isolated pasta (default)
# mode = "host"
# DNS server (isolated mode only, default: host DNS)
# dns = "1.1.1.1"
# Disable IPv6 in the cage (isolated mode only)
# ipv4_only = true
[mounts]
# Additional bind mounts into the container. Supported forms:
# "~/Documents" — same path in container
# "~/Documents:~/Documents" — ~ expanded on both sides
# "~/Documents:/home/user/Documents" — explicit container path
# Append :ro for read-only, e.g. "~/Documents:ro"
# nosuid,noexec are always enforced on bind mounts; an explicit "exec" or
# "suid" is rejected rather than silently dropped.
# Host paths must be absolute or start with "~/".
bind = [
"~/Documents:ro",
]
[init]
# Commands to run at image build time (as root). Changes trigger a rebuild.
# build = ["apt-get update && apt-get install -y --no-install-recommends vim"]
# Commands to run at container startup as root, before Firefox.
# root = ["chown user:user /some/path"]
# Commands to run at container startup as your user, before Firefox.
# user = ["mkdir -p ~/custom-dir"]
Perfil de Firefox del host
Para compartir un perfil de Firefox del host con la jaula, establece profile al directorio del perfil. Encuentra la ruta de tu perfil visitando about:profiles en Firefox en el host — o simplemente apunta a un directorio vacío nuevo si quieres que la jaula comience con un perfil limpio que persista en el host.```toml
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"
Solo este directorio está montado por bind dentro de la jaula. Los perfiles hermanos bajo `~/.mozilla/firefox/` y el registro `profiles.ini` no están expuestos — una jaula comprometida no puede manipularlos.
Si `profile` no está definido, un volumen de Podman con nombre almacena el perfil de Firefox en su lugar (consulta "Lo que persiste" más abajo). Si el mismo perfil ya está abierto en Firefox en el host, el archivo de bloqueo por perfil de Firefox provocará un conflicto — usa un perfil dedicado por jaula.
### Redes
Por defecto, el contenedor usa pasta con el loopback del host bloqueado y el DNS del host. pasta requiere podman 4.4 o más reciente (ha sido la opción predeterminada en modo rootless desde podman 5.0).
**Red de host** elimina por completo el aislamiento de red. Usa esto cuando la jaula necesite acceder a servicios en el `localhost` del host (p. ej., un servidor de desarrollo local, una base de datos en `127.0.0.1`):```toml
[network]
mode = "host"
dns no se puede combinar con mode = "host" — la red del host ya utiliza el resolver del host.
El modo host sacrifica más que localhost. Pone la jaula en el namespace de red del host, y los sockets Unix abstractos están limitados a ese namespace en lugar de al sistema de archivos. Por lo tanto, una jaula en modo host puede alcanzar directamente los sockets de dirección abstracta en el host, incluyendo el
@/tmp/.X11-unix/X0de Xwayland si ejecutas X11 o Xwayland (registro de entrada, a pesar de que foxcage es solo Wayland), y un bus de sesión configurado conunix:abstract=…, lo que eludiría el proxy de D-Bus filtrado. Esto es inherente a compartir la pila de red, no algo que foxcage pueda filtrar. Usa el modo host cuando lo necesites, y prefiere una jaula con nombre que solo lances para ese propósito.
Las jaulas solo IPv4 deshabilitan IPv6 por completo:```toml [network] ipv4_only = true
O por lanzamiento con la bandera `--ipv4-only` (forma corta `-4`, como en `ssh`/`curl`/pasta):```sh
./foxcage @tmp -4 https://example.com
Esto ejecuta pasta en modo solo IPv4 (-4), por lo que el contenedor no tiene ninguna pila IPv6, y además establece network.dns.disableIPv6 en Firefox para que no resuelva registros AAAA — lo cual importa cuando DoH está habilitado, ya que las respuestas de DoH omiten el resolvedor del contenedor. ipv4_only no se puede combinar con mode = "host" — la red de host utiliza directamente la pila de red del host, así que desactiva IPv6 en el host en su lugar.
Comandos init
Ejecuta comandos personalizados en el momento de la compilación o al iniciar el contenedor mediante [init]:
build— se ejecuta en el momento de compilar la imagen como root. Úsalo para instalar paquetes u otra configuración lenta. Los cambios en los comandos de compilación activan automáticamente una reconstrucción de la imagen.root— se ejecuta al iniciar el contenedor como root, antes de Firefox. Úsalo para tareas rápidas de root en tiempo de ejecución (ajustar permisos, escribir archivos de configuración).user— se ejecuta al iniciar el contenedor como tu usuario, antes de Firefox. Úsalo para crear directorios y configurar el estado a nivel de usuario.```toml [init] build = [ "apt-get update && apt-get install -y --no-install-recommends fonts-noto-cjk", "rm -rf /var/lib/apt/lists/*", ] root = ["chmod 777 /tmp/shared"] user = ["mkdir -p ~/workspace"]
Las tres claves son listas de cadenas de comandos de shell. Si falla algún comando, el contenedor se cierra sin iniciar Firefox.
**Nota de seguridad:** Cuando `init.root` está establecido, el contenedor arranca como root con `CAP_SETUID` y `CAP_SETGID` añadidas (junto con la `CAP_SYS_CHROOT` predeterminada) para poder volver al usuario regular. Estas capacidades solo se mantienen durante la fase de init como root — después de la caída de privilegios, el proceso del usuario regular no tiene capacidades adicionales. Sin `init.root`, el contenedor se ejecuta con el conjunto de capacidades mínimas predeterminado.
## Qué persiste
Sin configuración, un volumen Podman con nombre almacena el perfil de Firefox (marcadores, ajustes, extensiones, complemento Widevine DRM). Todo lo demás es efímero.
- Jaula predeterminada: `foxcage-profile`
- Jaula con nombre: `foxcage-<name>-profile`
Para empezar de cero, elimine el volumen:```sh
podman volume rm foxcage-profile
Si profile está establecido, el directorio del host se monta mediante bind y no se crea ningún volumen.
Uso de disco
Cada imagen de cage ocupa alrededor de 1 GB. Una reconstrucción reetiqueta la imagen y deja la anterior como una entrada <none> sin etiquetar, por lo que foxcage elimina la imagen que acaba de desplazar después de cada compilación exitosa. Solo elimina esa imagen específica, y nunca una que un cage en ejecución esté utilizando todavía.
Las imágenes huérfanas antes de que este comportamiento existiera no se limpian retroactivamente. Para recuperarlas:```sh podman images --filter dangling=true # review first podman image prune # then remove
Las actualizaciones de Firefox se detectan automáticamente en cada inicio. Para forzar una reconstrucción completa (p. ej., para aplicar inmediatamente las actualizaciones de seguridad del sistema):```sh
./foxcage --rebuild
Temas
foxcage pasa automáticamente lo siguiente desde el host, para que Firefox en el contenedor se vea y se sienta como una aplicación nativa:
- Fuentes. Las fuentes del sistema (
/usr/share/fonts) y del usuario (~/.local/share/fonts) se montan mediante bind-mount en modo solo lectura. La configuración de fuentes de~/.config/fontconfigtambién se transfiere. - Tema GTK y modo oscuro. Se detectan mediante
GTK_THEMEogsettingsy se pasan al contenedor. La configuración de GTK de~/.config/gtk-3.0y~/.config/gtk-4.0se monta mediante bind-mount en modo solo lectura. - Zona horaria. El nombre de la zona horaria del host (detectado desde
TZ, el enlace simbólico/etc/localtimeo/etc/timezone) se pasa al contenedor comoTZ, y/etc/localtimese monta mediante bind-mount en modo solo lectura. Ambos son necesarios: Firefox obtiene la zona horaria de JavaScript a partir del nombre de la zona, no del contenido del archivo — sinTZ, los sitios web mostrarían las horas en UTC. - Configuración regional.
LANGse transfiere. La configuración regional del host se genera en la imagen del contenedor en el momento de la compilación.
Etiqueta de la jaula. La barra de menús de Firefox muestra "FoxCage" (o "FoxCage - nombre" para jaulas con nombre) para que puedas saber de un vistazo que estás en una sesión contenerizada. La barra de menús siempre está visible mediante la política empresarial.
El contenedor solo incluye el tema GTK Adwaita. En escritorios GNOME esto funciona sin configuración adicional. En KDE u otros escritorios, Firefox recurrirá a Adwaita si tu tema GTK (p. ej. Breeze) no está instalado en el contenedor. La detección del modo oscuro sigue funcionando siempre que la preferencia esté establecida mediante gsettings o GTK_THEME.
DRM (Netflix, Disney+, etc.)
El DRM de Widevine funciona sin configuración adicional. En la primera visita a un sitio protegido con DRM, Firefox descargará automáticamente el CDM de Widevine. Esto puede llevar un momento.
Integración con el host (siempre activa)
foxcage utiliza un proxy D-Bus filtrado para dar a Firefox acceso al XDG Desktop Portal del host y al demonio de notificaciones. Estas funciones son seguras porque todo acceso está mediado por el usuario: el host muestra diálogos nativos con los que debes interactuar. Un navegador comprometido no puede acceder silenciosamente a los recursos del host.
- Subida de archivos — selector de archivos nativo del host (tú eliges qué archivos compartir)
- Enlaces externos —
mailto:, enlaces magnet, etc. se abren mediante el selector de aplicaciones del host - Notificaciones de escritorio — reenviadas al demonio de notificaciones del host
- Compartición de pantalla — selector de pantalla del portal + flujo de vídeo PipeWire (requiere PipeWire en el host)
Paso de dispositivos (opt-in)
Estas opciones pasan dispositivos del host directamente al contenedor y están desactivadas por defecto — a diferencia de las funciones del portal antes mencionadas, no hay confirmación en el lado del host. Un navegador comprometido podría usar el hardware en silencio.```toml webcam = true # /dev/video* — webcam for video calls local_printers = true # CUPS socket — USB printers (network printers work by default) security_keys = true # /dev/hidraw* — FIDO2/U2F hardware keys
## Aún no soportado
Algunas funciones de la plataforma web no funcionan en el contenedor debido a la falta de integración con el host. Estas se enumeran aquí para mayor transparencia.
**Bluetooth, USB, serie y NFC.** Las API Web Bluetooth, WebUSB, Web Serial y WebNFC requieren acceso a dispositivos y servicios del sistema (BlueZ, udev) que no están disponibles en el contenedor.
**Gamepads y MIDI.** La API Gamepad necesita acceso a `/dev/input/`. Web MIDI necesita acceso al secuenciador ALSA. Ninguno se pasa a través.
**Instalación de PWA.** Las aplicaciones web progresivas (PWA) no se pueden instalar en el escritorio del host desde el interior del contenedor.
**Accesibilidad.** El soporte de lector de pantalla mediante AT-SPI está deshabilitado (`NO_AT_BRIDGE=1`) — el contenedor no tiene conexión con el bus de accesibilidad del host. La síntesis de la Web Speech API sí funciona: `speech-dispatcher` con el motor `espeak-ng` está instalado en el contenedor y se inicia automáticamente en el primer uso, con el audio enrutado a través del socket compartido de PulseAudio.
## Configuración del host
### Recomendado: almacenamiento overlay con fuse-overlayfs
Podman sin root puede tener como predeterminado el controlador de almacenamiento `vfs`, que copia capas de imagen completas en lugar de usar montajes overlay. Esto hace que el arranque del contenedor después de una compilación sea mucho más lento. Para solucionarlo, instala `fuse-overlayfs` y añade lo siguiente a `~/.config/containers/storage.conf`:```toml
[storage]
driver = "overlay"
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
Configurar foxcage como tu navegador predeterminado
Primero, asegúrate de que el script foxcage esté en su ubicación permanente (p. ej. ~/bin/foxcage o /usr/local/bin/foxcage). El comando de instalación registra la ruta actual del script en el archivo .desktop, así que moverlo después romperá el lanzador.
Luego ejecuta:```sh foxcage --install
Esto crea un archivo `.desktop` que apunta a la ubicación actual del script, instala el icono de foxcage y actualiza las bases de datos del escritorio y de iconos. FoxCage debería aparecer entonces en el menú de aplicaciones.
Para establecer foxcage como el navegador web predeterminado, de modo que los enlaces en los que se hace clic en otras aplicaciones se abran en foxcage:```sh
xdg-settings set default-web-browser foxcage.desktop
Si una jaula ya se está ejecutando, las URLs se abren como una nueva pestaña en el navegador existente.
Para deshacer:```sh foxcage --uninstall
`StartupNotify=true` se establece en el archivo `.desktop`, lo que le indica al compositor que muestre un cursor giratorio mientras foxcage se inicia. Cuando se necesita construir una imagen (lo que puede tardar varios minutos), foxcage envía una notificación de escritorio para que sepas que Firefox está en camino. Cualquier error de salida anticipada (error tipográfico en la configuración, dependencia faltante, nombre de cage mal formado) también se muestra como notificación de escritorio, para que los usuarios que inician desde el escritorio no se queden mirando nada cuando foxcage falla sin una terminal adjunta. Ambos requieren `notify-send` (de `libnotify-bin` en Debian/Ubuntu) — si no está instalado, las notificaciones se omiten silenciosamente y el error sigue yendo a stderr.
<details>
<summary>Configuración manual</summary>
Si prefieres crear el archivo `.desktop` manualmente, crea `~/.local/share/applications/foxcage.desktop`:```ini
[Desktop Entry]
Type=Application
Name=FoxCage
Comment=Firefox in a rootless Podman container
Exec=/path/to/foxcage %u
Icon=foxcage
MimeType=text/html;x-scheme-handler/http;x-scheme-handler/https;
Terminal=false
Categories=Network;WebBrowser;
StartupNotify=true
StartupWMClass=foxcage
Reemplaza /path/to/foxcage con la ruta real al script. Regístralo:```sh
update-desktop-database ~/.local/share/applications
</details>
## Ejecutando pruebas
La suite de pruebas utiliza pytest + pytest-cov, declaradas como dependencias solo de desarrollo en `requirements-dev.txt`.```
pip install -r requirements-dev.txt
pytest
Las pruebas son totalmente herméticas: sin podman, sin red, sin sistema de archivos real más allá de tmp_path de pytest. La suite exige un 100 % de cobertura de líneas y ramas (configurado en pytest.ini y .coveragerc); cualquier línea sin cubrir, o rama no tomada de un condicional, hace fallar la ejecución. La CI ejecuta la suite en cada push mediante .gitlab-ci.yml.
Agradecimientos
Este proyecto fue desarrollado por Mike Cardwell, con la asistencia de Claude Code, la herramienta de codificación con IA de Anthropic.