Zurück zu den Updates
UpdatedAug 8, 2026

foxcage — Updated!

Firefox in einem rootless Podman-Container mit entfernten Capabilities, isoliertem Netzwerk und ephemerem Speicher ausführen, um Sandbox-Escapes einzudämmen und eine Kompromittierung des Hosts zu verhindern.

Teilen

foxcage-Symbol foxcage

Führen Sie Firefox in einem rootless Podman-Container zur Sicherheitsisolierung aus. Ihr Browser läuft mit nahezu keinen Linux-Capabilities, in seinem eigenen User- und Network-Namespace, isoliert vom Host — und hat dennoch volle GPU-Beschleunigung, Audio- und DRM-Unterstützung.

Warum foxcage?

Firefox hat bereits eine Multiprozess-Sandbox, die Web-Content-Renderer mithilfe von Linux-Namespaces und seccomp-bpf isoliert. Für die meisten Bedrohungen ist das effektiv. foxcage fügt eine zweite Mauer hinzu: Wenn ein Angreifer eine Schwachstelle ausnutzt, die Firefox-Sandbox entkommt (das passiert — dafür gibt es CVEs), landet er in einem abgeriegelten Container statt in Ihrer vollständigen Benutzersitzung.

Wovor foxcage schützt

  • Post-Exploit-Dateizugriff. Ein Sandbox-Escape bei nacktem Firefox gewährt Zugriff auf alles, was Ihr Benutzer lesen kann: ~/.ssh, ~/.gnupg, Browserprofile anderer Browser, Passwortmanager-Datenbanken, Dokumente, Quellcode. Bei foxcage sieht der Angreifer nur das, was Sie explizit eingebunden haben.
  • Tracking-Rückstände auf der Platte. Der ephemere @tmp-Käfig hinterlässt nach dem Schließen des Fensters keine Spur auf der Platte — einschließlich Erweiterungen, HSTS-Zustand, TLS-Sitzungscache und DNS-Cache, die der Private-Modus von Firefox weiterhin speichert. Mehrere @tmp-Käfige laufen gleichzeitig, ohne sich gegenseitig zu stören.
  • Persistenz. Bei nacktem Firefox kann Malware in ~/.config/autostart, ~/.bashrc, Cron oder anderswo schreiben, um einen Neustart zu überleben. Der ephemere Container von foxcage (--rm) bedeutet, dass nichts persistent bleibt, es sei denn, Sie haben es per Bind-Mount eingebunden.
  • Laterale Netzwerkbewegung. Standardmäßig kann der Container keine Dienste auf localhost abfragen. Bei nacktem Firefox hat ein Sandbox-Escape vollen Netzwerkzugriff. (Verwenden Sie [network] mode = "host", wenn ein Käfig Localhost-Zugriff benötigt, z. B. für lokale Entwicklung — aber beachten Sie den Hinweis unter „Netzwerk“: Der Host-Modus legt auch die abstrakten Unix-Sockets des Hosts offen.)
  • Privilege Escalation. Der Container verwirft alle Linux-Capabilities außer CAP_SYS_CHROOT und blockiert den Erwerb neuer Privilegien. Setuid-Binaries, Kernel-Exploits über obskure Syscalls und ähnliche Eskalationspfade sind abgeschnitten.

Wovor foxcage nicht schützt

  • Angriffe auf Browser-Ebene. Phishing, bösartige Erweiterungen und alles, was innerhalb der normalen Firefox-Funktionalität operiert, bleibt unberührt — foxcage isoliert den Container vom Host, nicht den Benutzer vom Browser.
  • Bind-gemountete Verzeichnisse. Alles, was Sie einbinden (profile, downloads_dir, zusätzliche Bind-Mounts), ist für einen kompromittierten Browser voll zugänglich. Wenn Sie ein Host-Profilverzeichnis mounten, kann ein Angreifer es genauso manipulieren wie bei nacktem Firefox.
  • Audioaufnahme über PulseAudio. Der PulseAudio-Socket ist in den Container bind-gemountet. Obwohl er auf Dateisystemebene schreibgeschützt gemountet ist, sind Unix-Domain-Sockets bidirektional — ein kompromittierter Prozess kann weiterhin Aufnahmeanfragen über den Socket senden. Ein Browser-Sandbox-Escape könnte potenziell Audio vom Host-Mikrofon aufnehmen.
  • Wayland-Compositor-Exploits. Der Wayland-Socket wird durchgereicht. Wayland-Compositors isolieren Clients standardmäßig voneinander, aber eine Schwachstelle im Compositor selbst wäre erreichbar.

Sicherheitskonfiguration

