Max Nardit
← Artikel
11 mindev

Claude-Code-Sandbox unter Linux: was srt abschottet und was bypassPermissions öffnet

Die eingebaute Sandbox ist eine echte Grenze auf Betriebssystemebene um Shell-Befehle, und nur um sie. Wie stark sie ist, hängt von zwei Dingen ab, die der Einrichtungsbildschirm nie zeigt: deinem Berechtigungsmodus und dem, was schon existierte, als der Befehl startete. Ein Test unter Ubuntu dazu, was srt blockiert und was es offen lässt, der Linux-Bug, der sie immer noch kaputt macht, und wann ein Container oder eine VM besser passt.

Inhalt

Anthropics sandbox-runtime ist nicht neu. Das Repository wurde am 20. Oktober 2025 angelegt, Version 0.0.81 erschien am 20. Oktober 2026. Es ist der Motor unter der eingebauten Sandbox von Claude Code, und über beide wird geredet, als wären sie ein Ding mit einer Grenze. Sind sie nicht, und die Grenze ist weniger fest, als das Wort vermuten lässt.

Ich habe beides auf einem Ubuntu-24.04-Rechner laufen lassen: srt 0.0.81 von Hand und Claude Code 2.1.310 headless mit eingeschalteter Sandbox. Zwei Ergebnisse aus diesen Läufen prägen diesen Text. Im Modus bypassPermissions hat die Netzwerkhälfte der Sandbox einen nicht gelisteten Host direkt durchgelassen, bis ich einen weiteren Schlüssel gesetzt habe. Und unter Linux wird die Liste der geschützten Pfade beim Start eines Befehls erstellt, also ist ein Repository, das in diesem Befehl entsteht, ungeschützt, bis er endet. Beides sagt dasselbe: Welche Sandbox du bekommst, hängt von Bedingungen ab, die das /sandbox-Panel nicht anzeigt. Die einzige Prüfung, der ich traue, ist deshalb ein Testlauf in deinem echten Modus.

Was schottet die Claude-Code-Sandbox tatsächlich ab?

Shell-Befehle und die Prozesse, die sie starten, und zwar auf zwei Seiten: Dateien und Netzwerk. Laut Sandbox-Doku darf ein Befehl in der Sandbox ins Arbeitsverzeichnis schreiben, in ein Temp-Verzeichnis pro Benutzer und in Verzeichnisse, die du hinzugefügt hast, sonst nirgendwohin. Einen direkten Weg ins Netzwerk hat er nicht; Verbindungen laufen über einen Proxy auf deinem Rechner, der jeden Hostnamen gegen eine Allowlist prüft, die leer beginnt. Die Sandbox ist standardmäßig aus. /sandbox schaltet sie für ein Projekt ein, sandbox.enabled in ~/.claude/settings.json für alle.

Was sie standardmäßig offen lässt, zählt genauso. Lesen ist auf dem größten Teil des Rechners erlaubt, ~/.ssh und ~/.aws/credentials eingeschlossen. Umgebungsvariablen werden von Claude Code geerbt, samt Secrets. Beides lässt sich mit filesystem.denyRead und dem Block credentials einschränken, und abgedeckt ist nur, was du auflistest: Laut Doku gibt es keine eingebaute Deny-Liste für Zugangsdaten.

Dann gibt es alles, was die Sandbox nie sieht. Read, Edit, Write, WebFetch und WebSearch folgen stattdessen den Berechtigungsregeln, also hält ein Eintrag in denyRead das Read-Tool nicht auf, und allowedDomains begrenzt WebFetch nicht. Hooks, lokale MCP-Server, LSP-Server, Plugin-Monitore, der Statuszeilen-Befehl und apiKeyHelper laufen mit deinen vollen Rechten. Genauso alles, was ein Mod startet.

Wie unterscheidet sich die Sandbox von Claude Code von sandbox-runtime (srt)?

