Ein Coding-Agent liest Quelltext, Anweisungen und Webseiten und handelt auf dieser Grundlage. Er bearbeitet Dateien, führt Befehle aus und ruft Dienste im Netz auf. Jeder Text, den er liest, kann damit sein Handeln beeinflussen, auch ein Kommentar in einer Abhängigkeit oder eine präparierte Webseite. Dieses Risiko wird als Prompt-Injection beschrieben (OWASP LLM01 ). Kritisch wird es, wenn ein Agent zugleich private Daten liest, fremden Inhalten ausgesetzt ist und nach außen kommunizieren kann (Willison ). Auf einem Entwicklerrechner treffen alle drei Bedingungen zu.
Ziel und Abgrenzung
Das hier beschriebene Werkzeug
cjx führt einen Agenten in einem
Container aus und begrenzt dabei drei Dinge. Das Projekt ist standardmäßig nur
lesbar und wird erst mit --rw beschreibbar. Netzverbindungen sind nur zu
Hosts möglich, die auf einer Allowlist stehen. Der Container läuft ohne
Privilegien. Unterstützt werden Claude Code, OpenAI Codex und
pi.dev, weitere Agenten lassen sich über eine Konfigurationsdatei ergänzen.
Die Sandbox ist eine praktische Grenze für das Verhalten eines Agenten. Gegen eine Schwachstelle im Kernel, die aus einem Container ausbricht, schützt sie nicht.
Aufbau
Jeder Start erzeugt einen Agent-Container, dem alle Linux-Capabilities entzogen
sind. Er läuft mit no-new-privileges, einem schreibgeschützten
Root-Dateisystem und begrenzter Zahl an CPUs, Speicher und Prozessen.
Beschreibbar sind nur flüchtige tmpfs-Verzeichnisse wie /tmp, das Home-Volume
und, mit --rw, das Projekt sowie per --add-dir eingebundene Verzeichnisse.
Das Home-Volume gehört zu einem Agenten und dessen Modus und enthält Anmeldung
und Verlauf. Es bleibt über Sitzungen hinweg erhalten und liegt im
Volume-Speicher der Engine, nicht in einem Verzeichnis des Benutzers.
Die Images entstehen in Schichten. Ein Basis-Image enthält Debian, Node.js und
einen Benutzer für den Agenten, darauf installiert ein Image pro Agent dessen
offizielles Installationsskript. Für Claude Code legt das Basis-Image zusätzlich
eine verwaltete Richtlinie an, die Claude Code das Lesen von .env-Dateien und
secrets/-Verzeichnissen verbietet
(Claude Code Permissions ). Sie
gilt für die Werkzeuge von Claude Code, nicht für Skripte, die der Agent
ausführt. Ein Hash über Build-Dateien und Agent-Definition, gespeichert als
Label am Image, legt fest, wann ein Image neu gebaut wird.
Firewall pro Sitzung
Zu jedem Start gehören ein eigenes Netz und ein eigener Squid-Container. Das Netz ist intern und hat keine Route nach außen. Unter Podman fehlt ihm zusätzlich ein DNS-Server, und Docker leitet DNS-Anfragen aus internen Netzen seit einer Sicherheitskorrektur nicht mehr nach außen weiter (CVE-2024-29018 ). Der Agent kann damit weder Namen im Internet auflösen noch Verbindungen dorthin aufbauen. Squid ist sowohl mit diesem Netz als auch mit dem Standardnetz der Engine verbunden und dient dem Agenten als Proxy.
Squid erlaubt ausschließlich die Methode CONNECT
(RFC 9110 ) auf Port 443
und nur zu Zielen auf der Allowlist.
acl allowed_dst dstdomain "/etc/cjx/allowlist"
http_access allow CONNECT SSL_ports allowed_dst
http_access deny all
Die Allowlist setzt sich aus den Hosts des Agenten und den Einträgen des Benutzers zusammen. Squid sieht dabei nur den Zielnamen, der Inhalt der TLS-Verbindung bleibt unberührt. Ist in der Shell ein Proxy gesetzt und vom Host aus erreichbar, leitet Squid über diesen weiter. Am Ende der Sitzung werden beide Container, das Netz und die Sitzungsdaten entfernt.
Anmeldung in der Sandbox
Die Agenten melden sich per OAuth beim jeweiligen Anbieter an. Auf einem Desktop öffnet der Agent dazu den Browser und nimmt die Antwort über einen lokalen Rückruf-Server entgegen (RFC 8252 ). In der Sandbox läuft dieser Server im Container, wo der Browser des Hosts ihn nicht erreicht.
Die Hersteller sehen für diesen Fall eigene Wege vor. Claude Code und pi.dev
zeigen den Anmeldelink an und nehmen den Code oder die Ziel-URL anschließend
per Eingabe entgegen. Codex bietet die Anmeldung per Gerätecode an
(RFC 8628 ), die einmal in den
ChatGPT-Sicherheitseinstellungen freigeschaltet wird. Dabei wird ein Code im
Browser eingegeben. Die Anmeldung landet im Home-Volume und gilt damit für
alle weiteren Sitzungen. Variablen, die das Terminal beschreiben, wie
TERM_PROGRAM und COLORTERM, reicht cjx in den Container weiter, damit ein
Agent anklickbare Links und Farben wie außerhalb verwendet. Weitere Variablen
der Shell erreichen die eingebauten Agenten nicht.
Podman und Docker
cjx ist für Podman ohne Root-Rechte gedacht und unterstützt außerdem einen Docker-Daemon mit Root-Rechten. Rootless Podman ist die bevorzugte Grundlage, da die Container-Laufzeit keine Rechte über die des Benutzers hinaus besitzt. Unter Docker läuft der Daemon als root, und wer ihn erreicht, erlangt damit Root-Rechte auf dem Host (Docker Security ).
Der Agent muss das eingebundene Projekt unter der Kennung des Benutzers lesen
und schreiben. Podman bildet die eigene uid mit --userns keep-id auf den
Benutzer im Container ab
(podman run ),
unter Docker übergibt cjx die uid und gid mit --user. Zwei Docker-Varianten
lehnt cjx ab. Rootless Docker bildet den Benutzer auf root im Container ab und
alle übrigen Kennungen in einen Bereich untergeordneter IDs
(Rootless mode ).
Mit userns-remap verschiebt der Daemon sämtliche Kennungen aller Container,
root eingeschlossen, in einen solchen Bereich
(userns-remap ). In
beiden Fällen gehört das eingebundene Projekt im Container nicht dem Agenten.
Grenzen
Die Allowlist begrenzt die Ziele, nicht die Daten. Ein erlaubter Host bleibt
ein möglicher Kanal nach außen, darunter die API des Modellanbieters selbst.
Die Richtlinie gegen das Lesen von .env-Dateien gilt nur für die Werkzeuge
von Claude Code. Skripte, die ein Agent ausführt, und die übrigen Agenten lesen,
was die Einbindung freigibt. Mit --no-firewall entfällt die Netzbeschränkung
vollständig. Unter Docker bleibt der Daemon mit Root-Rechten Teil der
Vertrauensbasis, und Codex läuft dort ohne Seccomp-Filter, weil seine
bubblewrap-Sandbox einen User-Namespace braucht.
Fazit
cjx trennt das Handeln eines Agenten vom Rechner, auf dem er läuft. Schreiben erfordert eine Freigabe, das Netz ist auf benannte Hosts beschränkt, und die Anmeldung bleibt im Container. Der Agent arbeitet damit weiter wie gewohnt, und seine Möglichkeiten reichen nicht über diese Freigaben hinaus.