Der Container läuft mit:

  • Alle Linux-Capabilities entfernt (nur CAP_SYS_CHROOT für die Content-Sandbox von Firefox wieder hinzugefügt; CAP_SETUID/CAP_SETGID vorübergehend hinzugefügt, wenn init.root konfiguriert ist)
  • no-new-privileges, um Privilege Escalation zu verhindern
  • Rootless-User-Namespace (--userns keep-id)
  • Privates /dev/shm (nicht mit Host geteilt) — konfigurierbare Größe über shm_size
  • Isolierte Netzwerkverbindung über pasta mit standardmäßig blockiertem Host-Loopback
  • DNS verwendet standardmäßig den Host-DNS (konfigurierbar über network.dns)
  • Nur bestimmte Sockets aus XDG_RUNTIME_DIR werden eingebunden (Wayland, PulseAudio, PipeWire und der gefilterte D-Bus-Proxy) — das vollständige Host-Runtime-Verzeichnis wird nie exponiert
  • Der Zugriff auf den D-Bus-Session-Bus des Hosts wird immer durch einen gefilterten xdg-dbus-proxy vermittelt, der auf dem Host läuft. Nur org.freedesktop.Notifications, org.freedesktop.portal.Desktop, org.mozilla.* und (bei Forks) der eigene Namespace des Forks (z. B. org.librewolf.*) sind erreichbar — Sitzungsdienste wie der Keyring und der SSH/GPG-Agent sind blockiert
  • Portalzugriff ist weit gefasst. org.freedesktop.portal.Desktop ist als Ganzes erlaubt, denn darüber funktionieren die Dateiauswahl, „Link in anderer App öffnen“ und Bildschirmfreigabe. Es legt auch RemoteDesktop (synthetische Tastatur/Maus für die gesamte Sitzung), Camera und Location offen. Diese sind durch die eigenen Genehmigungsdialoge Ihres Desktops abgesichert, nicht durch foxcage — und die RemoteDesktop-Eingabeaufforderung ähnelt der Bildschirmfreigabe-Aufforderung. Lesen Sie daher die Genehmigungsdialoge, bevor Sie sie akzeptieren. xdg-dbus-proxy hat keine „eine Schnittstelle verbieten“-Regel, daher bedeutet eine Eingrenzung, jede Schnittstelle aufzuzählen, die Firefox benötigt; siehe docs/DESIGN.md für die Begründung, warum das nicht standardmäßig geschieht
  • Alle Bind-Mounts (profile, downloads_dir, zusätzliche [mounts] bind) verwenden nosuid,noexec
  • Browser-Downloads werden gegen GPG-Signaturen verifiziert: Firefox gegen Mozillas signierte SHA-512-Checksummen, LibreWolf gegen die separate Signatur der LibreWolf-Maintainer sowie die zugehörigen SHA-256-Summen. Die Verifikation ist strenger als gpg --verify, das für eine Signatur mit einem widerrufenen Schlüssel und für jeden Schlüssel im Schlüsselbund mit Exit-Code 0 endet. foxcage verlangt zusätzlich, dass die Signatur bis zum festgelegten Primärschlüssel führt, und verweigert jede Veröffentlichung, die von einem Subkey signiert wurde, dessen Inhaber ihn als kompromittiert widerrufen hat — siehe Widerrufene Signaturschlüssel
  • Ephemerer Container (--rm) — Dateisystemschreibvorgänge gehen beim Beenden verloren
  • Keine Host-Geräte (Webcam, Sicherheitsschlüssel, Drucker) durchgereicht, sofern nicht explizit aktiviert

Jede Option unter [network] und [mounts], die Sie aktivieren, tauscht etwas Isolierung gegen Bequemlichkeit. Die Standardwerte sind die restriktivste Konfiguration, die Ihnen dennoch einen brauchbaren Browser bietet.

Anforderungen

  • Python 3.11+
  • Podman (rootless)
  • Wayland-Compositor (X11 wird nicht unterstützt)
  • pasta (sudo apt install passt) — außer bei network.mode = "host"
  • xdg-dbus-proxy (sudo apt install xdg-dbus-proxy)
  • PulseAudio oder PipeWire mit PulseAudio-Kompatibilität (für Audio)
  • GPU mit DRI-Unterstützung — optional; ohne /dev/dri warnt foxcage und Firefox rendert in Software

Führen Sie foxcage als Ihren normalen Desktop-Benutzer aus, nicht als root oder über sudo — die Sandbox bildet Ihren Benutzer in den Container ab, und die Ausführung als root entfernt die Isolierung, die foxcage bieten soll. Es weigert sich, als root zu starten.

Getestete Umgebung: Debian 13 (Trixie) mit GNOME 3. Andere Linux-Distributionen und Wayland-Compositors funktionieren möglicherweise, wurden aber nicht getestet.

Installation

foxcage ist ein einzelnes Python-Skript ohne Abhängigkeiten außerhalb der Python-Standardbibliothek. Kopieren Sie es in ein Verzeichnis in Ihrem PATH:```sh sudo cp foxcage /usr/local/bin/foxcage

Oder für eine benutzerlokale Installation:```sh
cp foxcage ~/.local/bin/foxcage

Stellen Sie sicher, dass das Skript ausführbar ist (chmod +x foxcage).

Überprüfen Sie mit foxcage --version, welche Revision Sie haben — nützlich, wenn Sie ein Problem melden, da foxcage durch Kopieren einer einzelnen Datei installiert wird.

Verwendung```sh

./foxcage

Beim ersten Start erstellt das Skript das Container-Image (lädt Firefox von Mozilla herunter, installiert minimale Debian-Abhängigkeiten) und startet dann Firefox. Bei späteren Ausführungen prüft foxcage auf Firefox-Updates und baut das Image automatisch neu, sobald eine neue Version verfügbar ist. Das Image wird außerdem regelmäßig neu erstellt (standardmäßig alle 7 Tage), um Systempaket-Updates zu übernehmen. Wenn die Update-Prüfung fehlschlägt (Netzwerkfehler, Zeitüberschreitung), wird eine Warnung protokolliert und das vorhandene Image verwendet — der Start wird nie blockiert.

Argumente an Firefox durchreichen:```sh
./foxcage https://example.com