Gleicher Motor, anderer Umfang. Claude Code baut seine Sandbox auf dem Paket @anthropic-ai/sandbox-runtime auf und packt jeden Bash-, PowerShell- und Monitor-Befehl in eine frische. Das Paket bringt außerdem eine CLI mit, srt, die jeden beliebigen Prozess einpackt: einen einzelnen Befehl, einen lokalen MCP-Server oder Claude Code selbst. Im letzten Fall landen auch Datei-Tools, Hooks und MCP-Server innerhalb der Grenze. Das README nennt es weiterhin eine Beta-Research-Preview, deren Konfigurationsformat sich ändern kann.

Unter Linux ist die Grenze bubblewrap. Der Prozess in der Sandbox bekommt schreibgeschützte Bind-Mounts über alles, wohin er nicht schreiben darf, und einen Netzwerk-Namespace ohne Route nach außen. Auf dem Host laufen zwei Proxys, einer für HTTP und einer für SOCKS5, und erreichen die Sandbox über Unix-Sockets, die socat hineinbrückt. Ein mitgelieferter seccomp-Helfer blockiert das Anlegen neuer Unix-Sockets und blockiert die Systemaufrufe, mit denen ein Befehl einen eigenen User-Namespace anlegen könnte, denn darin könnte er die schreibgeschützten Bind-Mounts aushängen. Unter macOS ist dieselbe Policy ein generiertes Seatbelt-Profil. Unter nativem Windows hat srt eine Alpha, die Befehle unter einem eigenen lokalen Konto ausführt; Claude Code selbst führt Befehle dort ohne Sandbox aus, außer du verlegst es in WSL2.

Was macht bypassPermissions mit der Netzwerkwand der Claude-Code-Sandbox?

Es beantwortet die Netzwerkfrage für dich, und die Antwort ist ja. Wenn sich ein Befehl in der Sandbox mit einem Host außerhalb der Allowlist verbindet, lässt Claude Code deinen Berechtigungsmodus entscheiden. Manuell und acceptEdits fragen dich, dontAsk lehnt ab, und der Auto-Modus lehnt ab, außer Claude hat den Host vorher beim Befehl aufgeführt und der Classifier hat die Liste genehmigt. In bypassPermissions wird die Verbindung laut Doku ohne Nachfrage erlaubt. Ich habe denselben curl auf example.com in vier Headless-Sessions laufen lassen, mit eingeschalteter Sandbox, abgeschaltetem Retry ohne Sandbox und ganz ohne erlaubte Domains:

BerechtigungsmodusstrictAllowlistErgebnis
defaultausblockiert: deny network-outbound example.com:443 (user denied)
defaultanblockiert: host is not on the allow list
bypassPermissionsaus200
bypassPermissionsanblockiert: host is not on the allow list

Die erste Zeile hat einen Vorbehalt. Eine Headless-Session mit claude -p hat niemanden, der eine Nachfrage beantwortet, also bedeutet „user denied“, dass die unbeantwortete Nachfrage als Ablehnung zählt (fail closed); in einer interaktiven Session wärst du gefragt worden. Den Auto-Modus habe ich nicht getestet.

Die dritte Zeile ist die, wegen der ich gekommen bin. Im selben Modus schlug touch ~/sandbox-probe weiterhin mit Read-only file system fehl. Mit abgeschaltetem Retry hielt also die Dateisystemhälfte, während die Netzwerkhälfte einen nicht gelisteten Host durchließ. Die Doku ergänzt: Bleibt der Retry auf seinem Standardwert, läuft der Retry ohne Sandbox nach einem fehlgeschlagenen Befehl in bypassPermissions ebenfalls ohne Nachfrage, und dann hält für diesen Befehl keine der beiden Hälften. strictAllowlist (ab v2.1.219) ist der Schlüssel, der die Netzwerkseite schließt: Er lehnt nicht gelistete Hosts in jedem Modus ab und hat keine Wirkung, wenn er in der .claude/settings.json eines Repositorys gesetzt ist. Zwei weitere Schlüssel härten den Rest:

json
{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "network": { "strictAllowlist": true, "allowedDomains": ["github.com", "registry.npmjs.org"] }
  }
}

