
foxcage — Mis à jour !
Exécutez Firefox dans un conteneur Podman rootless avec des capabilities supprimées, un réseau isolé et un stockage éphémère afin de contenir les évasions de sandbox et d'empêcher la compromission de l'hôte.
foxcage
Faire tourner Firefox dans un conteneur Podman sans root pour l'isolation de sécurité. Votre navigateur s'exécute avec presque aucune capacité Linux, dans son propre espace de noms utilisateur et réseau, isolé de l'hôte — tout en conservant l'accélération GPU complète, l'audio et la prise en charge des DRM.
Pourquoi foxcage ?
Firefox dispose déjà d'un sandbox multi-processus qui isole les processus de rendu de contenu web à l'aide des espaces de noms Linux et de seccomp-bpf. Pour la plupart des menaces, c'est efficace. foxcage ajoute une seconde muraille : si un attaquant exploite une vulnérabilité qui contourne le sandbox de Firefox (ce qui arrive — il existe des CVE pour cela), il atterrit dans un conteneur verrouillé au lieu de votre session utilisateur complète.
Ce que foxcage protège contre
- Accès aux fichiers après exploitation. Une évasion du sandbox sur Firefox nu donne accès à tout ce que votre utilisateur peut lire :
~/.ssh,~/.gnupg, les profils navigateur des autres navigateurs, les bases de données des gestionnaires de mots de passe, les documents, le code source. Dans foxcage, l'attaquant ne voit que ce que vous avez explicitement monté. - Résidus de suivi sur disque. La cage éphémère
@tmpne laisse aucune trace sur le disque après la fermeture de la fenêtre — y compris les extensions, l'état HSTS, le cache de session TLS et le cache DNS que la navigation privée de Firefox persiste malgré tout. Plusieurs cages@tmps'exécutent en parallèle sans interférer les unes avec les autres. - Persistance. Sur Firefox nu, un logiciel malveillant peut écrire dans
~/.config/autostart,~/.bashrc, cron ou ailleurs pour survivre à un redémarrage. Le conteneur éphémère de foxcage (--rm) signifie que rien ne persiste à moins que vous ne l'ayez monté par bind. - Mouvement réseau latéral. Par défaut, le conteneur ne peut pas sonder les services sur
localhost. Sur Firefox nu, une évasion du sandbox dispose d'un accès réseau complet. (Utilisez[network] mode = "host"si une cage a besoin d'un accès à localhost, par exemple pour le développement local — mais voir l'avertissement sous « Réseau » : le mode hôte expose également les sockets Unix abstraits de l'hôte.) - Élévation de privilèges. Le conteneur abandonne toutes les capacités Linux sauf
CAP_SYS_CHROOTet bloque l'acquisition de nouveaux privilèges. Les binaires setuid, les exploits du noyau via des appels système obscurs et les chemins d'élévation similaires sont coupés.
Ce que foxcage ne protège pas contre
- Attaques au niveau du navigateur. Le phishing, les extensions malveillantes et tout ce qui fonctionne dans le cadre des fonctionnalités normales de Firefox ne sont pas affectés — foxcage isole le conteneur de l'hôte, pas l'utilisateur du navigateur.
- Répertoires montés par bind. Tout ce que vous montez (
profile,downloads_dir, montages bind supplémentaires) est entièrement accessible à un navigateur compromis. Si vous montez un répertoire de profil hôte, un attaquant peut le modifier tout comme sur Firefox nu. - Capture audio via PulseAudio. La socket PulseAudio est montée par bind dans le conteneur. Bien qu'elle soit montée en lecture seule au niveau du système de fichiers, les sockets Unix sont bidirectionnelles — un processus compromis peut toujours envoyer des requêtes d'enregistrement via la socket. Une évasion du sandbox du navigateur pourrait potentiellement enregistrer l'audio du microphone de l'hôte.
- Exploits du compositeur Wayland. La socket Wayland est transmise. Les compositeurs Wayland isolent les clients les uns des autres par conception, mais une vulnérabilité dans le compositeur lui-même serait accessible.
Configuration de sécurité
Le conteneur s'exécute avec :
- Toutes les capacités Linux abandonnées (seule
CAP_SYS_CHROOTest rajoutée pour le sandbox de contenu de Firefox ;CAP_SETUID/CAP_SETGIDsont ajoutées temporairement lorsqueinit.rootest configuré) no-new-privilegespour empêcher l'élévation de privilèges- Espace de noms utilisateur sans root (
--userns keep-id) /dev/shmprivé (non partagé avec l'hôte) — taille configurable viashm_size- Réseau isolé via pasta avec le loopback de l'hôte bloqué par défaut
- DNS utilisant le DNS de l'hôte par défaut (configurable via
network.dns) - Seules les sockets spécifiques de
XDG_RUNTIME_DIRsont montées par bind (Wayland, PulseAudio, PipeWire et le proxy D-Bus filtré) — le répertoire runtime complet de l'hôte n'est jamais exposé - L'accès au bus de session D-Bus de l'hôte est toujours médié par un
xdg-dbus-proxyfiltré s'exécutant sur l'hôte. Seulsorg.freedesktop.Notifications,org.freedesktop.portal.Desktop,org.mozilla.*et (pour les forks) l'espace de noms propre du fork (par exempleorg.librewolf.*) sont accessibles — les services de session comme le trousseau et l'agent SSH/GPG sont bloqués - L'accès aux portails est large.
org.freedesktop.portal.Desktopest autorisé dans son ensemble, car c'est ainsi que fonctionnent le sélecteur de fichiers, « ouvrir le lien dans une autre application » et le partage d'écran. Cela expose égalementRemoteDesktop(clavier/souris synthétiques pour toute la session),CameraetLocation. Ceux-ci sont contrôlés par les dialogues d'approbation de votre bureau plutôt que par foxcage — et l'inviteRemoteDesktopressemble à l'invite de partage d'écran, alors lisez les dialogues d'approbation avant de les accepter.xdg-dbus-proxyn'a pas de règle « refuser une interface », donc restreindre cela signifie énumérer chaque interface dont Firefox a besoin ; voirdocs/DESIGN.mdpour savoir pourquoi cela n'est pas fait par défaut - Tous les montages bind (
profile,downloads_dir,[mounts] bindsupplémentaires) utilisentnosuid,noexec - Téléchargement du navigateur vérifié par signatures GPG : Firefox contre les sommes SHA-512 signées de Mozilla, LibreWolf contre la signature détachée des mainteneurs LibreWolf plus les SHA-256 associées
- Conteneur éphémère (
--rm) — les écritures du système de fichiers sont perdues à la sortie - Aucun périphérique hôte (webcam, clés de sécurité, imprimantes) transmis sauf activation explicite
Chaque option [network] et [mounts] que vous activez échange une partie de l'isolation contre la commodité. Les valeurs par défaut sont la configuration la plus restrictive qui vous offre tout de même un navigateur utilisable.
Prérequis
- Python 3.11+
- Podman (sans root)
- Compositeur Wayland (X11 n'est pas pris en charge)
- pasta (
sudo apt install passt) — sauf sinetwork.mode = "host" - xdg-dbus-proxy (
sudo apt install xdg-dbus-proxy) - PulseAudio ou PipeWire avec compatibilité PulseAudio (pour l'audio)
- GPU avec prise en charge DRI — facultatif ; sans
/dev/dri, foxcage émet un avertissement et Firefox rend en logiciel
Exécutez foxcage en tant qu'utilisateur de bureau normal, pas en tant que root ni via sudo — le sandbox mappe votre utilisateur dans le conteneur, et s'exécuter en root supprime l'isolation que foxcage existe pour fournir. Il refuse de démarrer en root.
Environnement testé : Debian 13 (Trixie) avec GNOME 3. D'autres distributions Linux et compositeurs Wayland peuvent fonctionner mais n'ont pas été testés.
Installation
foxcage est un script Python unique sans dépendances en dehors de la bibliothèque standard Python. Copiez-le dans un répertoire de votre PATH :```sh
sudo cp foxcage /usr/local/bin/foxcage
Ou pour une installation locale à l’utilisateur :```sh
cp foxcage ~/.local/bin/foxcage
Assurez-vous que le script est exécutable (chmod +x foxcage).
Vérifiez quelle révision vous avez avec foxcage --version — utile pour signaler un problème, car foxcage est installé en copiant un seul fichier.
Utilisation```sh
./foxcage
Au premier lancement, le script construit l’image du conteneur (télécharge Firefox depuis Mozilla, installe les dépendances Debian minimales), puis démarre Firefox. Lors des lancements suivants, foxcage vérifie les mises à jour de Firefox et reconstruit l’image automatiquement lorsqu’une nouvelle version est disponible. L’image est également reconstruite périodiquement (tous les 7 jours par défaut) pour récupérer les mises à jour des paquets système. Si la vérification des mises à jour échoue (erreur réseau, délai d’attente dépassé), un avertissement est consigné dans les journaux et l’image existante est utilisée — le démarrage n’est jamais bloqué.
Transmettez les arguments à Firefox :```sh
./foxcage https://example.com
Combinez une cage nommée avec les indicateurs Firefox :```sh ./foxcage @work --kiosk https://example.com
Si une cage est déjà en cours d'exécution, l'URL s'ouvre dans un nouvel onglet du navigateur existant au lieu de démarrer un second conteneur. Lancer `foxcage` (ou `foxcage @cage`) sans URL contre une cage en cours d'exécution se termine proprement avec un message « cage is already running » — foxcage ne peut pas faire remonter une fenêtre Wayland existante depuis l'extérieur du conteneur, il n'essaie donc pas.
Les options par lancement ne s'appliquent **pas** lorsqu'une cage est déjà en cours d'exécution. `--dns`, `--ipv4-only`, `--lifetime`, `--color` et `--fork` sont consommées au démarrage du conteneur, et les paramètres d'un conteneur en cours d'exécution ne peuvent pas être modifiés depuis l'extérieur ; elles sont donc ignorées avec un avertissement. Fermez la cage et relancez-la pour les appliquer.
> Utilisez la clé de configuration `private_browsing` pour les sessions en mode privé — *pas* l'option `--private-window` transmise telle quelle à Firefox. La clé de configuration active le mode privé pour toute la session (`browser.privatebrowsing.autostart`), de sorte que les invocations ultérieures `foxcage @cage URL` peuvent rouvrir dans des onglets. `--private-window` comme option transmise à Firefox ne rendrait privée que la première fenêtre et casserait le comportement de réouverture dans un onglet décrit ci-dessus.
>
> **Attention :** les sessions activées via `private_browsing = true` n'affichent pas les indices d'interface habituels de Firefox pour les fenêtres privées (barre d'accent violette, icône de masque, « (Private Browsing) » dans le titre). C'est parce que chaque fenêtre de la session est privée, Firefox n'a donc aucune fenêtre non privée à contraster visuellement — il supprime l'indicateur. La session *est* réellement privée ; vérifiez si vous le souhaitez en visitant `about:privatebrowsing` dans la cage (affiche la page d'informations standard de la navigation privée) ou `about:config` et en vérifiant `browser.privatebrowsing.autostart = true`.
### Navigation éphémère avec `@tmp`
Pour les liens à usage unique qui ne doivent laisser aucune trace, utilisez la cage réservée `tmp` :```sh
./foxcage @tmp https://somewhere-suspicious.example
Chaque lancement @tmp crée un Firefox neuf et jetable, sans profil persistant. Lorsque la fenêtre se ferme, tout disparaît — cookies, cache, historique, extensions, état HSTS, cache de session TLS, cache DNS, état des onglets enregistré. Cela va plus loin que la navigation privée de Firefox, qui conserve quand même les extensions et une bonne partie de l'état sur disque.
Plusieurs cages @tmp s'exécutent en parallèle, chacune isolée des autres. La barre de menus affiche FoxCage - tmp (<short id>) afin que vous puissiez distinguer les fenêtres éphémères concurrentes.
Les cages éphémères ouvrent une page blanche au démarrage et de nouveaux onglets vierges — la page d'accueil par défaut de Firefox et le contenu des nouveaux onglets (sites populaires, recommandations Pocket, flux d'activité) ne sont que du bruit sur un profil neuf sur le point d'être jeté, ils sont donc supprimés. Les cages persistantes conservent les paramètres par défaut de Firefox.
Cages éphémères nommées
Si vous souhaitez donner un nom explicite à une session jetable (par exemple, un terrier de lapin de recherche que vous voudrez rouvrir dans un nouvel onglet), utilisez @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>` reste éphémère — lorsque vous fermez la fenêtre, tout disparaît. La différence avec `@tmp` seul est que les lancements suivants avec le même nom **réutilisent la fenêtre existante** (comme les cages persistantes), vous pouvez donc ajouter d'autres onglets plus tard sans démarrer une copie parallèle. `@tmp` seul conserve son comportement « chaque lancement crée un nouvel environnement jetable ».
Le libellé de la barre de menus affiche le nom que vous avez choisi (`FoxCage - tmp-research`), ce qui rend la fenêtre clairement identifiée.
#### Personnalisation des valeurs par défaut éphémères
Créez `~/.config/foxcage/tmp.toml` pour définir les valeurs par défaut de toutes les cages éphémères (aussi bien `@tmp` seul que chaque `@tmp-<name>`). Par exemple :```toml
private_browsing = true
lifetime = "30m"
[network]
dns = "cloudflare"
Chaque lancement éphémère obtient désormais une fenêtre privée, Cloudflare DoH, et se ferme automatiquement après 30 minutes — avec une éphéméralité totale préservée. Les éphémères nommés héritent de tmp.toml par défaut ; si vous souhaitez remplacer par nom, créez ~/.config/foxcage/tmp-<name>.toml. Ce fichier s'applique alors à la place de tmp.toml — pas de fusion, le fichier le plus spécifique l'emporte. Copiez-y les valeurs par défaut partagées si vous les voulez.
Tout ce que vous pouvez définir dans la configuration d'une cage régulière fonctionne ici, sauf la clé qui contredirait l'éphéméralité elle-même :
profile— erreur fatale.
Elle pointe vers un répertoire de profil persistant sur l'hôte, ce qui contredit directement l'objectif de @tmp. Si vous voulez une cage sandbox avec un profil persistant, utilisez une cage nommée régulière (@work, @research, etc.) qui ne commence pas par tmp-.
Remplacer le DNS par lancement
Le drapeau --dns (et la clé de configuration équivalente network.dns) accepte trois formes :```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
**Lorsque la valeur correspond à un fournisseur connu (par alias ou par IP), foxcage active automatiquement le DNS sur HTTPS forcé vers ce fournisseur.** Le TRR de Firefox est alors défini sur le mode 3 (strict, sans repli en clair) avec l'adresse d'amorçage renseignée, afin qu'aucune résolution non chiffrée ne fuite au démarrage. Vous voyez un message d'une ligne sur stderr, par exemple `Enabling DNS over HTTPS via Cloudflare`.
Alias intégrés :
| Alias | IP | Filtrage |
|-------|------|-----------|
| `cloudflare` | 1.1.1.1 | aucun |
| `cloudflare-security` | 1.1.1.2 | bloque les logiciels malveillants |
| `cloudflare-family` | 1.1.1.3 | bloque les logiciels malveillants + contenus adultes |
| `google` | 8.8.8.8 | aucun |
| `quad9` | 9.9.9.9 | bloque les logiciels malveillants (défaut Quad9) |
| `quad9-unfiltered` | 9.9.9.10 | aucun |
| `adguard` | 94.140.14.14 | bloque les publicités + traceurs |
| `adguard-family` | 94.140.14.15 | publicités + traceurs + contenus adultes |
| `opendns` | 208.67.222.222 | certains |
Une IP qui n'est pas dans le tableau (par exemple le Pi-hole de votre réseau local) reste en clair uniquement — aucun DoH n'est activé, car foxcage ne connaît pas le point de terminaison DoH correspondant. Utilisez alors la forme URI : `--dns https://pi.hole/dns-query` (avec un certificat valide) active le DoH et laisse le DNS du conteneur intact.
La forme URI ne configure pas le DNS en clair du conteneur, donc tout ce qui se trouve dans le conteneur et qui n'est pas Firefox utilise toujours le DNS de l'hôte. C'est volontaire — `--dns URI` signifie « faire utiliser à Firefox ce résolveur DoH », un point c'est tout.
`--dns` est incompatible avec `network.mode = "host"`, qui dispose déjà d'un accès réseau hôte complet.
### Identification visuelle des cages
Chaque cage nommée reçoit une couleur d'accent dans la barre de menus afin que vous puissiez distinguer les fenêtres d'un coup d'œil. **Vous n'avez rien à configurer** — la couleur est dérivée de manière déterministe du nom de la cage (hachée en SHA256 pour obtenir une teinte, avec saturation et luminosité fixes). `@banking`, `@work`, `@personal`, `@tmp-research` obtiennent tous des couleurs distinctes et stables sans que vous ayez à lever le petit doigt.
La cage par défaut (anonyme) conserve l'orange intégré.
Si vous souhaitez remplacer la couleur auto-dérivée, définissez-la explicitement :```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626" # red — overrides the auto-derived colour
I don't see any content to translate. The input appears to be empty. Please provide the actual Markdown content for chunk 21/73, and I'll translate it into French while preserving all structure and code elements.```sh ./foxcage @experiment --color "#10b981" https://example.com # teal, one-off
Accepte les codes hexadécimaux CSS standard : `#rgb`, `#rrggbb` ou `#rrggbbaa` (avec alpha). Les couleurs dérivées automatiquement sont réglées pour être visibles à la fois sur les barres de menus claires et sombres (luminosité fixée à 55 %, saturation à 75 %), vous ne devriez donc pas avoir besoin de les remplacer pour des raisons de thème.
### Cages à durée limitée
Le drapeau `--lifetime` (et la clé de configuration équivalente `lifetime`) ferme automatiquement une cage après une durée définie. Le format est `<number><unit>` avec l'unité `s`, `m` ou `h` :```sh
./foxcage @tmp --lifetime 10m https://example.com
./foxcage @work --lifetime 2h
Le compte à rebours démarre lorsque Firefox se lance réellement dans la cage — le démarrage du conteneur et le temps de construction de l'image n'entament pas votre budget. Le libellé de la barre de menu de la cage affiche le compte à rebours en plus de l'identité de la cage — p. ex. FoxCage - tmp (a3f2b1) | 9m — mis à jour une fois par minute tant qu'il reste plus d'une minute, et une fois par seconde dans la dernière minute. Lorsque le compte à rebours atteint zéro, Firefox se ferme de lui-même et le conteneur se termine. Si vous fermez Firefox vous-même avant la fin de la durée de vie, rien d'inhabituel ne se produit.
Définissez une durée de vie par défaut pour chaque cage dans sa configuration :```toml
~/.config/foxcage/tmp.toml — every @tmp launch auto-closes after 15 minutes
lifetime = "15m" private_browsing = true
`--lifetime` sur la ligne de commande prime sur toute valeur de configuration.
Forcez une reconstruction complète de l'image (re-télécharge Firefox et tous les paquets système) :```sh
./foxcage --rebuild
Un conteneur en cours d'exécution conserve l'image à partir de laquelle il a été démarré, même après que foxcage a reconstruit l'étiquette de l'image. Si vous essayez d'ouvrir un onglet dans une cage dont l'image a été mise à jour depuis (par --rebuild, une mise à jour de Firefox ou la reconstruction planifiée), foxcage refuse avec une erreur (également affichée sous forme de notification bureau) et vous demande de quitter Firefox et de relancer — ce qui démarre un nouveau conteneur sur l'image actuelle. Avec --rebuild sur une cage active, foxcage avertit au préalable, effectue la construction, puis applique la même vérification.
Mises à jour
foxcage vérifie les nouvelles versions du navigateur à chaque lancement — l'API de publication de Mozilla pour Firefox, le point de terminaison des versions de GitLab pour LibreWolf. Si une mise à jour est disponible, l'image du conteneur est reconstruite automatiquement. L'image est également reconstruite périodiquement (tous les 7 jours par défaut) pour intégrer les mises à jour de sécurité Debian. Le mécanisme de mise à jour automatique intégré du navigateur est désactivé car les mises à jour sont gérées au niveau de l'image.
Si la vérification de mise à jour échoue (pas de réseau, délai d'attente de l'API), un avertissement est affiché et l'image existante est utilisée — vous pouvez toujours naviguer.
La cadence de mise à jour se trouve au niveau supérieur de la configuration ; l'épinglage de version et de canal se trouve dans la section par 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)
**Pour épingler une version ESR, il faut aussi préciser le canal.** L'index des versions de Mozilla répertorie les versions ESR sans le suffixe `esr` que portent leurs téléchargements, donc un simple `version = "140"` sur le canal par défaut pointe vers une version qui n'existe pas. Définissez les deux :```toml
[firefox]
channel = "esr"
version = "140" # → 140.13.0esr
Un pin qui ne correspond à aucune version est désormais une erreur nommant le pin, plutôt que de revenir silencieusement à la dernière version. Un échec temporaire pour atteindre l'API de Mozilla émet toujours un avertissement et continue avec l'image existante, de sorte qu'un réseau instable ne bloque jamais le démarrage.
Les pins avec suffixe doivent être entièrement qualifiés — "140.13.0esr" et "150.0b9" fonctionnent, "140esr" et "150b9" sont rejetés au chargement de la configuration car aucune version ne peut jamais leur correspondre. Il en va de même pour les révisions LibreWolf : "146.0.1-1" fonctionne, "146-1" non.
Pour forcer une reconstruction complète immédiate : ./foxcage --rebuild
Forks de Firefox (LibreWolf)
foxcage peut exécuter un fork de Firefox axé sur la confidentialité à la place du Firefox en amont :```toml fork = "librewolf" # default is "firefox"
[librewolf] version = "146.0.1-1" # optional pin; partial pins ("146", "146.0.1") also work
Ou par lancement via CLI :```sh
foxcage @tmp --fork librewolf https://example.com
LibreWolf : fork de Firefox durci pour la confidentialité — protection stricte contre le pistage, DoH, RFP, télémétrie désactivée par défaut. Archive Linux signée depuis GitLab (librewolf-community/browser/bsys6), vérifiée par GPG avec la clé des mainteneurs LibreWolf 662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16 et une vérification croisée via le fichier .sha256sum adjacent. Le librewolf.cfg fourni avec LibreWolf est conservé ; foxcage ajoute ses propres préférences par-dessus plutôt que de les écraser.
Le canal est réservé à Firefox : firefox.channel = "beta" | "esr" est rejeté lorsque fork est autre chose que "firefox". LibreWolf ne dispose que d'un seul canal de publication.
Changer de fork (via la config ou --fork) modifie le hash du Containerfile, ce qui déclenche une reconstruction au prochain lancement — pas besoin de --rebuild manuel.
Compatibilité des profils
Utilisez un profil dédié pour chaque fork. La valeur par défaut la plus sûre est de laisser foxcage fournir son propre profil (omettez
profilede la configuration), ou de pointerprofilevers un répertoire que vous n'ouvrez pas également depuis l'hôte.
- LibreWolf : généralement sans problème à partager avec votre profil Firefox hôte — LibreWolf suit les versions de Firefox à quelques jours près, donc les conflits de schéma
compatibility.inisont rares. Risques : (1) seule une utilisation séquentielle est sûre (le fichier de verrouillage de Firefox empêche les ouvertures simultanées) ; (2) dans la courte fenêtre après une version stable de Firefox, lancer Firefox puis LibreWolf peut déclencher une boîte de dialogue de migration "utilisé par une version plus récente" ; (3) les fonctionnalités que LibreWolf retire (Sync, Pocket, compte Mozilla) échouent silencieusement mais ne corrompent pas les données.
Cages nommées
Exécutez des instances sandboxées séparées avec leur propre configuration et profil Firefox :```sh ./foxcage @work
Cela charge `~/.config/foxcage/work.toml` et utilise une image distincte (`foxcage-work`), un conteneur (`foxcage-work`) et un volume (`foxcage-work-profile`). Le fichier de configuration doit exister pour les cages nommées. Les noms de cages ne peuvent contenir que des lettres, des chiffres, des traits d'union et des underscores.
## Configuration
Les fichiers de configuration se trouvent dans `$XDG_CONFIG_HOME/foxcage/` (par défaut `~/.config/foxcage/`).
- `config.toml` — cage par défaut (facultatif, des valeurs par défaut raisonnables sans lui)
- `<name>.toml` — cage nommée, chargée avec `@<name>` (obligatoire)
Les clés de configuration inconnues sont rejetées avec une erreur. Consultez `config.toml.example` pour toutes les options disponibles avec leurs valeurs par défaut.
### Exemple 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"]
Profil Firefox de l'hôte
Pour partager un profil Firefox de l'hôte avec la cage, définissez profile sur le répertoire du profil. Trouvez le chemin de votre profil en visitant about:profiles dans Firefox sur l'hôte — ou pointez simplement vers un nouveau répertoire vide si vous voulez que la cage démarre avec un profil propre qui persiste sur l'hôte.```toml
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"
Seul ce répertoire est bind-monté dans la cage. Les profils voisins sous `~/.mozilla/firefox/` et le registre `profiles.ini` ne sont pas exposés — une cage compromise ne peut pas les altérer.
Si `profile` n'est pas défini, un volume Podman nommé stocke le profil Firefox à la place (voir « Ce qui persiste » plus bas). Si le même profil est déjà ouvert dans Firefox sur l'hôte, le fichier de verrouillage par profil de Firefox provoquera un conflit — utilisez un profil dédié par cage.
### Réseau
Par défaut, le conteneur utilise pasta avec le loopback de l'hôte bloqué et le DNS de l'hôte. pasta nécessite podman 4.4 ou plus récent (c'est le mode par défaut en rootless depuis podman 5.0).
**Réseau hôte** supprime entièrement l'isolation réseau. Utilisez-le lorsque la cage doit atteindre des services sur le `localhost` de l'hôte (par exemple, un serveur de développement local, une base de données sur `127.0.0.1`) :```toml
[network]
mode = "host"
dns ne peut pas être combiné avec mode = "host" — la mise en réseau de l'hôte utilise déjà le résolveur de l'hôte.
Le mode hôte abandonne plus que localhost. Il place la cage dans l'espace de noms réseau de l'hôte, et les sockets Unix abstraites sont cantonnées à cet espace de noms plutôt qu'au système de fichiers. Ainsi, une cage en mode hôte peut atteindre directement les sockets à adresse abstraite sur l'hôte — y compris
@/tmp/.X11-unix/X0de Xwayland si vous exécutez X11 ou Xwayland (journalisation des entrées, bien que foxcage soit uniquement Wayland), et un bus de session configuré avecunix:abstract=…, ce qui contournerait le proxy D-Bus filtré. Cela est inhérent au partage de la pile réseau, et non quelque chose que foxcage peut filtrer. Utilisez le mode hôte lorsque vous en avez besoin, et préférez une cage nommée que vous ne lancez que dans ce but.
Les cages IPv4 uniquement désactivent complètement IPv6 :```toml [network] ipv4_only = true
Ou lors du lancement avec l'option `--ipv4-only` (forme courte `-4`, comme dans `ssh`/`curl`/pasta):```sh
./foxcage @tmp -4 https://example.com
Ceci exécute pasta en mode IPv4 uniquement (-4), de sorte que le conteneur ne possède aucune pile IPv6, et définit en plus network.dns.disableIPv6 dans Firefox afin qu'il ne résolve pas les enregistrements AAAA — ce qui importe lorsque DoH est activé, car les réponses DoH contournent le résolveur du conteneur. ipv4_only ne peut pas être combiné avec mode = "host" — le réseau hôte utilise directement la pile réseau de l'hôte, désactivez donc IPv6 sur l'hôte à la place.
Commandes d'init
Exécutez des commandes personnalisées au moment de la construction ou au démarrage du conteneur via [init] :
build— s'exécute lors de la construction de l'image en tant que root. À utiliser pour installer des paquets ou toute autre configuration lente. Les modifications des commandes de construction déclenchent automatiquement une reconstruction de l'image.root— s'exécute au démarrage du conteneur en tant que root, avant Firefox. À utiliser pour des tâches root rapides à l'exécution (ajustement des permissions, écriture de fichiers de configuration).user— s'exécute au démarrage du conteneur en tant que votre utilisateur, avant Firefox. À utiliser pour créer des répertoires, configurer l'état au niveau utilisateur.```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"]
Les trois clés sont des listes de chaînes de commandes shell. Si l'une de ces commandes échoue, le conteneur se termine sans démarrer Firefox.
**Remarque de sécurité :** Lorsque `init.root` est défini, le conteneur démarre en tant que root avec `CAP_SETUID` et `CAP_SETGID` ajoutés (en plus du `CAP_SYS_CHROOT` par défaut) afin de pouvoir revenir à l'utilisateur standard. Ces capacités ne sont conservées que pendant la phase d'initialisation root — après l'abandon des privilèges, le processus de l'utilisateur standard ne possède aucune capacité supplémentaire. Sans `init.root`, le conteneur s'exécute avec l'ensemble minimal de capacités par défaut.
## Ce qui persiste
Sans configuration, un volume Podman nommé stocke le profil Firefox (marque-pages, paramètres, extensions, plugin Widevine DRM). Tout le reste est éphémère.
- Cage par défaut : `foxcage-profile`
- Cage nommée : `foxcage-<name>-profile`
Pour repartir de zéro, supprimez le volume :```sh
podman volume rm foxcage-profile
Si profile est défini, le répertoire hôte est monté directement par bind mount et aucun volume n'est créé.
Utilisation du disque
Chaque image de cage fait environ 1 Go. Une reconstruction ré-étiquette l'image et laisse la précédente derrière en tant qu'entrée non étiquetée <none>, donc foxcage supprime l'image qu'il vient de remplacer après chaque construction réussie. Il ne supprime que cette image spécifique, et jamais celle qu'une cage en cours d'exécution utilise encore.
Les images devenues orphelines avant l'existence de ce comportement ne sont pas nettoyées rétroactivement. Pour les récupérer :```sh podman images --filter dangling=true # review first podman image prune # then remove
Les mises à jour de Firefox sont détectées automatiquement à chaque lancement. Pour forcer une reconstruction complète (par exemple pour appliquer immédiatement les mises à jour de sécurité du système) :```sh
./foxcage --rebuild
Thème
foxcage transmet automatiquement les éléments suivants de l'hôte, afin que Firefox dans le conteneur ait l'apparence et le comportement d'une application native :
- Polices. Les polices système (
/usr/share/fonts) et les polices utilisateur (~/.local/share/fonts) sont montées en lecture seule (bind mount). La configuration des polices depuis~/.config/fontconfigest également transmise. - Thème GTK et mode sombre. Détectés via
GTK_THEMEougsettingset transmis au conteneur. La configuration GTK depuis~/.config/gtk-3.0et~/.config/gtk-4.0est montée en lecture seule (bind mount). - Fuseau horaire. Le nom du fuseau horaire de l'hôte (détecté depuis
TZ, le lien symbolique/etc/localtimeou/etc/timezone) est passé dans le conteneur commeTZ, et/etc/localtimeest monté en lecture seule (bind mount). Les deux sont nécessaires : Firefox déduit le fuseau horaire JavaScript du nom de la zone, et non du contenu du fichier — sansTZ, les sites web afficheraient les heures en UTC. - Locale.
LANGest transmise. La locale de l'hôte est générée dans l'image du conteneur au moment de la construction.
Étiquette de cage. La barre de menus de Firefox affiche « FoxCage » (ou « FoxCage - nom » pour les cages nommées) afin que vous sachiez d'un coup d'œil que vous êtes dans une session conteneurisée. La barre de menus est toujours visible via la politique d'entreprise.
Le conteneur n'inclut que le thème GTK Adwaita. Sur les bureaux GNOME, cela fonctionne immédiatement. Sur KDE ou d'autres bureaux, Firefox reviendra à Adwaita si votre thème GTK (par ex. Breeze) n'est pas installé dans le conteneur. La détection du mode sombre fonctionne toujours tant que la préférence est définie via gsettings ou GTK_THEME.
DRM (Netflix, Disney+, etc.)
Le DRM Widevine fonctionne immédiatement. Lors de la première visite d'un site protégé par DRM, Firefox téléchargera automatiquement le CDM Widevine. Cela peut prendre un moment.
Intégration hôte (toujours active)
foxcage utilise un proxy D-Bus filtré pour donner à Firefox l'accès au portail de bureau XDG et au démon de notification de l'hôte. Ces fonctionnalités sont sûres car tout accès passe par l'utilisateur — l'hôte affiche des boîtes de dialogue natives avec lesquelles vous devez interagir. Un navigateur compromis ne peut pas accéder silencieusement aux ressources de l'hôte.
- Téléversement de fichiers — sélecteur de fichiers natif de l'hôte (vous choisissez les fichiers à partager)
- Liens externes —
mailto:, liens magnet, etc. s'ouvrent via le sélecteur d'applications de l'hôte - Notifications de bureau — transmises au démon de notification de l'hôte
- Partage d'écran — sélecteur d'écran du portail + flux vidéo PipeWire (nécessite PipeWire sur l'hôte)
Passage de périphériques (optionnel)
Ces fonctionnalités transmettent directement les périphériques de l'hôte dans le conteneur et sont désactivées par défaut — contrairement aux fonctionnalités de portail ci-dessus, il n'y a aucune confirmation côté hôte. Un navigateur compromis pourrait utiliser le matériel silencieusement.```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
## Pas encore pris en charge
Certaines fonctionnalités de la plateforme web ne fonctionnent pas dans le conteneur en raison d'une intégration hôte manquante. Elles sont listées ici par souci de transparence.
**Bluetooth, USB, série et NFC.** Les API Web Bluetooth, WebUSB, Web Serial et WebNFC nécessitent un accès aux périphériques et des services système (BlueZ, udev) qui ne sont pas disponibles dans le conteneur.
**Manettes et MIDI.** L'API Gamepad nécessite l'accès à `/dev/input/`. Web MIDI nécessite l'accès au séquenceur ALSA. Aucun des deux n'est transmis.
**Installation de PWA.** Les applications web progressives ne peuvent pas être installées sur le bureau de l'hôte depuis l'intérieur du conteneur.
**Accessibilité.** La prise en charge des lecteurs d'écran via AT-SPI est désactivée (`NO_AT_BRIDGE=1`) — le conteneur n'a aucune connexion au bus d'accessibilité de l'hôte. La synthèse vocale de l'API Web Speech fonctionne : `speech-dispatcher` avec le moteur `espeak-ng` est installé dans la cage et se lance automatiquement à la première utilisation, l'audio étant routé via la socket PulseAudio partagée.
## Configuration de l'hôte
### Recommandé : stockage en overlay avec fuse-overlayfs
Podman en mode rootless peut utiliser par défaut le pilote de stockage `vfs`, qui copie les couches d'image entières au lieu d'utiliser des montages overlay. Cela rend le démarrage du conteneur après une génération beaucoup plus lent. Pour corriger cela, installez `fuse-overlayfs` et ajoutez ce qui suit à `~/.config/containers/storage.conf` :```toml
[storage]
driver = "overlay"
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
Définir foxcage comme navigateur par défaut
Tout d'abord, assurez-vous que le script foxcage se trouve dans son emplacement permanent (par exemple ~/bin/foxcage ou /usr/local/bin/foxcage). La commande d'installation enregistre le chemin actuel du script dans le fichier .desktop ; le déplacer ensuite cassera le lanceur.
Exécutez ensuite :```sh foxcage --install
This crée un fichier `.desktop` pointant vers l'emplacement actuel du script, installe l'icône foxcage, et actualise les bases de données du bureau et des icônes. FoxCage devrait alors apparaître dans votre menu d'applications.
Pour définir foxcage comme navigateur web par défaut afin que les liens cliqués dans d'autres applications s'ouvrent dans foxcage :```sh
xdg-settings set default-web-browser foxcage.desktop
Si une cage est déjà en cours d'exécution, les URL s'ouvrent dans un nouvel onglet du navigateur existant.
Pour annuler :```sh foxcage --uninstall
`StartupNotify=true` est défini dans le fichier `.desktop`, ce qui indique au compositeur d'afficher un curseur de chargement pendant que foxcage démarre. Lorsqu'une construction d'image est nécessaire (ce qui peut prendre plusieurs minutes), foxcage envoie une notification bureau pour que vous sachiez que Firefox est en route. Toute erreur de sortie anticipée (faute de frappe dans la configuration, dépendance manquante, nom de cage mal formé) est également signalée par une notification bureau, afin que les utilisateurs ayant lancé l'application depuis le bureau ne restent pas à regarder un écran vide quand foxcage échoue sans terminal attaché. Ces deux cas nécessitent `notify-send` (fourni par `libnotify-bin` sur Debian/Ubuntu) — s'il n'est pas installé, les notifications sont silencieusement ignorées et l'erreur est toujours envoyée vers stderr.
<details>
<summary>Configuration manuelle</summary>
Si vous préférez créer le fichier `.desktop` manuellement, créez `~/.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
Remplacez /path/to/foxcage par le chemin réel du script. Enregistrez-le :```sh
update-desktop-database ~/.local/share/applications
</details>
## Exécution des tests
La suite de tests utilise pytest + pytest-cov, déclarés comme dépendances de développement uniquement dans `requirements-dev.txt`.```
pip install -r requirements-dev.txt
pytest
Les tests sont entièrement hermétiques — pas de podman, pas de réseau, pas de vrai système de fichiers en dehors de tmp_path de pytest. La suite exige une couverture de lignes et de branches à 100 % (configurée dans pytest.ini et .coveragerc) ; toute ligne non couverte, ou tout côté non pris d'un conditionnel, fait échouer l'exécution. La CI exécute la suite à chaque push via .gitlab-ci.yml.
Remerciements
Ce projet a été développé par Mike Cardwell, avec l'assistance de Claude Code, l'outil de codage IA d'Anthropic.