Kombiniere einen benannten Käfig mit Firefox-Flags:```sh ./foxcage @work --kiosk https://example.com

Wenn ein Käfig bereits läuft, wird die URL in einem neuen Tab im vorhandenen Browser geöffnet, anstatt einen zweiten Container zu starten. Die Ausführung von `foxcage` (oder `foxcage @cage`) ohne URL gegen einen laufenden Käfig endet sauber mit einer Meldung "cage is already running" — foxcage kann ein vorhandenes Wayland-Fenster nicht von außerhalb des Containers in den Vordergrund holen, daher versucht es das auch nicht.

Die Flags pro Aufruf gelten **nicht**, wenn ein Käfig bereits läuft. `--dns`, `--ipv4-only`, `--lifetime`, `--color` und `--fork` werden beim Start des Containers verbraucht, und die Einstellungen eines laufenden Containers können von außen nicht geändert werden, daher werden sie mit einer Warnung ignoriert. Schließe den Käfig und führe den Befehl erneut aus, um sie anzuwenden.

> Verwende den Konfigurationsschlüssel `private_browsing` für Sitzungen im privaten Modus — *nicht* das rohe `--private-window`-CLI-Flag von Firefox. Der Konfigurationsschlüssel aktiviert den sitzungsweiten privaten Modus (`browser.privatebrowsing.autostart`), sodass spätere `foxcage @cage URL`-Aufrufe in Tabs wieder geöffnet werden können. `--private-window` als Firefox-Durchreichung würde nur das erste Fenster privat machen und das oben beschriebene Verhalten zum Wiederöffnen in Tabs brechen.
>
> **Achtung:** Sitzungen, die über `private_browsing = true` aktiviert werden, zeigen nicht die üblichen UI-Hinweise des privaten Fensters von Firefox (lila Akzentleiste, Maskensymbol, „(Private Browsing)“ im Titel). Das liegt daran, dass jedes Fenster in der Sitzung privat ist und Firefox daher kein nicht-privates Fenster hat, um visuell dagegen abzusetzen — es unterdrückt den Hinweis. Die Sitzung *ist* tatsächlich privat; wenn du es überprüfen möchtest, besuche `about:privatebrowsing` im Käfig (zeigt die Standard-Informationsseite für den privaten Modus) oder `about:config` und prüfe `browser.privatebrowsing.autostart = true`.

### Ephemeres Browsen mit `@tmp`

Für einmalige Links, die keine Spuren hinterlassen sollen, verwende den reservierten Käfig `tmp`:```sh
./foxcage @tmp https://somewhere-suspicious.example

Jeder @tmp-Start ist ein frischer, wegwerfbarer Firefox ohne persistentes Profil. Wenn das Fenster geschlossen wird, ist alles weg — Cookies, Cache, Verlauf, Erweiterungen, HSTS-Status, TLS-Sitzungscache, DNS-Cache, gespeicherter Tab-Zustand. Das geht weiter als Firefox Private Browsing, das weiterhin Erweiterungen und einiges an On-Disk-Zustand beibehält.

Mehrere @tmp-Käfige laufen gleichzeitig, jeder von den anderen isoliert. Die Menüleiste zeigt FoxCage - tmp (<kurze ID>), damit du gleichzeitige ephemere Fenster voneinander unterscheiden kannst.

Ephemere Käfige öffnen beim Start eine leere Seite und leere neue Tabs — die standardmäßige Firefox-Startseite und der Inhalt neuer Tabs (Top-Sites, Pocket-Empfehlungen, Activity Stream) sind bei einem frischen Profil, das ohnehin weggeworfen wird, reines Rauschen, daher werden sie unterdrückt. Persistente Käfige behalten die Firefox-Standardeinstellungen bei.

Benannte ephemere Käfige

Wenn du einer Wegwerf-Sitzung einen aussagekräftigen Namen geben möchtest (sagen wir, ein Recherche-Kaninchenloch, das du in einem neuen Tab wieder öffnen möchtest), verwende @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>` ist weiterhin ephemeral – wenn du das Fenster schließt, geht alles verloren. Der Unterschied zu bloßem `@tmp` besteht darin, dass ein zweiter Start mit demselben Namen **das vorhandene Fenster wiederverwendet** (wie bei persistenten Käfigen), sodass du später weitere Tabs hinzufügen kannst, ohne eine parallele Kopie zu starten. Bloßes `@tmp` behält sein Verhalten „jeder Start ein frischer Wegwerf-Käfig“.

Die Menüleisten-Beschriftung zeigt den gewählten Namen (`FoxCage - tmp-research`), sodass das Fenster aussagekräftig beschriftet ist.

#### Ephemere Standardwerte anpassen

Erstelle `~/.config/foxcage/tmp.toml`, um Standardwerte für alle ephemeren Käfige festzulegen (sowohl für bloßes `@tmp` als auch für jedes `@tmp-<name>`). Zum Beispiel:```toml
private_browsing = true
lifetime = "30m"

[network]
dns = "cloudflare"

Jeder ephemere Start erhält jetzt ein privates Fenster, Cloudflare DoH und schließt sich nach 30 Minuten automatisch – bei vollständig erhaltener Ephemeralität. Benannte ephemere Instanzen erben standardmäßig tmp.toml; wenn Sie diese pro Name überschreiben möchten, erstellen Sie ~/.config/foxcage/tmp-<name>.toml. Diese Datei gilt dann anstelle von tmp.toml – keine Zusammenführung, die spezifischere Datei gewinnt eindeutig. Kopieren Sie gemeinsame Standardeinstellungen hinein, falls Sie sie benötigen.

Alles, was Sie in der Konfiguration eines regulären Käfigs festlegen können, funktioniert auch hier, außer dem einen Schlüssel, der die Ephemeralität selbst zunichtemachen würde:

  • profile – harter Fehler.

Er verweist auf ein persistentes Profilverzeichnis auf dem Host, was dem Zweck von @tmp direkt widerspricht. Wenn Sie einen Sandbox-Käfig mit einem persistenten Profil möchten, verwenden Sie einen regulären benannten Käfig (@work, @research, etc.), der nicht mit tmp- beginnt.

DNS pro Start überschreiben