allowUnsandboxedCommands: false lässt Claude Code den Retry ignorieren. failIfUnavailable stoppt es beim Start, wenn die Sandbox nicht laufen kann, statt des Standardverhaltens, Befehle dann ohne Sandbox auszuführen. Wo du das ablegst, macht einen Unterschied. Mit --settings übergeben oder als Managed Settings ausgeliefert, macht das Abschalten des Retrys die Sandbox zur Admin-Vorgabe, und Claude Code ignoriert dann die excludedCommands eines Repositorys und andere lockernde Einträge. In normalen Benutzereinstellungen tut es das nicht, und die excludedCommands eines Repositorys können passende Befehle weiterhin aus der Sandbox herausnehmen. Prüf also in beiden Fällen, was ausgenommen ist.

Selbst abgeriegelt arbeitet die Allowlist mit Hostnamen und schaut nicht in TLS hinein. Die Doku warnt, dass ein breiter Eintrag wie github.com ein Weg zur Exfiltration sein kann und dass Domain Fronting Hosts außerhalb der Liste erreichen kann. Jede Domain, die du hinzufügst, ist ein Kanal. Das ist das Inventarproblem aus dem Artikel über alle Wege aus der Sandbox, geschrumpft auf einen Rechner.

Wie bringst du sandbox-runtime (srt) unter Linux zum Laufen?

Du brauchst bubblewrap, socat und ripgrep, Node für das Paket und eine Einstellungsdatei. Prüf zuerst die Kernel-Seite:

shell
bwrap --version
sysctl kernel.apparmor_restrict_unprivileged_userns

Gibt der sysctl 1 aus, die Standard-AppArmor-Policy ab Ubuntu 24.04, bekommt bubblewrap User-Namespaces ohne die Capabilities, die es braucht. Die Sandbox-Doku liefert ein AppArmor-Profil für /usr/bin/bwrap; das README liefert die gröbere Lösung, den sysctl auf 0 zu setzen. Auf dem Testrechner stand er schon auf 0.

Mit Root sind die Abhängigkeiten jeweils eine Zeile:

shell
sudo apt-get install bubblewrap socat ripgrep
npm install -g @anthropic-ai/sandbox-runtime

Der Testrechner hatte bubblewrap, aber weder socat noch ripgrep, die die eigenständige Runtime im PATH sucht. Ohne Root kannst du die Debian-Pakete in ein Verzeichnis entpacken, das dir gehört. Halte dieses Verzeichnis und das Paket außerhalb aller Pfade, für die du Schreibzugriff erlaubst. Das README warnt, dass eine Kopie in einem beschreibbaren node_modules aus der Sandbox heraus umgeschrieben werden und beim nächsten Mal ohne Sandbox laufen kann, und dasselbe gilt für ein beschreibbares rg oder socat im Pfad.

shell
mkdir -p ~/opt/srt-deps && cd ~/opt/srt-deps
apt-get download socat ripgrep
for f in *.deb; do dpkg-deb -x "$f" root; done
npm install --prefix ~/opt/srt @anthropic-ai/sandbox-runtime@0.0.81
export PATH="$HOME/opt/srt-deps/root/usr/bin:$HOME/opt/srt/node_modules/.bin:$PATH"

Arbeite dann in einem separaten Scratch-Verzeichnis, denn genau das öffnet "allowWrite": ["."], und leg die Einstellungen dort oder in ~/.srt-settings.json ab. Das ist die Datei, die ich für die Netzwerk- und Credential-Tests verwendet habe:

shell
mkdir -p ~/srt-lab && cd ~/srt-lab && git init -q
cat > srt-test.json <<'EOF'
{
  "network": { "allowedDomains": ["example.com"], "deniedDomains": [] },
  "filesystem": { "denyRead": ["~/.ssh"], "allowWrite": ["."], "denyWrite": [] },
  "credentials": { "envVars": [{ "name": "DEMO_TOKEN", "mode": "deny" }] }
}
EOF

