
Ein leichtgewichtiges Sandboxing-Tool zur Durchsetzung von Dateisystem- und Netzwerkbeschränkungen für beliebige Prozesse auf Betriebssystemebene, ohne dass ein Container erforderlich ist.
# Anthropic Sandbox Runtime (srt)
Ein leichtgewichtiges Sandboxing-Tool zur Durchsetzung von Dateisystem- und Netzwerkbeschränkungen für beliebige Prozesse auf Betriebssystemebene, ohne dass ein Container erforderlich ist.
`srt` verwendet native Sandboxing-Primitive des Betriebssystems (`sandbox-exec` unter macOS, `bubblewrap` unter Linux) und proxybasierte Netzwerkfilterung. Es kann verwendet werden, um das Verhalten von Agenten, lokalen MCP-Servern, Bash-Befehlen und beliebigen Prozessen in einer Sandbox zu isolieren.
> **Beta Research Preview**
>
> Die Sandbox Runtime ist eine Research Preview, die für [Claude Code](https://www.claude.com/product/claude-code) entwickelt wurde, um sicherere KI-Agenten zu ermöglichen. Sie wird als frühe Open-Source-Vorschau bereitgestellt, um dem breiteren Ökosystem zu helfen, sicherere agentische Systeme zu entwickeln. Da es sich um eine frühe Research Preview handelt, können sich APIs und Konfigurationsformate weiterentwickeln. Wir freuen uns über Feedback und Beiträge, um KI-Agenten standardmäßig sicherer zu machen!
## Installation
```bash
npm install -g @anthropic-ai/sandbox-runtime
```
## Grundlegende Verwendung
```bash
# Network restrictions
$ srt "curl anthropic.com"
Running: curl anthropic.com
<html>...</html> # Request succeeds
$ srt "curl example.com"
Running: curl example.com
Connection blocked by network allowlist # Request blocked
# Filesystem restrictions
$ srt "cat README.md"
Running: cat README.md
# Anthropic Sandb... # Current directory access allowed
$ srt "cat ~/.ssh/id_rsa"
Running: cat ~/.ssh/id_rsa
cat: /Users/ollie/.ssh/id_rsa: Operation not permitted # Specific file blocked
```
## Überblick
Dieses Paket bietet eine eigenständige Sandbox-Implementierung, die sowohl als CLI-Tool als auch als Bibliothek verwendet werden kann. Es ist mit einer **Secure-by-Default**-Philosophie konzipiert, die auf gängige Entwickler-Anwendungsfälle zugeschnitten ist: Prozesse starten mit minimalem Zugriff, und Sie öffnen explizit nur die Lücken, die Sie benötigen.
**Wichtige Funktionen:**
- **Netzwerkbeschränkungen**: Steuern Sie, auf welche Hosts/Domains über HTTP/HTTPS und andere Protokolle zugegriffen werden kann
- **Dateisystembeschränkungen**: Steuern Sie, welche Dateien/Verzeichnisse gelesen/geschrieben werden können
- **Unix-Socket-Beschränkungen**: Steuern Sie den Zugriff auf lokale IPC-Sockets
- **Verletzungsüberwachung**: Greifen Sie unter macOS auf den Sandbox-Verletzungsprotokollspeicher des Systems zu, um Echtzeitwarnungen zu erhalten
### Beispielanwendungsfall: Sandboxing von MCP-Servern
Ein wichtiger Anwendungsfall ist das Sandboxing von Model Context Protocol (MCP)-Servern, um deren Fähigkeiten einzuschränken. Um beispielsweise den Dateisystem-MCP-Server in eine Sandbox zu packen:
**Ohne Sandboxing** (`.mcp.json`):
```json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem"]
}
}
}
```
**Mit Sandboxing** (`.mcp.json`):
```json
{
"mcpServers": {
"filesystem": {
"command": "srt",
"args": ["npx", "-y", "@modelcontextprotocol/server-filesystem"]
}
}
}
```
Dann konfigurieren Sie Einschränkungen in `~/.srt-settings.json`:
```json
{
"filesystem": {
"denyRead": [],
"allowWrite": ["."],
"denyWrite": ["~/sensitive-folder"]
},
"network": {
"allowedDomains": [],
"deniedDomains": []
}
}
```
Nun wird der MCP-Server daran gehindert, in den gesperrten Pfad zu schreiben:
```
> Write a file to ~/sensitive-folder
✗ Error: EPERM: operation not permitted, open '/Users/ollie/sensitive-folder/test.txt'
```
## Funktionsweise
Die Sandbox verwendet Betriebssystem-Primitive, um Einschränkungen durchzusetzen, die für den gesamten Prozessbaum gelten:
- **macOS**: Verwendet `sandbox-exec` mit dynamisch generierten [Seatbelt-Profilen](https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf)
- **Linux**: Verwendet [bubblewrap](https://github.com/containers/bubblewrap) für die Containerisierung mit Netzwerk-Namespace-Isolation
- **Windows**: Führt den sandboxed Prozess unter einem dedizierten lokalen Benutzerkonto `srt-sandbox` aus, mit einem [Windows Filtering Platform](https://learn.microsoft.com/en-us/windows/win32/fwp/windows-filtering-platform-start-page)-Egress-Zaun, der auf die SID dieses Kontos abgestimmt ist, einem schreibgeschützten Token und expliziten ACEs auf dem Arbeitsverzeichnisbaum
0d1c612947c798aef48e6ab4beb7e8544da9d41a-4096x2305
### Duales Isolationsmodell
Sowohl Dateisystem- als auch Netzwerkisolation sind für effektives Sandboxing erforderlich. Ohne Dateiisolation könnte ein kompromittierter Prozess SSH-Schlüssel oder andere sensible Dateien exfiltrieren. Ohne Netzwerkisolation könnte ein Prozess aus der Sandbox ausbrechen und uneingeschränkten Netzwerkzugriff erlangen.
**Dateisystemisolation** erzwingt Lese- und Schreibbeschränkungen:
- **Lesen** (Deny-then-Allow-Muster): Standardmäßig ist Lesezugriff überall erlaubt. Sie können breite Bereiche verweigern (z. B. `/Users`) und dann bestimmte Pfade darin wieder erlauben (z. B. `.`). `allowRead` hat Vorrang vor `denyRead` — das Gegenteil von Schreiben, wo `denyWrite` Vorrang vor `allowWrite` hat. Ein `denyRead`-Eintrag, der spezifischer ist als der `allowRead`-Bereich, in den er fällt (z. B. `denyRead: ["**/.env"]` oder `["./secrets"]` mit `allowRead: ["."]`), bleibt dennoch verweigert.
- **Schreiben** (Allow-only-Muster): Standardmäßig ist Schreibzugriff überall verweigert. Sie müssen Pfade explizit erlauben (z. B. `.`, `/tmp`). Eine leere Allow-Liste bedeutet keinen Schreibzugriff.
**Netzwerkisolation** (Allow-only-Muster): Standardmäßig ist jeglicher Netzwerkzugriff verweigert. Sie müssen Domains explizit erlauben. Eine leere allowedDomains-Liste bedeutet keinen Netzwerkzugriff. Der Netzwerkverkehr wird über Proxy-Server geleitet, die auf dem Host laufen:
- **Linux**: Anfragen werden über das Dateisystem über einen Unix-Domain-Socket geleitet. Der Netzwerk-Namespace des sandboxed Prozesses wird vollständig entfernt, sodass der gesamte Netzwerkverkehr über die Proxies laufen muss, die auf dem Host laufen (lauschend auf Unix-Sockets, die in die Sandbox bind-gemountet werden)
- **macOS**: Das Seatbelt-Profil erlaubt die Kommunikation nur zu einem bestimmten localhost-Port. Die Proxies lauschen auf diesem Port und schaffen so einen kontrollierten Kanal für jeglichen Netzwerkzugriff
- **Windows**: Ein maschinenweites WFP-Filterset blockiert ausgehende Verbindungen von Prozessen, die unter dem Konto `srt-sandbox` laufen, mit Ausnahme von Loopback zum Proxy-Portbereich. Die Proxies lauschen innerhalb dieses Bereichs und schaffen so einen kontrollierten Kanal für jeglichen Netzwerkzugriff. Die Filter folgen weder einem sandboxed Prozess, der vor dem Erstellen eines Sockets ein anderes Token auf einen Thread setzt, noch Verkehr, den ein Systemdienst in seinem Auftrag überträgt; siehe die Windows [Bekannten Einschränkungen](#known-limitations)
Sowohl HTTP/HTTPS (über HTTP-Proxy) als auch anderer TCP-Verkehr (über SOCKS5-Proxy) werden durch diese Proxies vermittelt, die Ihre Domain-Allowlists und -Denylists durchsetzen.
Weitere Details zum Sandboxing in Claude Code finden Sie unter:
- [Claude Code Sandboxing Documentation](https://docs.claude.com/en/docs/claude-code/sandboxing)
- [Beyond Permission Prompts: Making Claude Code More Secure and Autonomous](https://www.anthropic.com/engineering/claude-code-sandboxing)
## Architektur
```
src/
├── index.ts # Library exports
├── cli.ts # CLI entrypoint (srt command)
├── utils/ # Shared utilities
│ ├── debug.ts # Debug logging
│ ├── settings.ts # Settings reader (permissions + sandbox config)
│ ├── platform.ts # Platform detection
│ └── exec.ts # Command execution utilities
└── sandbox/ # Sandbox implementation
├── sandbox-manager.ts # Main sandbox manager
├── sandbox-schemas.ts # Zod schemas for validation
├── sandbox-violation-store.ts # Violation tracking
├── sandbox-utils.ts # Shared sandbox utilities
├── http-proxy.ts # HTTP/HTTPS proxy for network filtering
├── socks-proxy.ts # SOCKS5 proxy for network filtering
├── linux-sandbox-utils.ts # Linux bubblewrap sandboxing
├── macos-sandbox-utils.ts # macOS sandbox-exec sandboxing
└── windows-sandbox-utils.ts # Windows srt-win sandboxing
```
## Verwendung
### Als CLI-Tool
Der Befehl `srt` (Anthropic Sandbox Runtime) umschließt jeden Befehl mit Sicherheitsgrenzen:
```bash
# Run a command in the sandbox
srt echo "hello world"
# With debug logging
srt --debug curl https://example.com
# Specify custom settings file
srt --settings /path/to/srt-settings.json npm install
```
Die Einstellungsdatei ist optional — ohne Datei unter `~/.srt-settings.json` läuft `srt` mit integrierten Standardwerten: kein Netzwerkzugriff, keine Schreibvorgänge außerhalb der Standard-Schreibpfade und uneingeschränkte Lesezugriffe. Eine Einstellungsdatei, die _vorhanden_ ist, aber leer ist, nicht gelesen werden kann oder die Validierung nicht besteht, ist ein Fehler: `srt` meldet dies und beendet sich, anstatt auf diese Standardwerte zurückzufallen, die eine andere Konfiguration und keine schwächere darstellen — ein Zurückfallen würde die `denyRead`-, `allowRead`- und Anmeldedaten-Regeln der Datei zusammen mit allem anderen, was sie festlegt, verwerfen. Dasselbe gilt für eine mit `--settings` benannte Datei, die ebenfalls existieren muss.
#### Aktualisieren der Konfiguration während der Befehlsausführung: `--control-fd`
`--control-fd <fd>` liest Konfigurationsaktualisierungen aus einem Deskriptor, den der Aufrufer bereits geöffnet hat, ein JSON-Objekt pro Zeile in derselben Form wie die Einstellungsdatei. Jede Zeile ersetzt die gesamte Konfiguration und wird gegen dasselbe Schema validiert, sodass eine Zeile eine vollständige Konfiguration und kein Patch ist: `network` und `filesystem` sind erforderlich, und ein Schlüssel, den die Zeile auslässt, fällt auf seinen Standardwert zurück.
```bash
# fd 3 is the read end of a pipe the caller writes lines to
srt --control-fd 3 -- npm test
```
Was eine akzeptierte Zeile ändert, sobald der Befehl läuft:
- Die Netzwerklisten — `network.allowedDomains`, `deniedDomains` (mit
`deniedDomainReasons`) und `deniedResolvedAddresses` — werden bei der
nächsten Verbindung wirksam, auf jeder Plattform: Der Proxy konsultiert sie
pro Anfrage, und die Prüfung der aufgelösten Adressen wird aus derselben
Zeile neu aufgebaut. Ebenso `network.mitmProxy` und
`network.tlsTerminate.excludeDomains`, die er ebenfalls pro Anfrage liest.
- `credentials.sigv4` wird bei der nächsten Anfrage wirksam, sofern srt mit
einem `credentials`-Block gestartet wurde: Der Signierungs-Hook wird nur
dann installiert und liest die Richtlinien pro Anfrage, statt sie zu
erfassen.
- Nichts anderes ändert sich. Die Dateisystemregeln stehen bereits im
Seatbelt-Profil, im Bubblewrap-argv oder in den Windows-ACEs, die
Credential-Masken wurden beim Umhüllen des Befehls erstellt, und die
laufenden Proxy-Server haben `network.parentProxy` und die Entscheidung, ob
TLS überhaupt terminiert wird, beim Start erfasst.
- Unter Windows bemerkt srt eine Zeile, deren Dateizugriffsmenge
(`filesystem.*` zusammen mit `credentials.files`) sich von der beim Start
angewandten unterscheidet, und meldet dies nur unter `SRT_DEBUG`: Die
ACL-Gewährung gilt sitzungsweit, daher bleibt die beim Start angewandte
Menge in Kraft. Der Rest der Zeile wird wie oben beschrieben angewandt.
Wie srt den Kanal liest und was es ablehnt:
- Der Deskriptor muss eine ganze Zahl **3 oder höher** und lesbar sein —
`0`-`2` sind die Standardströme. srt beendet sich mit einem Fehler, statt
den Befehl auszuführen, wenn es den übergebenen Deskriptor nicht lesen
kann, sodass ein toter Kanal nie für einen lebenden gehalten wird. Ein
Kanal, der stirbt, bevor er ein einziges Update geliefert hat, reißt den
Befehl mit sich; einer, der danach stirbt, meldet dies und lässt den Befehl
unter der zuletzt angewandten Konfiguration weiterlaufen.
- Eine Zeile, die keine gültige Konfiguration ist, wird auf stderr gemeldet
und verworfen; die vorherige Konfiguration bleibt in Kraft. Die Zeile
selbst geht ins Debug-Log statt ins Terminal, und eine leere Zeile wird
stillschweigend ignoriert.
- srt beginnt erst mit dem Lesen, wenn die Sandbox läuft, sodass ein vorher
geschriebenes Update im Kanal wartet, statt verloren zu gehen — und nicht
von der Konfiguration überschrieben werden kann, mit der die Sandbox
startet. Eine solche Zeile kann gelesen werden, während der Befehl noch
umhüllt wird, und ist dann die Konfiguration, mit der er umhüllt wird,
einschließlich der Dateisystemregeln. Ob sie so früh gelesen wird, hängt
davon ab, wie lange das Umhüllen dauert (unter Linux reicht ein
Deny-Glob über einen großen Baum), daher gehört eine Regel, unter der der
Befehl starten muss, in die Einstellungsdatei.
- srt **beendet sich mit dem umhüllten Befehl** und wartet nicht darauf,
dass der Schreiber den Deskriptor schließt. Auch das Ende der Eingabe ist
kein Fehler: Der Befehl läuft unter der zuletzt angewandten Konfiguration
weiter.
- Gib srt ein **dediziertes, schreibgeschütztes Ende**. srt versetzt eine
Pipe oder einen Socket in den nicht blockierenden Modus, und dieses Flag
lebt auf der offenen Dateibeschreibung, sodass alles andere, was dieselbe
Beschreibung hält — ein `exec 3<fifo` einer Shell, ein `pass_fds` eines
Deskriptors, den der Parent weiterhin verwendet —, von da an `EAGAIN` bei
seinen eigenen blockierenden Lesevorgängen erhält.
- Unter macOS und Linux erhält der sandboxed Befehl den Deskriptor nicht:
srt zeigt diesen Slot für den Befehl auf `/dev/null`, sodass nichts
innerhalb der Sandbox die Updates lesen oder eine eigene Konfiguration
schreiben kann. Unter Windows wird der Befehl nur mit den drei
Standardströmen gestartet, sodass der Steuerungsdeskriptor nicht zu den
Deskriptoren gehört, die ihm übergeben werden.
### Als eigenständiger Proxy mit externem Entscheider: `srt proxy`
`srt proxy` (oder die von `bun run build:srt-proxy` erzeugte
Einzeldatei-Ausführungsdatei `srt-proxy`) führt nur SRTs HTTP-Proxy aus, ohne
sandboxed Kindprozess, für einen Host, der die Arbeitslast anderswo ausführt.
Es nimmt auf einem Listening-Socket an, den der Host übergibt, terminiert
jedes CONNECT prozessintern und leitet eine Anfrage nur weiter, wenn ein
separater Entscheider-Prozess, der über einen weiteren geerbten Deskriptor
erreicht wird, sie erlaubt. Siehe [docs/srt-proxy.md](https://github.com/anthropic-experimental/sandbox-runtime/blob/main/docs/srt-proxy.md) für
die Befehlszeile, das Entscheider-Protokoll und das Sicherheitsmodell.
### Als Bibliothek
```typescript
import {
SandboxManager,
type SandboxRuntimeConfig,
} from '@anthropic-ai/sandbox-runtime'
import { spawn } from 'child_process'
// Define your sandbox configuration
const config: SandboxRuntimeConfig = {
network: {
allowedDomains: ['example.com', 'api.github.com'],
deniedDomains: [],
},
filesystem: {
denyRead: ['~/.ssh'],
allowWrite: ['.', '/tmp'],
denyWrite: ['.env'],
},
}
// Initialize the sandbox (starts proxy servers, etc.)
await SandboxManager.initialize(config)
// Wrap a command with sandbox restrictions
const sandboxedCommand = await SandboxManager.wrapWithSandbox(
'curl https://example.com',
)
// Execute the sandboxed command
const child = spawn(sandboxedCommand, { shell: true, stdio: 'inherit' })
// Handle exit and cleanup after child process completes
child.on('exit', async code => {
console.log(`Command exited with code ${code}`)
// Cleanup when done (optional, happens automatically on process exit)
await SandboxManager.reset()
})
```
**Zuordnung von Verstößen (`commandId` / `commandText`).** Verstöße, die während der Ausführung eines umschlossenen Befehls beobachtet werden (Seatbelt-Logzeilen, Seccomp-Ereignisse, Proxy-Ablehnungen), werden unter einem Zuordnungsschlüssel gespeichert, und `annotateStderrWithSandboxFailures(key, stderr)` / `getViolationsForCommand(key)` schlagen sie unter genau diesem Schlüssel nach. Standardmäßig ist der Schlüssel die umschlossene Zeichenkette selbst. Übergeben Sie eine undurchsichtige `commandId` pro Aufruf (z. B. eine Tool-Use-ID), um stattdessen danach zu schlüsseln — empfohlen: Schlüssel werden anhand ihrer ersten 100 Zeichen verglichen, sodass lange Befehle mit gemeinsamem Präfix andernfalls falsch zugeordnet würden, und ein erneuter Lauf desselben Textes würde die Ereignisse des früheren Laufs erben. Wenn die Zeichenkette, die Sie _ausführen_, nicht der Befehl ist, den der Aufruf _repräsentiert_ (z. B. wenn Sie ein zusammengesetztes `source <snapshot> && eval '<cmd>'` umschließen), übergeben Sie zusätzlich `commandText: '<cmd>'`: Dies ist der Text, gegen den `ignoreViolations`-Befehlsmuster abgeglichen werden, und der als `command` jedes Verstoßes gemeldet wird. Eine `commandId`, die Sie an `wrapWithSandbox` übergeben, muss dieselbe nicht-leere Zeichenkette sein, die Sie anschließend an `annotateStderrWithSandboxFailures` / `getViolationsForCommand` übergeben; eine leere wird so behandelt, als gäbe es überhaupt keine `commandId`, sodass der Schlüssel der Befehl ist.
Nur der Schlüssel wird auf 100 Zeichen gekürzt. Ab v0.0.76 ist der gemeldete `command` — und der Text, gegen den `ignoreViolations`-Befehlsmuster abgeglichen werden — der vollständige Befehl für einen ohne `commandId` umschlossenen Aufruf, nicht dessen erste 100 Zeichen; ein Muster kann daher nur mehr unterdrücken als zuvor, niemals weniger. Ein Zuordnungsschlüssel, den kein Aufruf dieses Prozesses registriert hat (die Träger sind von innerhalb der Sandbox beschreibbar), wird bereinigt und auf dieselbe Schlüssellänge gekürzt gemeldet.
```typescript
const wrapped = await SandboxManager.wrapWithSandbox(
assembledCommand, // what actually runs
undefined,
undefined,
undefined,
{ commandId: invocationId, commandText: rawCommand },
)
// ... run it ...
const annotated = SandboxManager.annotateStderrWithSandboxFailures(
invocationId,
stderr,
)
```
**Abfrage zu einem Host, über den keine Regel entscheidet (der Ask-Callback).** Das zweite Argument von `SandboxManager.initialize` ist ein optionaler `SandboxAskCallback`. Er wird zu einem Ziel befragt, über das weder `network.deniedDomains` noch `network.allowedDomains` noch eine befehlsspezifische Allow-List (nächster Abschnitt) entschieden hat, und er wird unter `network.strictAllowlist` niemals befragt. Ohne einen Callback wird ein solches Ziel abgelehnt. Der Callback löst zu einem der folgenden Werte auf:
- `true` – die Verbindung erlauben. Nur der Wert `true` erlaubt.
- `false` – sie ablehnen. Die Verstoßzeile meldet den generischen Grund `user denied`.
- `{ allow: false, reason: '...' }` – sie ablehnen und `reason` in der Verstoßzeile anstelle des generischen Textes melden, damit jeder, der den `<sandbox_violations>`-Block liest (auch ein Modell), erfährt, warum und was stattdessen zu tun ist. Der sandboxed Client liest ihn ebenfalls, als Statusphrase des `403`, das seine CONNECT-Anfrage beantwortet.
Jede andere Antwort lehnt ab, ob truthy oder nicht, und meldet den generischen Grund. Das schließt ein Objekt ein, das einen `reason` trägt, ohne `allow: false` zu sagen, etwa `{ allow: true, reason: '...' }`: sein Grund wird nicht gemeldet.
Der Grund wird bereinigt, bevor er gespeichert wird, so wie der Rest einer Verstoßzeile. Jede Folge von Steuerzeichen (Zeilenumbrüche und Tabs eingeschlossen) oder von unsichtbaren Zeichen (Zero-Width-Zeichen, darunter der Joiner, und Bidi-Steuerzeichen) wird zu einem Leerzeichen; `<` und `>` werden entfernt, sodass `re-run with <host> listed` als `re-run with host listed` gespeichert wird; die Enden werden getrimmt. Das Ergebnis wird auf 500 Zeichen gekürzt, gezählt als UTF-16-Codeeinheiten, so wie `String.prototype.length` sie zählt. Ein Grund, von dem danach nichts übrig bleibt, fällt auf `user denied` zurück. Schreiben Sie ihn als eine Zeile reinen Text.
**Verhaltensänderung in der ersten Version nach 0.0.79:** Der Filter erlaubte früher bei jeder truthy Antwort, sodass ein Callback, der zu einem anderen truthy Wert als `true` auflöste (`1`, ein String, irgendein Objekt), die Verbindung erlaubte. Jetzt lehnt er ab. Aus demselben Grund würde `{ allow: false, reason }`, zurückgegeben an eine ältere Version, dort als Erlaubnis gelesen, weil ein Objekt truthy ist. `SandboxManager.askCallbackDenyReason` ist `true` in einer Version, die das Objekt versteht: prüfen Sie es, bevor Sie eines zurückgeben, und lehnen Sie mit einem einfachen `false` ab, wo es fehlt.
```typescript
const deny = (reason: string) =>
SandboxManager.askCallbackDenyReason
? { allow: false as const, reason }
: false
await SandboxManager.initialize(config, async ({ host, port }) => {
if (await userApproves(host, port)) return true
return deny(`${host} was not approved for this session`)
})
```
**Netzwerk-Allow-Lists pro Befehl (`registerCommandNetworkLists`).** Ein Aufruf kann zusätzlich zur konfigurierten Allow-List eine eigene Allow-List mitführen, solange er läuft:
```typescript
import { randomBytes } from 'node:crypto'
// 128 bits of randomness, 22 characters. See below for why nothing less will do.
const commandId = randomBytes(16).toString('base64url')
const wrapped = await SandboxManager.wrapWithSandbox(
command,
undefined,
undefined,
undefined,
{ commandId },
)
SandboxManager.registerCommandNetworkLists(commandId, {
allowedDomains: ['registry.example.org', '*.cdn.example.org:443'],
})
const child = spawn(wrapped, { shell: true })
const unregister = () => SandboxManager.unregisterCommandNetworkLists(commandId)
child.once('exit', unregister)
child.once('error', unregister) // the child never started
```
Einträge verwenden die Grammatik und den Matcher von `network.allowedDomains` und erhalten dieselbe Validierung. `registerCommandNetworkLists` wirft bei einem ungültigen Eintrag und bei einer `commandId`, die kürzer als 22 Zeichen ist (ein unpaariges Surrogat zählt nicht). Das erneute Registrieren einer id ersetzt ihre Liste; `unregisterCommandNetworkLists` ist ein No-op für eine id, die keine hat; `reset()` entfernt jede Registrierung.
Die Reihenfolge der Auswertung für eine Verbindung ist:
1. ein Treffer in `network.deniedDomains` verweigert;
2. ein Treffer in `network.allowedDomains` erlaubt;
3. `network.strictAllowlist` verweigert;
4. ein Treffer in der für die id, die die Verbindung vorweist, registrierten Liste erlaubt;
5. das Ask-Callback entscheidet, falls eines vorhanden ist, andernfalls wird die Verbindung verweigert.
Ein Eintrag pro Befehl überschreibt also niemals einen konfigurierten `deniedDomains`-Eintrag und wird unter `strictAllowlist` vollständig ignoriert. Er gelangt auch nie in `network.*`: `getConfig()` und `getNetworkRestrictionConfig()` zeigen ihn nicht, und der Standard-`injectHosts`-Scope eines maskierten Credentials, also `network.allowedDomains`, wächst nicht. Ein auf diese Weise erlaubter Host durchläuft beim Verbindungsaufbau dennoch die Prüfung der aufgelösten Adresse, und ein IP-Literal in einer Liste pro Befehl fügt dort keine Ausnahme hinzu.
Was die id sein muss und was dieses Feature nicht ist: Der Proxy erfährt aus dem Proxy-Benutzernamen, zu welcher Invocation eine Verbindung gehört, und der Benutzername wird vom Client innerhalb der Sandbox vorgewiesen. Die id ist daher das **einzige**, was eine Verbindung an eine Allow-Liste bindet. Sie **muss** unerratbar sein: mindestens 128 Bit Zufälligkeit, niemals ein Zähler, ein Zeitstempel oder irgendetwas vom Befehl Abgeleitetes. Ein sandboxed Prozess, der die id einer anderen laufenden Invocation vorweist, erhält deren Allows; dies ist also Attribution, keine Grenze zwischen gleichzeitigen Befehlen einer Sitzung. Registriere unmittelbar vor dem Spawnen des umhüllten Befehls und deregistriere, wenn das Kind beendet wird oder nie gestartet ist: Eine Registrierung, die ihren Befehl überlebt, erweitert das Zeitfenster, in dem es sich lohnt, ihre id vorzuweisen. Wie bei jedem Attributionsschlüssel nehmen nur die ersten 100 Zeichen einer id teil. Halte ids ASCII: Eine Liste unter einer nicht-ASCII-id, deren kodierte Form nicht in den Proxy-Benutzernamen (255 Bytes) passt, wird registriert und kommt nie zur Anwendung.
Keine Liste gilt, solange `network.httpProxyPort` einen externen Proxy benennt: Der Wrapper gibt diesem Proxy keinen Benutzernamen, sodass keine Verbindung eine id trägt. `registerCommandNetworkLists` ist dennoch erfolgreich (und teilt dies in einer Zeile unter `SRT_DEBUG` mit), und jede Verbindung wird entschieden, als ob keine Liste existierte.
#### Verfügbare Exporte
```typescript
// Main sandbox manager
export { SandboxManager } from '@anthropic-ai/sandbox-runtime'
// Violation tracking
export { SandboxViolationStore } from '@anthropic-ai/sandbox-runtime'
// TypeScript types
export type {
SandboxRuntimeConfig,
NetworkConfig,
FilesystemConfig,
IgnoreViolationsConfig,
SandboxAskCallback,
FsReadRestrictionConfig,
FsWriteRestrictionConfig,
NetworkRestrictionConfig,
} from '@anthropic-ai/sandbox-runtime'
```
## Konfiguration
### Speicherort der Einstellungsdatei
Standardmäßig sucht die Sandbox-Laufzeitumgebung nach der Konfiguration unter `~/.srt-settings.json`. Sie können einen benutzerdefinierten Pfad mit dem Flag `--settings` angeben:
```bash
srt --settings /path/to/srt-settings.json <command>
```
### Vollständiges Konfigurationsbeispiel
```json
{
"network": {
"allowedDomains": [
"github.com",
"*.github.com",
"lfs.github.com",
"api.github.com",
"npmjs.org",
"*.npmjs.org"
],
"deniedDomains": ["malicious.com"],
"allowUnixSockets": ["/var/run/docker.sock"],
"allowLocalBinding": false
},
"filesystem": {
"denyRead": ["~/.ssh"],
"allowRead": [],
"allowWrite": [".", "src/", "test/", "/tmp"],
"denyWrite": [".env", "config/production.json"]
},
"ignoreViolations": {
"*": ["/usr/bin", "/System"],
"git push": ["/usr/bin/nc"],
"npm": ["/private/tmp"]
},
"enableWeakerNestedSandbox": false,
"allowNestedUserNamespaces": false,
"enableWeakerNetworkIsolation": false,
"allowAppleEvents": false
}
```
### Konfigurationsoptionen
#### Netzwerkkonfiguration
Verwendet ein **Allow-only-Muster** – jeglicher Netzwerkzugriff ist standardmäßig verweigert.
- `network.allowedDomains` – Array erlaubter Domains (unterstützt Wildcards wie `*.example.com`). Leeres Array = kein Netzwerkzugriff. Ein optionales `:port`-Suffix (`api.example.com:443`, `*.example.com:8443`) beschränkt einen Eintrag auf diesen Zielport; Einträge ohne Port passen auf jeden Port.
- IPv6-Literale müssen in Klammern gesetzt werden, im RFC-3986-Stil: `[::1]`, `[2001:db8::1]:443`. Ein nicht geklammerter Eintrag mit mehreren Doppelpunkten wird als mehrdeutig abgelehnt (`2001:db8::1:443` ist selbst eine gültige Adresse).
- `network.deniedDomains` – Array verweigerter Domains (wird zuerst geprüft, hat Vorrang vor allowedDomains). Gleiches `:port`-Suffix, und ein bloßes `*` (oder `*:22`) wird für Deny-all akzeptiert.
- `network.deniedDomainReasons` – Optionale Zuordnung von einem `deniedDomains`-Eintrag (abgeglichen per exaktem String) zu einem modellseitigen Grund, der in der `<sandbox_violations>`-Zeile erscheint, wenn dieser Eintrag eine Verbindung verweigert – nennen Sie, was blockiert wird, und die zulässige Alternative (z. B. `{"github.com:22": "SSH pushes to GitHub are blocked; use an https:// remote"}`). Einträge ohne Grund melden einen generischen. Für SSH-Ziele (Port 22) wird der Grund zusätzlich in-band übermittelt: Ein SSH-Client, der über einen No-Auth-SOCKS-ProxyCommand (z. B. BSD `nc -X 5`) getunnelt wird, erhält eine SSH-Disconnect vor dem Schlüsselaustausch, deren Beschreibung der Grund ist, den OpenSSH wörtlich ausgibt – halten Sie solche Gründe unter ~400 ASCII-Zeichen, imperativ zuerst, da OpenSSH Nicht-ASCII-Zeichen abschneidet und maskiert. Ein Client, der sich tatsächlich authentifiziert, wie es das von srt injizierte `GIT_SSH_COMMAND` tut, erhält den Grund auf jedem Port als Statusphrase des `403`, das seine CONNECT-Anfrage beantwortet, was jenes Kommando und so mancher HTTP-Client ausgibt. Dort wird er bereinigt und gekürzt wie in der Violation-Zeile, und jedes Nicht-ASCII-Zeichen wird zu `?`, da Clients sich uneinig sind, wie ein solches in einer Statuszeile zu lesen ist.
- `network.allowLocalBinding` – Sandboxed-Befehlen erlauben, lokale Ports direkt zu verwenden (Boolean, Standard: false). macOS: Binden und Annehmen auf jedem lokalen Port sowie Verbinden mit `localhost` auf jedem Port. Windows: Verbinden mit Loopback-Adressen auf jedem TCP- oder UDP-Port; es ist Teil der maschinenweiten Installation, siehe [Lokale Server unter Windows](#local-servers-on-windows). Linux: ignoriert – die Sandbox hat ihren eigenen Netzwerk-Namespace, dessen Loopback immer nutzbar ist und nicht das des Hosts ist.
**Prüfung aufgelöster Adressen.** Die Allow/Deny-Listen gleichen nach _Namen_ ab, aber wer die DNS-Auflösung eines erlaubten Namens (oder eines beliebigen Labels unter einem erlaubten Wildcard) kontrolliert, kontrolliert auch, worauf er aufgelöst wird. Bevor der Proxy also einen erlaubten **Hostnamen** direkt anwählt, löst er ihn einmal auf, verwirft jede Adresse in einer Deny-Menge und verbindet sich mit einer überlebenden Adresse (die Adresse, die die Prüfung bestanden hat, ist die angewählte – es gibt keinen zweiten Lookup). Wenn nichts überlebt, wird die Verbindung wie jede andere Policy-Verweigerung abgelehnt: HTTP/CONNECT erhalten `403` (`X-Proxy-Error: blocked-by-sandbox-runtime`, der Grund im Body und bei einem CONNECT als Statusphrase), SOCKS erhält „connection not allowed by ruleset", und eine Zeile `deny network-outbound host:port (resolved to a loopback address)` – die die Klasse der Adresse nennt (Loopback, Link-Local, die dieses Hosts, Cloud-Metadaten, deny-gelistet, gelistet, …), nicht die Adresse selbst, die nur das Debug-Log enthält – wird im Violation-Store aufgezeichnet.
Die verweigerte Menge ist: Loopback (`127.0.0.0/8`, `::1`), unspezifiziert (`0.0.0.0/8`, `::`), Link-Local (`169.254.0.0/16`, `fe80::/10`), Multicast (`224.0.0.0/4`, `ff00::/8`), Broadcast, die Cloud-Instance-Metadata-/Plattform-Endpunkte, die außerhalb von Link-Local liegen (`100.100.100.200`, `168.63.129.16`, `192.0.0.192`, `fd00:ec2::/32`, `fd20:ce::254`, `fd00:c1::a9fe:a9fe`, `fd00:42::42`), jede Adresse, die derzeit einer der eigenen Netzwerkschnittstellen dieses Hosts zugewiesen ist (ein Dienst, der an `0.0.0.0` gebunden ist, antwortet auf der LAN- oder globalen Adresse genauso wie auf Loopback), jedes IP-Literal, das in `deniedDomains` aufgeführt ist (unter Berücksichtigung seines `:port`, falls vorhanden), und alles in `deniedResolvedAddresses`. IPv4-Einträge treffen auch die IPv6-Formen, die eine IPv4-Adresse tragen — IPv4-gemappte, IPv4-kompatible und IPv4-translatierte Adressen, das NAT64-Well-Known-Präfix (`64:ff9b::/96`) und 6to4 (`2002::/16`) werden anhand der eingebetteten IPv4-Adresse beurteilt. Das Local-Use-NAT64-Präfix `64:ff9b:1::/48` und netzwerkspezifische Präfixe werden nicht dekodiert — ihr Layout (RFC 6052 erlaubt die IPv4 an mehreren Positionen) kann nicht allein aus der Adresse erkannt werden; in einem solchen Netzwerk listen Sie die Übersetzungen der Präfixe der Bereiche auf, die Sie verweigern (z. B. `<prefix>::a00:0/104` für `10.0.0.0/8`). Adressen, die diesen Host erreichen, ohne ihm zugewiesen zu sein — die 1:1-NAT-Public-Adresse einer Cloud-Instance, eine Router-Portweiterleitung, ein Container- oder VM-Host-Gateway-Alias —, sind nicht automatisch abgedeckt; listen Sie sie in `deniedResolvedAddresses` auf.
---
[Read more](https://github.com/anthropic-experimental/sandbox-runtime)