Das --dns-Flag (und der entsprechende Konfigurationsschlüssel network.dns) akzeptiert drei Formen:```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

**Wenn der Wert mit einem bekannten Anbieter übereinstimmt (per Alias oder IP), aktiviert foxcage automatisch erzwungenes DNS über HTTPS für diesen Anbieter.** Der TRR von Firefox wird auf Modus 3 gesetzt (strikt, kein Klartext-Fallback), wobei die Bootstrap-Adresse ausgefüllt ist, damit beim Start kein unverschlüsseltes Auflösungsleck entsteht. Auf stderr erscheint eine einzeilige Meldung wie `Enabling DNS over HTTPS via Cloudflare`.

Integrierte Aliase:

| Alias | IP | Filterung |
|-------|------|-----------|
| `cloudflare` | 1.1.1.1 | keine |
| `cloudflare-security` | 1.1.1.2 | blockiert Schadsoftware |
| `cloudflare-family` | 1.1.1.3 | blockiert Schadsoftware + Erwachseneninhalte |
| `google` | 8.8.8.8 | keine |
| `quad9` | 9.9.9.9 | blockiert Schadsoftware (Quad9-Standard) |
| `quad9-unfiltered` | 9.9.9.10 | keine |
| `adguard` | 94.140.14.14 | blockiert Werbung + Tracker |
| `adguard-family` | 94.140.14.15 | Werbung + Tracker + Erwachseneninhalte |
| `opendns` | 208.67.222.222 | teilweise |

Eine IP, die nicht in der Tabelle steht (z.B. dein Pi-hole im LAN), bleibt reiner Klartext — es wird kein DoH aktiviert, da foxcage den zugehörigen DoH-Endpunkt nicht kennt. Verwende dafür die URI-Form: `--dns https://pi.hole/dns-query` (mit einem gültigen Zertifikat) aktiviert DoH und lässt das Container-DNS unangetastet.

Die URI-Form überspringt das Setzen des Klartext-DNS des Containers, sodass alles im Container, das nicht Firefox ist, weiterhin das DNS des Hosts verwendet. Das ist beabsichtigt — `--dns URI` bedeutet „Firefox diesen DoH-Resolver verwenden lassen“, Punkt.

`--dns` ist inkompatibel mit `network.mode = "host"`, das bereits vollen Host-Netzwerkzugriff hat.

### Visuelle Käfig-Identifizierung

Jeder benannte Käfig erhält eine Akzentfarbe in der Menüleiste, damit du Fenster auf einen Blick unterscheiden kannst. **Du musst nichts konfigurieren** — die Farbe wird deterministisch aus dem Käfignamen abgeleitet (SHA256-gehasht in einen Farbton, mit fester Sättigung und Helligkeit). `@banking`, `@work`, `@personal`, `@tmp-research` erhalten alle unterschiedliche, stabile Farben, ohne dass du einen Finger rühren musst.

Der Standardkäfig (anonym) behält das fest eingebaute Orange.

Wenn du die automatisch abgeleitete Farbe überschreiben möchtest, setze sie explizit:```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626"   # red — overrides the auto-derived colour

I don't see any content to translate — the input after "INPUT:" is empty. Please provide the actual Markdown chunk text for chunk 21/75, and I'll translate it into German.```sh ./foxcage @experiment --color "#10b981" https://example.com # teal, one-off

Akzeptiert standardmäßige CSS-Hex-Farbwerte: `#rgb`, `#rrggbb` oder `#rrggbbaa` (mit Alpha). Die automatisch abgeleiteten Farben sind so abgestimmt, dass sie auf hellen und dunklen Menüleisten sichtbar sind (Helligkeit fest bei 55 %, Sättigung bei 75 %), daher solltest du sie nicht aus Designgründen überschreiben müssen.

### Zeitlich begrenzte Cages

Das `--lifetime`-Flag (und der entsprechende `lifetime`-Konfigurationsschlüssel) schließt einen Cage nach einer festgelegten Dauer automatisch. Das Format ist `<number><unit>` mit der Einheit `s`, `m` oder `h`:```sh
./foxcage @tmp --lifetime 10m https://example.com
./foxcage @work --lifetime 2h

Der Countdown startet, wenn Firefox tatsächlich innerhalb der Cage startet – die Container-Startzeit und die Image-Build-Zeit gehen nicht von deinem Budget ab. Das Label in der Menüleiste der Cage zeigt den Countdown zusammen mit der Cage-Identität an – z. B. FoxCage - tmp (a3f2b1) | 9m – aktualisiert einmal pro Minute, solange mehr als eine Minute übrig ist, und einmal pro Sekunde in der letzten Minute. Wenn der Countdown Null erreicht, schließt sich Firefox selbst und der Container beendet sich. Wenn du Firefox selbst schließt, bevor die Lebensdauer abgelaufen ist, passiert nichts Ungewöhnliches.

Lege eine Standard-Lebensdauer pro Cage in dessen Konfiguration fest:```toml

~/.config/foxcage/tmp.toml — every @tmp launch auto-closes after 15 minutes

lifetime = "15m" private_browsing = true

`--lifetime` auf der Kommandozeile hat Vorrang vor jedem Konfigurationswert.

Erzwingen Sie einen vollständigen Image-Neuaufbau (Firefox und alle Systempakete werden erneut heruntergeladen):```sh
./foxcage --rebuild

Ein laufender Container behält das Image, mit dem er gestartet wurde, auch nachdem foxcage das Image-Tag neu erstellt. Wenn Sie versuchen, einen Tab in einer Cage zu öffnen, deren Image inzwischen aktualisiert wurde (durch --rebuild, ein Firefox-Update oder den geplanten Neubau), verweigert foxcage mit einem Fehler (der auch als Desktop-Benachrichtigung angezeigt wird) und fordert Sie auf, Firefox zu beenden und neu zu starten — das startet einen frischen Container auf dem aktuellen Image. Unter --rebuild mit einer aktiven Cage warnt foxcage im Voraus, führt den Build aus und wendet dann dieselbe Prüfung an.

Updates

foxcage prüft bei jedem Start auf neue Browser-Versionen — Mozillas Release-API für Firefox, GitLabs Releases-Endpunkt für LibreWolf. Wenn ein Update verfügbar ist, wird das Container-Image automatisch neu erstellt. Das Image wird außerdem regelmäßig neu erstellt (standardmäßig alle 7 Tage), um Debian-Sicherheitsupdates zu übernehmen. Der integrierte Auto-Updater des Browsers ist deaktiviert, da Updates auf Image-Ebene behandelt werden.

Wenn die Update-Prüfung fehlschlägt (kein Netzwerk, API-Timeout), wird eine Warnung ausgegeben und das vorhandene Image verwendet — Sie können immer browsen.

Die Update-Häufigkeit befindet sich auf der obersten Ebene der Konfiguration; Versions- und Kanalfestlegung liegen im Abschnitt pro 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)

**Das Pinnen einer ESR-Version erfordert ebenfalls den Kanal.** Mozillas Versionsindex listet ESR-Releases ohne das `esr`-Suffix auf, das ihre Downloads tragen, daher ergibt eine bloße `version = "140"` im Standardkanal eine Version, die es nicht gibt. Geben Sie beides an:```toml
[firefox]
channel = "esr"
version = "140"        # → 140.13.0esr