Eine Falle aus den eigenen Beispielen des README. srt "curl example.com" mit dem Befehl in Anführungszeichen schlug bei mir mit command not found fehl, weil 0.0.81 den ganzen String als Programmnamen nahm. Ein Befehlsstring gehört hinter -c:

shell
srt --settings ./srt-test.json -c 'curl -sS -o /dev/null -w "%{http_code}\n" https://example.com'

Was hat sandbox-runtime unter Ubuntu blockiert, und was hat es durchgelassen?

Jeder Schreibzugriff und jede Verbindung, die ich außerhalb der Policy angesetzt habe, ist fehlgeschlagen, und die Standardwerte haben genau das durchgelassen, was laut Doku durchgeht. Jede Zeile ist ein srt -c-Lauf aus dem Scratch-Verzeichnis:

TestErgebnis unter srt 0.0.81
touch ~/sandbox-probeRead-only file system
eine Datei im Arbeitsverzeichnis schreibenerlaubt
an .bashrc im Arbeitsverzeichnis anhängenPermission denied
.git/hooks/pre-commit schreibenRead-only file system
nach /tmp schreibenRead-only file system
nach $TMPDIR (/tmp/claude) schreibenNo such file or directory, bis das Verzeichnis auf dem Host existierte, dann erlaubt
~/.ssh auflisten ohne denyReaderlaubt, 15 Einträge
~/.ssh auflisten mit denyReaderlaubt, 0 Einträge
curl auf einen Host bei leerer AllowlistCONNECT tunnel failed, response 403
curl auf einen erlaubten Host200
curl --noproxy '*' auf einen erlaubten HostCould not resolve host
ein UDP-Paket an 1.1.1.1:53 sendenNetwork is unreachable
curl auf einen Server auf 127.0.0.1 des HostsCouldn't connect to server
einen Server in der Sandbox starten und mit curl ansprechen200
mit dem D-Bus-System-Socket verbindenOperation not permitted
unshare -U trueOperation not permitted
DEMO_TOKEN (gelistet) und OTHER_TOKEN (nicht gelistet)DEMO_TOKEN nicht gesetzt, OTHER_TOKEN sichtbar

Ein Verzeichnis mit Leseverbot lässt sich trotzdem öffnen, es wirkt nur leer: bubblewrap hängt ein leeres tmpfs darüber, also findet ein Tool, das nach einer Schlüsseldatei sucht, keine statt eines Fehlers. Die Token-Zeile ist die Credential-Regel im Kleinen: Was du aufzulisten vergisst, wird nicht zurückgehalten. Die Temp-Verzeichnis-Zeile passt zu einem Satz auf der Seite zu den Umgebungen: Unter Linux gelten Schreibfreigaben nur für Pfade, die schon existieren.

Das localhost-Paar ist das Linux-Verhalten, das die Doku beschreibt. Die Sandbox hat ihr eigenes Loopback, also schlägt eine direkte Verbindung zu einem Dev-Server oder einer Datenbank auf dem Host fehl, und allowLocalBinding bewirkt unter Linux nichts. Über den Proxy ist ein Dienst auf dem Host nur erreichbar, wenn du ihn auf die Allowlist setzt, als localhost oder als Adresse mit Port wie 127.0.0.1:3000; jeder andere Hostname, der auf eine Loopback-Adresse auflöst, wird abgelehnt. In der Sandbox zeigte env außerdem die Verkabelung: HTTP_PROXY, ALL_PROXY und ihre Verwandten, ein GIT_SSH_COMMAND, das Gits SSH durch den Proxy tunnelt, und einen JAVA_TOOL_OPTIONS-Agenten für JVM-Tools, die Proxy-Variablen ignorieren.

Wo verfehlt die Deny-Liste von sandbox-runtime unter Linux ihr Ziel?

Bei geschützten Pfaden, die innerhalb einer laufenden Sandbox auftauchen. Die Seite zu den Umgebungen sagt es direkt: Unter Linux baut die Runtime ihre Deny-Liste einmal beim Start, deckt das Projektwurzelverzeichnis ab, sucht flach nach verschachtelten Kopien, die schon existieren, und deckt nicht ab, was die Session später anlegt. Ein Lauf zeigt es:

