
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.
Exécutez 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 utilisateur et réseau, isolé de l'hôte — tout en conservant une accélération GPU complète, l'audio et la prise en charge des DRM.
Firefox dispose déjà d'un sandbox multi-processus qui isole les moteurs de rendu de contenu web à l'aide d'espaces de noms Linux et de seccomp-bpf. Pour la plupart des menaces, cela est efficace. foxcage ajoute une seconde paroi : 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.
~/.ssh, ~/.gnupg, les profils de navigateur pour d'autres navigateurs, les bases de données de gestionnaires de mots de passe, les documents, le code source. Avec foxcage, l'attaquant ne voit que ce que vous avez explicitement monté.@tmp ne 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 encore. Plusieurs cages @tmp s'exécutent simultanément sans interférer les unes avec les autres.~/.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.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.)CAP_SYS_CHROOT et 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.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 falsifier tout comme sur Firefox nu.Le conteneur s'exécute avec :
CAP_SYS_CHROOT rajoutée pour le sandbox de contenu de Firefox ; CAP_SETUID/CAP_SETGID ajoutées temporairement lorsque init.root est configuré)no-new-privileges pour empêcher l'élévation de privilèges--userns keep-id)/dev/shm privé (non partagé avec l'hôte) — taille configurable via shm_sizenetwork.dns)XDG_RUNTIME_DIR sont montés par bind (Wayland, PulseAudio, PipeWire et le proxy D-Bus filtré) — le répertoire d'exécution complet de l'hôte n'est jamais exposéxdg-dbus-proxy filtré s'exécutant sur l'hôte. Seuls org.freedesktop.Notifications, org.freedesktop.portal.Desktop, org.mozilla.*, et (pour les forks) l'espace de noms propre du fork (par exemple org.librewolf.*) sont accessibles — les services de session comme le trousseau et l'agent SSH/GPG sont bloquésorg.freedesktop.portal.Desktop est 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. Il expose également RemoteDesktop (clavier/souris synthétiques pour toute la session), Camera et Location. Ceux-ci sont contrôlés par les propres dialogues d'approbation de votre bureau plutôt que par foxcage — et l'invite RemoteDesktop ressemble à l'invite de partage d'écran, alors lisez les dialogues d'approbation avant de les accepter. xdg-dbus-proxy n'a pas de règle « refuser une interface », donc restreindre cela signifie énumérer chaque interface dont Firefox a besoin ; voir docs/DESIGN.md pour savoir pourquoi cela n'est pas fait par défautprofile, downloads_dir, [mounts] bind supplémentaires) utilisent nosuid,noexecgpg --verify, qui sort avec 0 pour une signature faite par une clé révoquée et pour toute clé du trousseau. foxcage exige en outre que la signature remonte à la clé primaire épinglée, et refuse toute version signée par une sous-clé que son propriétaire a révoquée comme compromise — voir Clés de signature révoquées--rm) — les écritures du système de fichiers sont perdues à la sortieChaque option [network] et [mounts] que vous activez échange une partie de l'isolation contre de la commodité. Les valeurs par défaut sont la configuration la plus restrictive qui vous donne tout de même un navigateur utilisable.
sudo apt install passt) — sauf si network.mode = "host"sudo apt install xdg-dbus-proxy)/dev/dri, foxcage émet un avertissement et Firefox effectue le rendu en logiciel. Les pilotes VA-API pour Intel, AMD et nouveau sont installés dans l'image, donc le décodage vidéo matériel fonctionne sans paquets de pilotes hôte — voir Décodage vidéo matériel (VA-API)Exécutez foxcage en tant que votre utilisateur de bureau normal, pas en tant que root ou via sudo — le sandbox mappe votre utilisateur dans le conteneur, et s'exécuter en tant que root supprime l'isolation que foxcage existe pour fournir. Il refuse de démarrer en tant que 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.
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 lors du signalement d'un problème, car foxcage est installé en copiant un seul fichier.
./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 intégrer 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és 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 ; ils sont donc ignorés avec un avertissement. Fermez la cage et relancez pour les appliquer.
> Utilisez la clé de configuration `private_browsing` pour les sessions en mode privé — *pas* l’option CLI brute `--private-window` de Firefox. La clé de configuration active le mode privé à l’échelle de la session (`browser.privatebrowsing.autostart`), de sorte que les invocations ultérieures `foxcage @cage URL` puissent rouvrir dans des onglets. `--private-window` comme option de passage à Firefox rendrait uniquement la première fenêtre privée 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 indicateurs d’interface habituels du mode privé de Firefox (barre d’accent violette, icône de masque, « (Private Browsing) » dans le titre). Cela vient du fait que chaque fenêtre de la session est privée, Firefox n’a donc aucune fenêtre non privée pour créer un contraste visuel — il supprime l’indicateur. La session *est* réellement privée ; vérifiez-le si vous le souhaitez en visitant `about:privatebrowsing` dans la cage (affiche la page d’informations standard du mode privé) ou `about:config` et en contrôlant `browser.privatebrowsing.autostart = true`.
### Navigation éphémère avec `@tmp`
Pour les liens ponctuels qui ne doivent laisser aucune trace, utilisez la cage réservée `tmp` :```sh
./foxcage @tmp https://somewhere-suspicious.example
Chaque lancement @tmp est un Firefox éphémère 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 encore 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 (<id court>) afin que vous puissiez distinguer les fenêtres éphémères concurrentes.
Les cages éphémères ouvrent une page vierge au démarrage et des 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 qui est sur le point d'être jeté, ils sont donc supprimés. Les cages persistantes conservent les valeurs par défaut de Firefox.
Si vous souhaitez un nom explicite pour une session jetable (par exemple, un terrier de lapin de recherche que vous voudrez rouvrir dans un nouvel onglet), utilisez @tmp-<nom> :```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` simple est que les secondes ouvertures portant le même nom **réutilisent la fenêtre existante** (comme pour les cages persistantes), vous pouvez donc ajouter d'autres onglets plus tard sans lancer une copie parallèle. `@tmp` simple conserve son comportement « chaque lancement est un jetable neuf ».
Le libellé de la barre de menus affiche le nom que vous avez choisi (`FoxCage - tmp-research`), la fenêtre est donc étiquetée de manière significative.
#### Personnaliser les 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 (à la fois `@tmp` simple et chaque `@tmp-<name>`). Par exemple :```toml
private_browsing = true
lifetime = "30m"
extensions = ["ublock-origin"]
[network]
dns = "cloudflare"
Chaque lancement éphémère dispose désormais d'une fenêtre privée, d'uBlock Origin, de Cloudflare DoH, et se ferme automatiquement après 30 minutes — avec une éphéméralité totale intacte. Les cages éphémères nommées héritent de tmp.toml par défaut ; si vous souhaitez remplacer ce comportement par nom, créez ~/.config/foxcage/tmp-<name>.toml. Ce fichier s'applique alors à la place de tmp.toml — aucune fusion, le fichier le plus spécifique l'emporte directement. Copiez-y les paramètres partagés par défaut si vous les souhaitez.
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 souhaitez une cage sandboxée avec un profil persistant, utilisez une cage nommée régulière (@work, @research, etc.) qui ne commence pas par tmp-.
Une cage peut avoir des extensions intégrées dans son image et installées de force à chaque lancement :```toml extensions = ["ublock-origin"]
Mettez cela dans `~/.config/foxcage/tmp.toml` et **chaque cage jetable démarre avec uBlock Origin déjà actif** — ce qui compte, car une cage éphémère est sinon le navigateur le moins protégé que vous possédez, utilisé précisément sur les liens auxquels vous faites le moins confiance. Un profil `@tmp` frais ne comporte aucune extension, et en installer une à la main est inutile dans une session qui s’autodétruit à la fermeture.
Chaque entrée peut être le nom court de l’extension figurant dans l’URL addons.mozilla.org, l’URL de la fiche elle-même, ou l’identifiant de l’extension :```toml
extensions = [
"ublock-origin",
"https://addons.mozilla.org/firefox/addon/noscript/",
"[email protected]",
]
Les modules complémentaires sont résolus via l'API d'addons.mozilla.org au lancement, téléchargés lors de la construction de l'image, et vérifiés par rapport au SHA-256 publié par AMO. La version résolue fait partie du Containerfile, donc une nouvelle version d'une extension modifie le hash de l'image et déclenche une reconstruction — les extensions se mettent à jour de la même manière que Firefox, et pour la même raison : rien n'est installé au moment de l'exécution, donc un profil éphémère frais ne re-télécharge jamais rien.
Parce qu'elles sont installées par politique d'entreprise plutôt qu'à la main :
about:addons les affiche comme installées par votre organisation).private_browsing = true. Cela nécessite Firefox 136 ou ESR 128.8 ; les versions plus anciennes ignorent le paramètre et laissent le module inerte dans les fenêtres privées.Limitations :
.xpi nue est rejetée.@tmp et chaque @tmp-<name> construisent une seule image foxcage-tmp, donc donner à une cage éphémère nommée une liste extensions différente de celle de tmp.toml fait que les deux se reconstruisent l'une par-dessus l'autre à des lancements alternés. Conservez les listes d'extensions éphémères dans tmp.toml.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 over HTTPS forcé vers ce fournisseur.** Le TRR de Firefox est défini sur le mode 3 (strict, sans repli en clair) avec l'adresse d'amorçage renseignée, afin qu'il n'y ait aucune fuite de résolution non chiffrée au démarrage. Vous voyez une notification d'une ligne sur stderr comme `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 malwares |
| `cloudflare-family` | 1.1.1.3 | bloque les malwares + contenus adultes |
| `google` | 8.8.8.8 | aucun |
| `quad9` | 9.9.9.9 | bloque les malwares (Quad9 par défaut) |
| `quad9-unfiltered` | 9.9.9.10 | aucun |
| `adguard` | 94.140.14.14 | bloque les publicités + traqueurs |
| `adguard-family` | 94.140.14.15 | publicités + traqueurs + contenus adultes |
| `opendns` | 208.67.222.222 | certains |
Une IP qui ne figure pas dans le tableau (par ex. le Pi-hole de votre LAN) reste uniquement en clair — aucun DoH n'est activé, car foxcage ne connaît pas le point de terminaison DoH correspondant. Utilisez le format URI pour cela : `--dns https://pi.hole/dns-query` (avec un certificat valide) active le DoH et laisse le DNS du conteneur intact.
Le format URI ignore la définition du DNS en clair du conteneur, donc tout ce qui se trouve à l'intérieur du conteneur et qui n'est pas Firefox utilise toujours le DNS de l'hôte. C'est délibéré — `--dns URI` signifie « faire utiliser ce résolveur DoH à Firefox », un point c'est tout.
`--dns` est incompatible avec `network.mode = "host"`, qui dispose déjà d'un accès réseau complet à l'hôte.
### Identification visuelle des cages
Chaque cage nommée reçoit une couleur d'accent dans la barre de menu 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 une saturation et une luminosité fixes). `@banking`, `@work`, `@personal`, `@tmp-research` obtiennent tous des couleurs distinctes et stables sans que vous leviez le petit doigt.
La cage par défaut (anonyme) conserve l'orange intégré.
Si vous souhaitez remplacer la couleur dérivée automatiquement, définissez-la explicitement :```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626" # red — overrides the auto-derived colour
## Installation
To install the tool, run the following command:
```bash
pip install tool-name
After installation, you can use the tool from the command line:
tool-name --help
This project is licensed under the MIT License.
./foxcage @experiment --color "#10b981" https://example.com # teal, one-off
```
Accepts standard CSS hex: `#rgb`, `#rrggbb`, or `#rrggbbaa` (with alpha). The auto-derived colours are tuned to be visible on both light and dark menubars (lightness fixed at 55%, saturation at 75%), so you shouldn't need to override for theme reasons.
### Time-limited cages
The `--lifetime` flag (and equivalent `lifetime` config key) auto-closes a cage after a set duration. Format is `<number><unit>` with unit `s`, `m`, or `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 temps de démarrage du conteneur et de construction de l'image ne compte pas dans votre budget. L'étiquette 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` — mise à 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 tout seul 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 par 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 l'emporte sur toute valeur de configuration.
Forcer 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 reconstruit le tag de l'image. Si vous essayez d'ouvrir un onglet dans une cage dont l'image a été mise à jour depuis (via `--rebuild`, une mise à jour de Firefox ou la reconstruction planifiée), foxcage refuse avec une erreur (également signalée via une notification de bureau) et vous demande de quitter Firefox et de le relancer — ce qui démarre un nouveau conteneur sur l'image actuelle. Sous `--rebuild` avec 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. La mise à jour automatique intégrée du navigateur est désactivée, car les mises à jour sont gérées au niveau de l'image.
Si la vérification des mises à jour échoue (pas de réseau, délai d'expiration de l'API), un avertissement est affiché et l'image existante est utilisée — vous pouvez toujours naviguer.
La cadence des mises à jour se trouve au niveau supérieur de la configuration ; l'épinglage de la version et du 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)
```
**Épingler une version ESR nécessite aussi 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 renvoie vers une version qui n'existe pas. Définissez les deux :```toml
[firefox]
channel = "esr"
version = "140" # → 140.13.0esr
```
Une épingle qui ne correspond à aucune version est désormais une erreur nommant l’épingle, plutôt qu’un repli silencieux vers la dernière version. Un échec temporaire à joindre 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 épingles suffixées doivent être entièrement qualifiées — `"140.13.0esr"` et `"150.0b9"` fonctionnent, `"140esr"` et `"150b9"` sont rejetées 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`
### Clés de signature révoquées
foxcage refuse d’installer une version du navigateur dont la signature a été réalisée par une sous-clé de signature que le projet en amont a révoquée comme **compromise** (motif de révocation RFC 4880 `0x02`). `gpg --verify` ne fait pas cela de lui-même : il affiche un avertissement et se termine avec le code 0, donc sans la vérification supplémentaire, une clé de signature divulguée authentifierait toujours un téléchargement falsifié.
Un refus ressemble à ceci, et fait échouer la construction plutôt que d’installer :```
foxcage: REFUSING /tmp/SHA512SUMS - signed by 09BEED63F3462A2DFFAB3B875ECB6497C1A20256,
which its owner revoked as compromised. This build cannot be trusted; wait for
upstream to re-sign this release with a current key.
```
Il n'y a rien à configurer et aucun remplacement possible. Si vous rencontrez ce problème, le correctif vient de l'amont : soit épingler une version signée avec une clé actuelle, soit attendre que la version concernée soit re-signée.
La rotation régulière des clés est traitée différemment. Une sous-clé révoquée comme remplacée, retirée, ou sans raison indiquée n'invalide pas les signatures effectuées *avant* la révocation, donc ces versions s'installent avec un avertissement. Une signature datée *après* toute révocation est refusée quelle que soit la raison indiquée.
**Rotation de clé de Mozilla d'août 2026.** Mozilla a révoqué la sous-clé de signature `09BEED63…C1A20256` le 2026-08-06 après qu'une copie non chiffrée a été commitée dans un dépôt GitHub privé, et l'a remplacée par `827E6586…76767AA3`. Les versions de Firefox signées avec l'ancienne sous-clé — tout ce qui se situe entre le 2025-03-13 et le 2026-08-06, ce qui inclut au moment de la rédaction encore l'ESR actuelle (`140.13.0esr`) et tout épinglage `version` dans cette fenêtre — sont refusées par la vérification ci-dessus. Les canaux Release et Beta ne sont pas affectés. foxcage récupère les clés depuis `keys.openpgp.org` plutôt que `keyserver.ubuntu.com` car ce dernier ne servait ni la sous-clé de remplacement ni la révocation pendant des jours après la rotation ; un serveur de clés obsolète casserait les builds directement et réduirait silencieusement la vérification de révocation à une opération sans effet.
### Forks de Firefox (LibreWolf)
foxcage peut exécuter un fork de Firefox orienté vie privée à la place de Firefox 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 contre la clé des mainteneurs LibreWolf `662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16` avec un contrôle croisé `.sha256sum` associé, sous les mêmes [règles de révocation](#revoked-signing-keys) que Firefox. Le `librewolf.cfg` fourni par LibreWolf est conservé ; foxcage ajoute ses propres préférences par-dessus sans les écraser. Les mainteneurs ont fait pivoter leur sous-clé de signature le 2026-04-25 sans en donner la raison ; les archives actuelles ont été signées avant cette date, elles s'installent donc avec un avertissement plutôt que d'être refusées.
**Le canal est réservé à Firefox** : `firefox.channel = "beta" | "esr"` est rejeté lorsque `fork` est autre chose que `"firefox"`. LibreWolf ne dispose que d'une seule piste de versions.
Changer `fork` (via la config ou `--fork`) modifie le hash du Containerfile, ce qui déclenche une reconstruction au prochain lancement — aucun `--rebuild` manuel nécessaire.
#### Compatibilité des profils
> **Utilisez un profil dédié par fork.** Le plus sûr est de laisser foxcage provisionner son propre profil (omettez `profile` de la config), ou de pointer `profile` vers un répertoire que vous n'ouvrez pas non plus 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.ini` sont 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) ne fonctionnent pas silencieusement mais ne corrompent pas les données.
### Cages nommées
Exécutez des instances sandboxées séparées avec leur propre config et profil Firefox :```sh
./foxcage @work
```
Ceci charge `~/.config/foxcage/work.toml` et utilise une image séparée (`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 tirets 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 (optionnel, des valeurs par défaut raisonnables s’appliquent sans lui)
- `<name>.toml` — cage nommée, chargée avec `@<name>` (requis)
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 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"
# Extensions pre-installed into the cage (addons.mozilla.org short name,
# listing URL, or add-on ID). Most useful in tmp.toml.
# extensions = ["ublock-origin"]
# 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 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 souhaitez 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 monté en bind dans la cage. Les profils frères 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 » ci-dessous). 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 la valeur par défaut sans root 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 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 abstraits sont limités à 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/X0` de Xwayland si vous exécutez X11 ou Xwayland (journalisation des entrées, malgré le fait que foxcage soit uniquement Wayland), ainsi qu'un bus de session configuré avec `unix: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
```
Or au lancement avec le drapeau `--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`), donc le conteneur n'a aucune pile IPv6, et définit en plus `network.dns.disableIPv6` dans Firefox pour qu'il ne résolve pas les enregistrements AAAA — ce qui compte 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"` — la mise en réseau hôte utilise directement la pile réseau de l'hôte, donc désactivez IPv6 sur l'hôte à la place.
### Commandes d'initialisation
Exécutez des commandes personnalisées au moment de la construction ou au démarrage du conteneur via `[init]` :
- **`build`** — s'exécute au moment de la construction de l'image en tant que root. À utiliser pour installer des paquets ou d'autres configurations lentes. 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"]
```
Toutes les trois clés sont des listes de chaînes de commandes shell. Si une commande échoue, le conteneur se termine sans démarrer Firefox.
**Note 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 redescendre vers l'utilisateur standard. Ces capacités ne sont détenues 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 de capacités minimal par défaut.
## Ce qui est conservé
Sans configuration, un volume Podman nommé stocke le profil Firefox (marque-pages, paramètres, extensions, plugin DRM Widevine). 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 en bind et aucun volume n'est créé.
Les extensions listées dans `extensions` ne font pas partie de cet état : elles vivent dans l'image et sont réinstallées à chaque lancement, donc la suppression du volume (ou l'utilisation d'une cage éphémère, qui n'en possède pas) ne les perd pas.
### 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 elle en tant qu'entrée `<none>` non étiquetée, donc foxcage supprime l'image qu'il vient de déplacer après chaque build réussi. Il ne supprime que cette image spécifique, et jamais celle qu'une cage en cours d'exécution utilise encore.
Les images orphelines antérieures à 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 depuis l'hôte, afin que Firefox dans le conteneur ressemble et se comporte comme une application native :
- **Polices.** Les polices système (`/usr/share/fonts`) et les polices utilisateur (`~/.local/share/fonts`) sont montées en lecture seule. La configuration des polices de `~/.config/fontconfig` est également transmise.
- **Thème GTK et mode sombre.** Détectés via `GTK_THEME` ou `gsettings` et transmis au conteneur. La configuration GTK de `~/.config/gtk-3.0` et `~/.config/gtk-4.0` est montée en lecture seule.
- **Fuseau horaire.** Le nom du fuseau horaire de l'hôte (détecté à partir de `TZ`, du lien symbolique `/etc/localtime` ou de `/etc/timezone`) est transmis dans le conteneur comme `TZ`, et `/etc/localtime` est monté en lecture seule. Les deux sont nécessaires : Firefox dérive le fuseau horaire JavaScript du *nom* de la zone, pas du contenu du fichier — sans `TZ`, les sites web afficheraient les heures en UTC.
- **Locale.** `LANG` est transmis. La locale de l'hôte est générée dans l'image du conteneur au moment de la construction.
**Étiquette de la cage.** La barre de menus de Firefox affiche « FoxCage » (ou « FoxCage - nom » pour les cages nommées) afin que vous puissiez voir 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 directement. Sur KDE ou d'autres bureaux, Firefox reviendra à Adwaita si votre thème GTK (par exemple 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`.
## Décodage vidéo matériel (VA-API)
Le conteneur possède son propre espace utilisateur, donc les pilotes VA-API installés sur l'hôte sont
sans importance — l'image embarque les siens. `va-driver-all` tire `i965-va-driver` (Intel
plus ancien) et `mesa-va-drivers` (AMD, nouveau), aux côtés de `intel-media-va-driver-non-free`
(Intel Gen8+, le pilote iHD) et `libva2`/`libva-drm2`, que Firefox charge à l'exécution.
Le décodage matériel nécessite le passage de `/dev/dri`, ce que foxcage fait automatiquement
dès que l'hôte en dispose. Rien à configurer.
Le pilote Intel est la version **non-free**, donc l'image active le composant `non-free`
de Debian. Le `intel-media-va-driver` libre de Debian est un reconditionnement `+dfsg` avec les
noyaux de codecs non redistribuables supprimés, et ce qu'il perd, c'est le décodage AV1 — le format
que YouTube sert désormais par défaut. La version libre laisserait l'AV1 retomber sur le logiciel
sur chaque machine Intel.
Pour vérifier que cela fonctionne réellement, exécutez `vainfo` dans une cage en direct :```bash
podman exec foxcage-<name> vainfo
```
Il doit lister le pilote utilisé (`iHD` sur Intel, `radeonsi` sur AMD) et les profils
pris en charge — `VAProfileH264*`, `VAProfileVP9Profile0`, `VAProfileAV1Profile0`, etc.
La vérification équivalente depuis le navigateur est `about:support` → Média, où la
colonne Décodage matériel doit afficher `Supported` pour H264, VP8, VP9, HEVC et AV1. Avec
une vidéo en cours de lecture, `intel_gpu_top` sur l'hôte montre une activité sur le moteur Vidéo.
L'**encodage** matériel est distinct, et sur Intel il provient du même pilote : H264 et
HEVC doivent afficher `Supported` dans cette colonne, ce que WebRTC utilise pour le flux
sortant de la caméra lors des appels vidéo et ce que `MediaRecorder` utilise. L'encodage VP8, VP9 et AV1
reste `Unsupported` — Firefox ne connecte que les encodeurs VA-API H264 et HEVC, quelle que soit
la capacité du GPU.
Les codecs audio (AAC, MP3, Opus, Vorbis, FLAC, Wave) affichent `Unsupported` sous Décodage
matériel sur toutes les machines — aucun GPU grand public ne possède de bloc de décodage audio. Cette ligne n'est
pas une mauvaise configuration.
Si `vainfo` signale `failed to initialize display`, le conteneur ne peut pas ouvrir
`/dev/dri/renderD128`. Sur un bureau systemd normal, logind accorde à votre utilisateur une ACL sur
ce périphérique, donc cela signifie généralement que foxcage est exécuté depuis une session qui ne
possède pas le siège (SSH, un autre TTY).
## DRM (Netflix, Disney+, etc.)
Le DRM Widevine fonctionne immédiatement. Lors de la première visite sur 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 XDG Desktop et au démon de notifications de l'hôte. Ces fonctionnalités sont sûres car tout accès est médiatisé 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éversements 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 notifications 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 options 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 du 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 des plateformes 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 un accès à `/dev/input/`. Le Web MIDI nécessite un accès au séquenceur ALSA. Aucun des deux n'est transmis.
**Installation de PWA.** Les Progressive Web Apps 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 toutefois : `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
Podman sans root peut basculer sur le pilote de stockage `vfs`, qui copie des couches d'image entières au lieu d'utiliser des montages en overlay. Cela rend le démarrage du conteneur après une construction beaucoup plus lent. Vérifiez quel pilote vous avez :```sh
podman info --format '{{.Store.GraphDriverName}} {{.Store.GraphStatus}}'
```
`overlay` avec `Native Overlay Diff:true` est le chemin rapide et ne nécessite aucune configuration — sur un noyau 5.13 ou plus récent avec un système de fichiers de support ext4/xfs, Podman utilise directement overlayfs non privilégié. Si c'est ce que vous voyez, il n'y a rien à faire, et installer `fuse-overlayfs` ne sera d'aucune aide.
Uniquement si vous tombez sur `vfs` (ancien noyau, ou un système de fichiers de support qui ne peut pas faire d'overlay non privilégié), installez `fuse-overlayfs` et ajoutez à `~/.config/containers/storage.conf` :```toml
[storage]
driver = "overlay"
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
```
Ceci est un repli, pas une amélioration : FUSE fait transiter chaque opération du système de fichiers par l'espace utilisateur et est plus lent que l'overlay natif. Définir `mount_program` sur un système prenant en charge l'overlay natif aggrave les choses, pas l'inverse.
### Définir foxcage comme navigateur par défaut
Tout d'abord, assurez-vous que le script `foxcage` se trouve à 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 par la suite cassera le lanceur.
Exécutez ensuite :```sh
foxcage --install
```
Cela 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 en forme de roue pendant le démarrage de foxcage. Lorsqu'une construction d'image est nécessaire (ce qui peut prendre plusieurs minutes), foxcage envoie une notification de bureau pour vous informer 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 via une notification de bureau afin que les utilisateurs lançant depuis le bureau ne restent pas à regarder un écran vide lorsque foxcage échoue sans terminal attaché. Les deux nécessitent `notify-send` (provenant de `libnotify-bin` sur Debian/Ubuntu) — s'il n'est pas installé, les notifications sont silencieusement ignorées et l'erreur est toujours envoyée sur 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 au-delà de `tmp_path` de pytest. La suite impose **100 % de couverture de lignes et de branches** (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. CI exécute la suite à chaque push via `.gitlab-ci.yml`.
## Remerciements
Ce projet a été développé par Mike Cardwell, avec l'aide de [Claude Code](https://claude.ai/claude-code), l'outil de codage IA d'Anthropic.
## Soutenir/Apprécier mon travail
- [Bitcoin](bitcoin:1PQLtWnjUi1itHLG6QCQeHM3Nxua8pRsq1) : 1PQLtWnjUi1itHLG6QCQeHM3Nxua8pRsq1
- [Paypal](https://www.paypal.me/grepular)