Ein Pin, der zu keiner Version passt, ist jetzt ein Fehler, der den Pin benennt, anstatt stillschweigend auf die neueste Version zurückzufallen. Ein vorübergehender Fehler beim Erreichen der Mozilla-API erzeugt weiterhin nur eine Warnung und fährt mit dem vorhandenen Image fort, sodass ein unzuverlässiges Netzwerk den Start nie blockiert.

Pins mit Suffix müssen vollständig qualifiziert sein — "140.13.0esr" und "150.0b9" funktionieren, "140esr" und "150b9" werden beim Laden der Konfiguration abgelehnt, da keine Version sie jemals erfüllen kann. Gleiches gilt für LibreWolf-Revisionen: "146.0.1-1" funktioniert, "146-1" nicht.

Um sofort einen vollständigen Neuaufbau zu erzwingen: ./foxcage --rebuild

Widerrufene Signaturschlüssel

foxcage weigert sich, einen Browser-Build zu installieren, dessen Signatur von einem Signatur-Unterschlüssel erstellt wurde, den das Upstream-Projekt als kompromittiert widerrufen hat (RFC-4880-Widerrufsgrund 0x02). gpg --verify tut dies nicht von sich aus: Es gibt eine Warnung aus und beendet sich mit 0, sodass ohne die zusätzliche Prüfung ein geleakter Signaturschlüssel einen manipulierten Download weiterhin authentifizieren würde.

Eine Ablehnung sieht so aus und schlägt den Build fehl, anstatt zu installieren:``` 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.

Es gibt nichts zu konfigurieren und kein Override. Wenn Sie darauf stoßen, liegt die Lösung beim Upstream: entweder eine Version pinnen, die mit einem aktuellen Schlüssel signiert ist, oder warten, bis die betroffene Version neu signiert wird.

Routinemäßige Schlüsselrotation wird anders behandelt. Ein als ersetzt, zurückgezogen oder ohne Angabe von Gründen widerrufener Subkey macht Signaturen, die *vor* dem Widerruf erstellt wurden, nicht ungültig, sodass diese Releases mit einer Warnung installiert werden. Eine Signatur, die *nach* einem Widerruf datiert ist, wird unabhängig vom angegebenen Grund verweigert.

**Mozilla's August 2026 key rotation.** Mozilla hat den Signierungs-Subkey `09BEED63…C1A20256` am 2026-08-06 widerrufen, nachdem eine unverschlüsselte Kopie in ein privates GitHub-Repository übertragen wurde, und ihn durch `827E6586…76767AA3` ersetzt. Firefox-Releases, die mit dem alten Subkey signiert wurden — alles zwischen 2025-03-13 und 2026-08-06, was zum Zeitpunkt der Erstellung dieses Textes noch das aktuelle ESR (`140.13.0esr`) und jeden `version`-Pin in dieses Fenster einschließt — werden von der obigen Prüfung verweigert. Release- und Beta-Kanäle sind nicht betroffen. foxcage holt Schlüssel von `keys.openpgp.org` statt von `keyserver.ubuntu.com`, weil Letzterer Tage nach der Rotation weder den Ersatz-Subkey noch den Widerruf ausgeliefert hat; ein veralteter Schlüsselserver würde sowohl Builds sofort brechen als auch die Widerrufsprüfung stillschweigend zu einem No-op machen.

### Firefox-Forks (LibreWolf)

foxcage kann einen datenschutzorientierten Firefox-Fork anstelle des Upstream-Firefox ausführen:```toml
fork = "librewolf"         # default is "firefox"

[librewolf]
version = "146.0.1-1"      # optional pin; partial pins ("146", "146.0.1") also work

Oder pro Start über die CLI:```sh foxcage @tmp --fork librewolf https://example.com

**LibreWolf**: datenschutzgehärteter Firefox-Fork – strikter Tracking-Schutz, DoH, RFP, Telemetrie standardmäßig deaktiviert. Signiertes Linux-Tarball von GitLab (`librewolf-community/browser/bsys6`), GPG-verifiziert gegen den Schlüssel der LibreWolf-Maintainer `662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16` mit einer zusätzlichen `.sha256sum`-Gegenprüfung, unter denselben [Widerrufsregeln](#revoked-signing-keys) wie Firefox. Das mitgelieferte `librewolf.cfg` von LibreWolf bleibt erhalten; foxcage fügt seine eigenen Einstellungen oben hinzu, anstatt sie zu überschreiben. Die Maintainer haben ihren Signier-Subkey am 2026-04-25 ohne Angabe von Gründen rotiert; aktuelle Tarballs wurden vor diesem Datum signiert, daher werden sie mit einer Warnung installiert statt abgelehnt.

**Channel ist nur für Firefox**: `firefox.channel = "beta" | "esr"` wird abgelehnt, wenn `fork` etwas anderes als `"firefox"` ist. LibreWolf hat nur eine einzige Release-Variante.

Das Wechseln von `fork` (per Konfiguration oder `--fork`) ändert den Containerfile-Hash, was beim nächsten Start einen Rebuild auslöst – kein manuelles `--rebuild` erforderlich.

#### Profil-Kompatibilität

> **Verwenden Sie ein dediziertes Profil pro Fork.** Die sicherste Standardeinstellung ist, foxcage sein eigenes Profil bereitstellen zu lassen (`profile` in der Konfiguration weglassen) oder `profile` auf ein Verzeichnis zu zeigen, das Sie nicht auch vom Host aus öffnen.

- **LibreWolf**: *normalerweise* unbedenklich, es mit Ihrem Host-Firefox-Profil zu teilen – LibreWolf folgt den Firefox-Versionen innerhalb weniger Tage, daher sind `compatibility.ini`-Schema-Konflikte selten. Risiken: (1) Nur sequenzielle Nutzung ist sicher (die Sperrdatei von Firefox verhindert gleichzeitige Öffnungen); (2) im kurzen Zeitfenster nach einem Firefox-Stable-Release kann ein vorheriges Ausführen von Firefox und dann LibreWolf einen „von einer neueren Version verwendet“-Migrationsdialog auslösen; (3) Funktionen, die LibreWolf entfernt (Sync, Pocket, Mozilla-Konto), funktionieren stillschweigend nicht, beschädigen aber keine Daten.

### Benannte Cages

Führen Sie separate Sandbox-Instanzen mit eigener Konfiguration und eigenem Firefox-Profil aus:```sh
./foxcage @work