shell
srt --settings ./srt-test.json -c 'git init -q sub && echo "echo hi" > sub/.git/hooks/pre-commit && echo wrote-hook'
# wrote-hook
srt --settings ./srt-test.json -c 'echo "echo hi" > sub/.git/hooks/post-commit'
# /usr/bin/bash: line 1: sub/.git/hooks/post-commit: Read-only file system

Das Repository, das im ersten Befehl entstand, hat einen Hook angenommen. Der nächste srt-Lauf hat eine frische Deny-Liste gebaut, das Repository gefunden und geschützt. Eine .bashrc in einem Unterverzeichnis, das innerhalb eines Befehls angelegt wurde, ging genauso durch. Unter macOS werden die Verbote bei jedem Schreibzugriff geprüft, also betrifft diese Lücke nur Linux. Wie groß sie ist, hängt davon ab, was die Sandbox einpackt. Die eingebaute Sandbox von Claude Code startet pro Befehl eine neue, also ist das Fenster einen Befehl lang. srt claude packt die ganze Session in eine Sandbox, also bleibt ein Repository, das die Session klont oder per Scaffolding anlegt, außerhalb der Liste, bis du neu startest.

Derselbe Mechanismus hinterlässt etwas. Um eine Datei zu schützen, die noch nicht existiert, legt die Linux-Sandbox für die Dauer des Befehls einen Platzhalter an ihren Pfad. In einem leeren Verzeichnis hat ein vier Sekunden langes sleep unter srt zwölf davon angelegt, von .bash_profile und .bashrc bis .mcp.json, .vscode und .idea, die ganze Zeit vom Host aus sichtbar. Ein sauberes Ende hat sie entfernt. Nach einem kill -9 blieben sie: leer, schreibgeschützt, mir gehörend. Die Sandbox-Doku beschreibt dieselben Überbleibsel an den Einstellungspfaden unter .claude und verweist auf claude doctor, um sie zu finden. In einem Projektverzeichnis ist das Unordnung. Mit deinem Home-Verzeichnis als Arbeitsverzeichnis ist eine leere ~/.bash_profile eine echte Änderung, denn eine Login-Bash liest sie und überspringt dann ~/.profile.

Warum schlägt die Claude-Code-Sandbox auf manchen Linux-Rechnern fehl?

Eine Regression ist noch offen. Issue 43454: Seit Claude Code 2.1.92 den Helfer apply-seccomp mitliefert, kann jeder Bash-Befehl in der Sandbox beim Aufsetzen fehlschlagen, und zwar auf Systemen, auf denen User-Namespaces ohne Capabilities kommen. Dazu gehört die Standard-AppArmor-Policy auf neueren Ubuntu-Versionen. Der ursprüngliche Bericht:

apply-seccomp: write /proc/self/setgroups (nested userns is capability-restricted; caller must provide CAP_SYS_ADMIN): Permission denied

Das Issue trägt die Labels regression und reproduced. Ein Kommentar eines Maintainers vom August bestätigt es auf 2.1.233 und nennt Workarounds: kernel.apparmor_restrict_unprivileged_userns=0 setzen, was die Systemhärtung schwächt, und weist darauf hin, dass das dokumentierte AppArmor-Profil nur bubblewrap abdeckt und deshalb allein womöglich nicht reicht. Zusammen mit dem Abschnitt zur Einrichtung ergibt das die Falle: Auf einem Standard-Ubuntu ab 24.04 bringt die dokumentierte Lösung bubblewrap zum Laufen und kann den Helfer trotzdem scheitern lassen. Die Ursachenanalyse eines Kommentators verortet den Fehler darin, dass der Helfer setgroups in einem Namespace schreibt, in dem bubblewrap diesen Schreibzugriff schon unmöglich gemacht hat, und berichtet, dass es auch mit dem sysctl auf 0 fehlschlägt. Am 24. Oktober 2026 war das Issue noch offen. Auf meinem Testrechner ließ es sich nicht reproduzieren, und warum, kann ich von einer einzigen Maschine aus nicht sagen. Ein sauberer Lauf hier sagt also wenig über deinen.

