security: Sandbox verbirgt API-Schlüssel und Home-Verzeichnis

Erzeugter Code las über pandas.io.common.os – ohne import os, an validate_imports
vorbei – den OPENROUTER_API_KEY und das gesamte Home-Verzeichnis (nachgewiesen).
Kein Netz, aber alles Gedruckte landet in Ergebnis-JSON und Report. In gespeicherten
Ergebnissen fand sich kein Schlüssel.

- Umgebung des Programms ist eine Positivliste (solver_env), auch bei SOLVER_SANDBOX=aus
- bubblewrap verdeckt das Home per --tmpfs; nur sys.prefix/sys.base_prefix wieder lesbar

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-18 13:23:53 +02:00
commit 8dd5073cd1

View file

@ -482,9 +482,18 @@ def sandbox_command(script: Path, tmp: str) -> List[str]:
if SANDBOX == "bwrap":
raise CodeRejected("SOLVER_SANDBOX=bwrap verlangt bubblewrap, es ist aber nicht installiert.")
return [sys.executable, str(script)]
# Das Home-Verzeichnis verdecken (Schlüsseldateien, .env, andere Projekte) – `validate_imports` hält den
# Zugriff nicht auf: beobachtet, dass `pandas.io.common.os` ohne `import os` das ganze Home auflistete.
# Wieder lesbar werden nur die Python-Installationen, die der Solver-Lauf braucht (.venv, Basis-Python).
home = Path.home().resolve()
python = []
for pfad in dict.fromkeys(Path(p).resolve() for p in (sys.prefix, sys.base_prefix)):
if pfad.is_relative_to(home):
python += ["--ro-bind", str(pfad), str(pfad)]
return [
"bwrap", "--unshare-all", "--die-with-parent",
"--ro-bind", "/", "/", # alles lesbar (Python, Solver-Bibliotheken), nichts beschreibbar
"--tmpfs", str(home), *python, # außer dem Home-Verzeichnis
"--bind", tmp, tmp, # einzige Ausnahme: das Arbeitsverzeichnis dieses Laufs
"--dev", "/dev", "--proc", "/proc",
"--setenv", "HOME", tmp, "--setenv", "TMPDIR", tmp,
@ -493,6 +502,16 @@ def sandbox_command(script: Path, tmp: str) -> List[str]:
]
# Umgebung des Solver-Programms: nur diese Variablen, nie die ganze Umgebung – sonst liest der erzeugte Code
# OPENROUTER_API_KEY/GEMINI_API_KEY (nachgewiesen) und kann sie in Ergebnis-JSON oder Report schreiben.
SOLVER_ENV_VARS = ("PATH", "LANG", "LC_ALL", "LC_CTYPE", "OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS")
def solver_env(tmp: str) -> dict:
env = {name: os.environ[name] for name in SOLVER_ENV_VARS if name in os.environ}
return {**env, "HOME": tmp, "TMPDIR": tmp, "PYTHONDONTWRITEBYTECODE": "1"}
def run_solver_code(code: str, timeout: int = 60, data: dict | None = None,
data_files=()) -> SolverRun:
try:
@ -506,7 +525,7 @@ def run_solver_code(code: str, timeout: int = 60, data: dict | None = None,
# Beigelegte Daten ins Laufverzeichnis: es ist das einzige, das die Sandbox beschreibbar bindet
for quelle in data_files or ():
shutil.copy2(quelle, Path(tmp) / Path(quelle).name)
env = dict(os.environ)
env = solver_env(tmp)
if data is not None: # geänderte Eingabedaten ohne neuen LLM-Aufruf
daten_datei = Path(tmp) / "daten.json"
daten_datei.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")