Dies lädt ~/.config/foxcage/work.toml und verwendet ein separates Image (foxcage-work), einen Container (foxcage-work) und ein Volume (foxcage-work-profile). Die Konfigurationsdatei muss für benannte Cages vorhanden sein. Cage-Namen dürfen nur Buchstaben, Ziffern, Bindestriche und Unterstriche enthalten.

Konfiguration

Konfigurationsdateien befinden sich in $XDG_CONFIG_HOME/foxcage/ (Standard: ~/.config/foxcage/).

  • config.toml — Standard-Cage (optional, ohne ihn gelten sinnvolle Standardwerte)
  • <name>.toml — benannter Cage, geladen mit @<name> (erforderlich)

Unbekannte Konfigurationsschlüssel werden mit einem Fehler abgelehnt. Siehe config.toml.example für alle verfügbaren Optionen mit Standardwerten.

Beispiel 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 ( 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 "-",

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"]

### Host-Firefox-Profil

Um ein Host-Firefox-Profil mit dem Cage zu teilen, setze `profile` auf das Profilverzeichnis. Finde deinen Profilpfad, indem du in Firefox auf dem Host `about:profiles` aufrufst — oder verweise einfach auf ein neues leeres Verzeichnis, wenn du möchtest, dass der Cage mit einem sauberen Profil startet, das auf dem Host erhalten bleibt.```toml
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"

Nur dieses eine Verzeichnis wird in den Käfig bind-gemountet. Geschwisterprofile unter ~/.mozilla/firefox/ und die profiles.ini-Registry sind nicht verfügbar – ein kompromittierter Käfig kann nicht daran herum manipulieren.

Wenn profile nicht gesetzt ist, speichert ein benannter Podman-Volume das Firefox-Profil stattdessen (siehe „Was bleibt erhalten“ unten). Wenn dasselbe Profil bereits in Firefox auf dem Host geöffnet ist, führt die pro-Profil-Sperrdatei von Firefox zu einem Konflikt – verwende ein dediziertes Profil pro Käfig.

Netzwerk

Standardmäßig verwendet der Container pasta mit blockiertem Host-Loopback und Host-DNS. pasta erfordert Podman 4.4 oder neuer (es ist seit Podman 5.0 die Rootless-Standardeinstellung).

Host-Netzwerk entfernt die Netzwerkisolation vollständig. Verwende dies, wenn der Käfig Dienste auf dem localhost des Hosts erreichen muss (z. B. einen lokalen Entwicklungsserver, eine Datenbank auf 127.0.0.1):```toml [network] mode = "host"

`dns` kann nicht mit `mode = "host"` kombiniert werden — Host-Netzwerk nutzt bereits den Resolver des Hosts.

> **Der Hostmodus gibt mehr auf als nur localhost.** Er versetzt den Käfig in den Netzwerk-Namensraum des Hosts, und abstrakte Unix-Sockets sind an diesen Namensraum gebunden statt an das Dateisystem. Ein Käfig im Hostmodus kann also direkt abstrakte Adress-Sockets auf dem Host erreichen – einschließlich Xwaylands `@/tmp/.X11-unix/X0`, wenn Sie X11 oder Xwayland ausführen (Eingabe-Logging, obwohl foxcage nur Wayland unterstützt), sowie einen Session-Bus, der mit `unix:abstract=…` konfiguriert ist, was den gefilterten D-Bus-Proxy umgehen würde. Das ist inhärent mit der gemeinsamen Nutzung des Netzwerk-Stacks verbunden und nicht etwas, das foxcage filtern kann. Verwenden Sie den Hostmodus, wenn Sie ihn benötigen, und bevorzugen Sie einen benannten Käfig, den Sie nur für diesen Zweck starten.

**Nur-IPv4-Käfige** deaktivieren IPv6 vollständig:```toml
[network]
ipv4_only = true

Oder beim Start mit dem Flag --ipv4-only (Kurzform -4, wie bei ssh/curl/pasta):```sh ./foxcage @tmp -4 https://example.com

Dies führt pasta im IPv4-only-Modus aus (`-4`), sodass der Container überhaupt keinen IPv6-Stack hat, und setzt zusätzlich `network.dns.disableIPv6` in Firefox, damit Firefox keine AAAA-Einträge auflöst – was wichtig ist, wenn DoH aktiviert ist, da DoH-Antworten den Resolver des Containers umgehen. `ipv4_only` kann nicht mit `mode = "host"` kombiniert werden – Host-Netzwerk nutzt den Netzwerk-Stack des Hosts direkt, also deaktivieren Sie IPv6 stattdessen auf dem Host.

### Init-Befehle

Führen Sie benutzerdefinierte Befehle zur Build-Zeit oder beim Containerstart über `[init]` aus:

- **`build`** — läuft zur Image-Build-Zeit als root. Verwenden Sie es zum Installieren von Paketen oder für andere langsame Einrichtungen. Änderungen an Build-Befehlen lösen automatisch einen Image-Rebuild aus.
- **`root`** — läuft beim Containerstart als root, vor Firefox. Verwenden Sie es für schnelle Root-Aufgaben zur Laufzeit (Berechtigungen anpassen, Konfigurationsdateien schreiben).
- **`user`** — läuft beim Containerstart als Ihr Benutzer, vor Firefox. Verwenden Sie es zum Erstellen von Verzeichnissen und zum Einrichten von benutzerspezifischem Zustand.```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"]

Alle drei Schlüssel sind Listen von Shell-Befehlszeichenfolgen. Wenn ein Befehl fehlschlägt, beendet sich der Container, ohne Firefox zu starten.

Sicherheitshinweis: Wenn init.root gesetzt ist, startet der Container als Root, wobei CAP_SETUID und CAP_SETGID hinzugefügt werden (zusätzlich zum standardmäßigen CAP_SYS_CHROOT), damit er wieder zum regulären Benutzer wechseln kann. Diese Capabilities werden nur während der Root-Init-Phase gehalten – nach dem Abgeben der Privilegien besitzt der Prozess des regulären Benutzers keine zusätzlichen Capabilities. Ohne init.root läuft der Container mit dem standardmäßigen minimalen Capability-Satz.

Was erhalten bleibt

Ohne Konfiguration speichert ein benanntes Podman-Volume das Firefox-Profil (Lesezeichen, Einstellungen, Erweiterungen, Widevine-DRM-Plugin). Alles andere ist flüchtig.

  • Standard-Cage: foxcage-profile
  • Benannter Cage: foxcage-<name>-profile

Um neu zu starten, entferne das Volume:```sh podman volume rm foxcage-profile

Wenn `profile` gesetzt ist, wird das Host-Verzeichnis direkt bind-gemountet und es wird kein Volume erstellt.

### Speichernutzung

Jedes Cage-Image ist etwa 1 GB groß. Ein Rebuild taggt das Image neu und lässt das vorherige als ungetaggten `<none>`-Eintrag zurück, daher entfernt foxcage das gerade ersetzte Image nach jedem erfolgreichen Build. Es entfernt nur dieses bestimmte Image und niemals eines, das ein laufender Cage noch verwendet.

Vor Einführung dieses Verhaltens verwaiste Images werden nicht nachträglich bereinigt. Um sie zurückzugewinnen:```sh
podman images --filter dangling=true    # review first
podman image prune                      # then remove

Firefox-Updates werden bei jedem Start automatisch erkannt. Um einen vollständigen Neuaufbau zu erzwingen (z. B. um System-Sicherheitsupdates sofort zu übernehmen):```sh ./foxcage --rebuild

## Theming

foxcage reicht automatisch Folgendes vom Host durch, sodass sich Firefox im Container wie eine native Anwendung anfühlt und aussieht:

- **Schriftarten.** Systemschriftarten (`/usr/share/fonts`) und Benutzerschriftarten (`~/.local/share/fonts`) werden schreibgeschützt eingebunden. Auch die Schriftkonfiguration aus `~/.config/fontconfig` wird durchgereicht.
- **GTK-Design und dunkler Modus.** Werden über `GTK_THEME` oder `gsettings` erkannt und an den Container übergeben. Die GTK-Konfiguration aus `~/.config/gtk-3.0` und `~/.config/gtk-4.0` wird schreibgeschützt eingebunden.
- **Zeitzone.** Der Zeitzonenname des Hosts (erkannt über `TZ`, den `/etc/localtime`-Symlink oder `/etc/timezone`) wird als `TZ` in den Container übergeben, und `/etc/localtime` wird schreibgeschützt eingebunden. Beides ist notwendig: Firefox leitet die JavaScript-Zeitzone aus dem Zonen-*Namen* ab, nicht aus dem Dateiinhalt – ohne `TZ` würden Websites Zeiten in UTC anzeigen.
- **Locale.** `LANG` wird durchgereicht. Die Locale des Hosts wird beim Erstellen des Container-Images erzeugt.

**Cage-Label.** Die Firefox-Menüleiste zeigt „FoxCage" (oder „FoxCage - name" für benannte Cages) an, sodass man auf einen Blick erkennt, dass man sich in einer Container-Sitzung befindet. Die Menüleiste ist über eine Unternehmensrichtlinie immer sichtbar.

