Sichere Ausführung: Docker-Sandbox
Wissen
Ein Agent, der Code ausführen, Dateien schreiben oder Shell-Befehle starten kann, ist mächtig -- und gefährlich. Ein falsch konfigurierter Agent kann Dateien löschen, auf sensible Daten zugreifen oder das Host-System destabilisieren. Die Lösung: Jede Tool-Ausführung passiert in einer isolierten Docker-Sandbox.
Warum Sandboxing Pflicht ist
Ohne Sandbox hat ein Agent dieselben Berechtigungen wie der Prozess, der ihn ausführt. Das bedeutet:
- Dateisystem-Zugriff: Der Agent kann jede Datei lesen und schreiben, die der Prozess erreicht
- Netzwerk-Zugriff: Der Agent kann beliebige HTTP-Requests senden -- auch an interne Services
- Prozess-Kontrolle: Der Agent kann andere Prozesse starten oder beenden
- Ressourcen-Verbrauch: Ein Endlos-Loop oder eine Speicher-Explosion betrifft das Host-System
In der Produktion ist das inakzeptabel. Docker-Container bieten eine kontrollierte Umgebung mit definierten Grenzen.
Container-Architektur für Agents
┌─────────────────────────────────────────────┐
│ Host-System │
│ ┌───────────────────────────────────────┐ │
│ │ Agent-Orchestrator │ │
│ │ (Planner + Reviewer laufen hier) │ │
│ └───────────┬───────────────────────────┘ │
│ │ Docker API │
│ ┌───────────▼───────────────────────────┐ │
│ │ Sandbox-Container (kurzlebig) │ │
│ │ - Executor-Tools laufen hier │ │
│ │ - Kein Netzwerk (optional) │ │
│ │ - Begrenzter Speicher (512MB) │ │
│ │ - CPU-Limit (1 Core) │ │
│ │ - Timeout: 30 Sekunden │ │
│ │ - Read-only Dateisystem (+ /tmp) │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Verstehen
Dockerfile für die Agent-Sandbox
# Minimales Image für Agent-Tool-Ausführung
FROM python:3.12-slim
# Sicherheits-Basics: Kein Root-User
RUN groupadd -r sandbox && useradd -r -g sandbox sandbox
# Nur die nötigsten Pakete
RUN pip install --no-cache-dir \
requests==2.32.0 \
beautifulsoup4==4.12.3
# Arbeitsverzeichnis
WORKDIR /workspace
# Kein Root
USER sandbox
# Standard-Timeout: 30 Sekunden
ENV TIMEOUT=30
ENTRYPOINT ["python", "-c"]
Tool-Ausführung im Container
import docker
import json
from dataclasses import dataclass
@dataclass
class SandboxConfig:
image: str = "agent-sandbox:latest"
memory_limit: str = "512m"
nano_cpus: int = 1_000_000_000 # 1 CPU core
timeout: int = 30
network_disabled: bool = True
read_only: bool = True
class DockerSandbox:
def __init__(self, config: SandboxConfig | None = None):
self.config = config or SandboxConfig()
self.client = docker.from_env()
def execute(self, code: str) -> dict:
"""Führt Code sicher in einem Container aus."""
try:
container = self.client.containers.run(
image=self.config.image,
command=code,
mem_limit=self.config.memory_limit,
nano_cpus=self.config.nano_cpus,
network_disabled=self.config.network_disabled,
read_only=self.config.read_only,
tmpfs={"/tmp": "size=64m"},
detach=True,
remove=False
)
# Auf Ergebnis warten mit Timeout
result = container.wait(timeout=self.config.timeout)
logs = container.logs().decode("utf-8")
exit_code = result["StatusCode"]
container.remove()
return {
"success": exit_code == 0,
"output": logs,
"exit_code": exit_code
}
except docker.errors.ContainerError as e:
return {"success": False, "output": str(e), "exit_code": 1}
except Exception as e:
return {"success": False, "output": f"Sandbox-Fehler: {e}", "exit_code": -1}
# Nutzung im Multi-Agent System
sandbox = DockerSandbox()
result = sandbox.execute(
"print('Hallo aus der Sandbox!'); import os; print(os.getcwd())"
)
TypeScript-Variante mit Dockerode
import Docker from "dockerode";
interface SandboxResult {
success: boolean;
output: string;
exitCode: number;
}
class DockerSandbox {
private docker: Docker;
constructor(private image = "agent-sandbox:latest") {
this.docker = new Docker();
}
async execute(code: string): Promise<SandboxResult> {
const container = await this.docker.createContainer({
Image: this.image,
Cmd: [code],
HostConfig: {
Memory: 512 * 1024 * 1024, // 512MB
CpuCount: 1,
NetworkMode: "none",
ReadonlyRootfs: true,
Tmpfs: { "/tmp": "size=64m" },
},
});
await container.start();
const result = await container.wait();
const logs = await container.logs({ stdout: true, stderr: true });
await container.remove();
return {
success: result.StatusCode === 0,
output: logs.toString(),
exitCode: result.StatusCode,
};
}
}
Resource Limits: Die wichtigsten Parameter
| Parameter | Empfohlener Wert | Warum? |
|---|---|---|
| Memory | 512MB | Reicht für die meisten Tool-Aufgaben, verhindert Speicher-Explosionen |
| CPU | 1 Core | Verhindert, dass ein Container das Host-System auslastet |
| Timeout | 30 Sekunden | Fängt Endlos-Loops ab -- die meisten Tools brauchen < 5s |
| Network | Disabled | Verhindert Datenexfiltration und ungewollte API-Calls |
| Filesystem | Read-only + /tmp | Agent kann temporäre Dateien schreiben, aber nichts Persistentes ändern |
| PID-Limit | 50 | Verhindert Fork-Bombs |
Anwenden
Dein Agent muss als Tool eine externe API aufrufen (z.B. GitHub). Wie löst du das in einer Sandbox mit deaktiviertem Netzwerk?
Reflektieren
Sandboxing ist nicht paranoid, sondern professionell. Jeder produktive Agent, der Code ausführt oder mit externen Systemen interagiert, braucht definierte Grenzen. Docker-Container sind der pragmatischste Weg dorthin: Sie sind etabliert, gut dokumentiert und in jede CI/CD-Pipeline integrierbar. Der Aufwand für das Setup amortisiert sich beim ersten verhinderten Incident.