Die anderen Beschwerden in diesem Bereich sind meist dokumentiertes Verhalten. Ein Befehl mit einer Variable davor, FOO=bar npm test, wird im Auto-Allow-Modus nicht automatisch genehmigt; Issue 50509 hat das als Bug gemeldet, und die Doku erklärt es inzwischen als beabsichtigt und nennt die Lösung, eine Allow-Regel, die die Zuweisung enthält. docker funktioniert in der Sandbox nicht. Einfaches ssh und die meisten Datenbanktreiber ignorieren die Proxy-Variablen und können sich nicht einmal mit einem erlaubten Host verbinden, während Git über SSH unter Linux funktioniert, sobald der Host erlaubt ist. Die eigene Sandbox von Chromium und rootless Podman scheitern, weil sie User-Namespaces brauchen. Jede Lösung im Abschnitt zur Fehlerbehebung nimmt einen Befehl aus der Sandbox heraus oder hebt eine Einschränkung auf, und sagt, welches von beiden.

Wann ist eine VM oder ein Container besser als die Claude-Code-Sandbox?

Wenn mehr als Shell-Befehle eingegrenzt werden sollen, und das gilt für jeden unbeaufsichtigten Lauf. Die Seite zu den Umgebungen sagt, dass die Bash-Sandbox allein für vollständig unbeaufsichtigte Arbeit nicht ausreicht und dass Sessions mit --dangerously-skip-permissions immer in einem Container, einer VM oder der Sandbox-Runtime laufen sollten. Sie nennt den Classifier des Auto-Modus außerdem eine Kontrolle pro Aktion und keine Isolationsgrenze. Das ist das Argument aus Dein Freigabe-Gate ist jetzt eine Vermutung: Die Grenze für unbeaufsichtigte Läufe setzt man bei der Reichweite an, die das Harness gewährt.

Die Optionen tauschen Umfang gegen Einrichtungsaufwand. Die eingebaute Sandbox ist für beaufsichtigte Arbeit mit weniger Nachfragen gedacht. srt claude steckt den ganzen Prozess ohne Docker in eine einzige bubblewrap-Grenze; laut Doku braucht es Schreibzugriff auf das Projekt, ~/.claude, ~/.claude.json und /tmp, Netzwerkzugriff auf api.anthropic.com und, für OAuth, auf claude.ai und platform.claude.com, dazu ein denyWrite über die Konfigurationspfade, die diese Freigaben offenlegen. Sonst kann eine Session Hooks und MCP-Server hinterlassen, die beim nächsten Mal ohne Sandbox laufen. Ein Dev-Container, etwa der Referenz-Container mit seiner Default-Deny-Firewall, leistet dasselbe mit Docker, und wie stark er ist, hängt davon ab, was du einhängst, welche Zugangsdaten darin liegen und was die Firewall erlaubt. Eine VM bringt einen eigenen Kernel mit, und die Doku empfiehlt eine VM oder eine Cloud-Session für nicht vertrauenswürdige Repositories.

Wo ich die Linie ziehe: Die eingebaute Sandbox ist eine gute Wand um Befehle, bei denen jemand zuschaut. Sobald niemand mehr zuschaut, sollte die Grenze den ganzen Prozess und sein Netzwerk umfassen, und die wichtigste Prüfung ist die, die der Einrichtungsbildschirm nie zeigt: was dein Berechtigungsmodus mit einem Host macht, der nicht auf der Liste steht. Lass Claude die Tests in dem Modus laufen, den du im Alltag nutzt, mit abgeschaltetem Retry, und lehn jedes Angebot ab, sie ohne Sandbox zu wiederholen: touch ~/sandbox-probe für die Schreibwand, curl --noproxy '*' auf eine beliebige Seite für den direkten Weg und ein einfaches curl auf einen Host, den du nie erlaubt hast, für die Allowlist. Glaub ihrer Ausgabe mehr als dem Panel.

01 / Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit@mnardit

02 / Weitere Artikel