Der Container enthält nur das GTK-Design Adwaita. Auf GNOME-Desktops funktioniert das sofort. Auf KDE oder anderen Desktops fällt Firefox auf Adwaita zurück, wenn dein GTK-Design (z. B. Breeze) nicht im Container installiert ist. Die Erkennung des dunklen Modus funktioniert weiterhin, solange die Einstellung über `gsettings` oder `GTK_THEME` gesetzt ist.

## DRM (Netflix, Disney+ usw.)

Widevine-DRM funktioniert sofort. Beim ersten Besuch einer DRM-geschützten Website lädt Firefox das Widevine-CDM automatisch herunter. Das kann einen Moment dauern.

## Host-Integration (immer aktiv)

foxcage verwendet einen gefilterten D-Bus-Proxy, um Firefox Zugriff auf das XDG-Desktop-Portal und den Benachrichtigungs-Daemon des Hosts zu geben. Diese Funktionen sind sicher, weil der gesamte Zugriff über den Benutzer vermittelt wird – der Host zeigt native Dialoge an, mit denen du interagieren musst. Ein kompromittierter Browser kann nicht stillschweigend auf Host-Ressourcen zugreifen.

- **Datei-Uploads** – native Dateiauswahl des Hosts (du wählst aus, welche Dateien geteilt werden)
- **Externe Links** – `mailto:`, Magnet-Links usw. öffnen über die App-Auswahl des Hosts
- **Desktop-Benachrichtigungen** – an den Benachrichtigungs-Daemon des Hosts weitergeleitet
- **Bildschirmfreigabe** – Portal-Bildschirmauswahl + PipeWire-Videostream (erfordert PipeWire auf dem Host)

## Geräte-Durchreichung (optional)

Diese reichen Host-Geräte direkt in den Container durch und sind **standardmäßig deaktiviert** – anders als bei den oben genannten Portal-Funktionen gibt es keine Bestätigung auf Host-Seite. Ein kompromittierter Browser könnte die Hardware stillschweigend nutzen.```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

Noch nicht unterstützt

Einige Webplattform-Funktionen funktionieren im Container nicht, da die Host-Integration fehlt. Diese sind hier aus Gründen der Transparenz aufgeführt.

Bluetooth, USB, serielle Schnittstellen und NFC. Die Web-Bluetooth-, WebUSB-, Web-Serial- und WebNFC-APIs benötigen Gerätezugriff und Systemdienste (BlueZ, udev), die im Container nicht verfügbar sind.

Gamepads und MIDI. Die Gamepad-API benötigt Zugriff auf /dev/input/. Web MIDI benötigt Zugriff auf die ALSA-Sequencer. Beides wird nicht durchgereicht.

PWA-Installation. Progressive Web Apps können vom Container aus nicht auf dem Host-Desktop installiert werden.

Barrierefreiheit. Die Unterstützung für Screenreader über AT-SPI ist deaktiviert (NO_AT_BRIDGE=1) – der Container hat keine Verbindung zum Barrierefreiheits-Bus des Hosts. Die Web-Speech-API-Synthese funktioniert jedoch: speech-dispatcher mit der espeak-ng-Engine ist im Käfig installiert und wird bei der ersten Verwendung automatisch gestartet, wobei Audio über den gemeinsamen PulseAudio-Socket geleitet wird.

Host-Konfiguration

Empfohlen: Overlay-Speicherung mit fuse-overlayfs

Rootless Podman verwendet möglicherweise standardmäßig den vfs-Speichertreiber, der ganze Image-Ebenen kopiert, anstatt Overlay-Mounts zu verwenden. Dadurch wird der Containerstart nach einem Build erheblich verlangsamt. Um dies zu beheben, installieren Sie fuse-overlayfs und fügen Sie Folgendes zu ~/.config/containers/storage.conf hinzu:```toml [storage] driver = "overlay"

[storage.options.overlay] mount_program = "/usr/bin/fuse-overlayfs"

### Foxcage als Standardbrowser festlegen

Stelle zunächst sicher, dass sich das Skript `foxcage` an seinem permanenten Speicherort befindet (z. B. `~/bin/foxcage` oder `/usr/local/bin/foxcage`). Der Installationsbefehl speichert den aktuellen Pfad des Skripts in der `.desktop`-Datei. Wenn du es danach verschiebst, funktioniert der Launcher nicht mehr.

Dann führe Folgendes aus:```sh
foxcage --install

Dies erstellt eine .desktop-Datei, die auf den aktuellen Speicherort des Skripts verweist, installiert das FoxCage-Symbol und aktualisiert die Desktop- und Icon-Datenbanken. FoxCage sollte danach in Ihrem Anwendungsmenü erscheinen.

Um FoxCage als Standard-Webbrowser festzulegen, sodass Links, die in anderen Anwendungen angeklickt werden, in FoxCage geöffnet werden:```sh xdg-settings set default-web-browser foxcage.desktop

Wenn ein Cage bereits läuft, werden URLs als neuer Tab im vorhandenen Browser geöffnet.

Zum Rückgängigmachen:```sh
foxcage --uninstall

StartupNotify=true ist in der .desktop-Datei gesetzt, was dem Compositor mitteilt, einen Spinner-Cursor anzuzeigen, während foxcage startet. Wenn ein Image-Build erforderlich ist (was einige Minuten dauern kann), sendet foxcage eine Desktop-Benachrichtigung, damit du weißt, dass Firefox unterwegs ist. Jeder Fehler bei einem frühen Beenden (Tippfehler in der Konfiguration, fehlende Abhängigkeit, fehlerhafter Cage-Name) wird ebenfalls als Desktop-Benachrichtigung angezeigt, damit Benutzer, die über den Desktop starten, nicht ins Leere starren, wenn foxcage ohne angeschlossenes Terminal fehlschlägt. Beides erfordert notify-send (von libnotify-bin auf Debian/Ubuntu) – wenn es nicht installiert ist, werden Benachrichtigungen stillschweigend übersprungen und der Fehler geht weiterhin an stderr.

Manuelle Einrichtung

Wenn du die .desktop-Datei lieber manuell erstellen möchtest, erstelle ~/.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

Ersetzen Sie `/path/to/foxcage` durch den tatsächlichen Pfad zum Skript. Registrieren Sie es:```sh
update-desktop-database ~/.local/share/applications

Tests ausführen

Die Testsuite verwendet pytest + pytest-cov, die in requirements-dev.txt als reine Entwicklungsabhängigkeiten deklariert sind.``` pip install -r requirements-dev.txt pytest

Die Tests sind vollständig hermetisch — kein podman, kein Netzwerk, kein echtes Dateisystem außer dem `tmp_path` von pytest. Die Suite erzwingt **100 % Zeilen- und Zweigabdeckung** (konfiguriert in `pytest.ini` und `.coveragerc`); jede nicht abgedeckte Zeile oder jeder nicht ausgeführte Zweig einer Bedingung lässt den Lauf fehlschlagen. CI führt die Suite bei jedem Push über `.gitlab-ci.yml` aus.

## Danksagungen

Dieses Projekt wurde von Mike Cardwell mit Unterstützung von [Claude Code](https://claude.ai/claude-code), dem KI-Codierungstool von Anthropic, entwickelt.

## Unterstütze/Wertschätze meine Arbeit

- [Bitcoin](bitcoin:1PQLtWnjUi1itHLG6QCQeHM3Nxua8pRsq1): 1PQLtWnjUi1itHLG6QCQeHM3Nxua8pRsq1
- [Paypal](https://www.paypal.me/grepular)
- [Patreon](https://patreon.com/grepular)

Kategorien