feat: pin bwrap rein runtimes and isolate private state

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
This commit is contained in:
tegwick 2026-09-05 20:36:11 +02:00
parent c2886b2f77
commit d69827aaa2
12 changed files with 639 additions and 9 deletions

View file

@ -15,6 +15,7 @@ from pathlib import Path
from typing import Any
from sandboxer.extensions.base import SandboxExtension
from sandboxer.extensions.runtime import RUNTIME_MOUNT, verified_runtime
from sandboxer.models import Profile
@ -72,6 +73,13 @@ class BwrapExtension(SandboxExtension):
runner = Path(__file__).with_name("bwrap_runner.py")
argv += ["--dir", "/run", "--dir", "/run/sandboxer"]
argv += ["--ro-bind", str(runner), "/run/sandboxer/bwrap_runner.py"]
runtime = verified_runtime(self.config)
if runtime is not None:
workspace = Path(workspace_dir).resolve()
if runtime.is_relative_to(workspace) or workspace.is_relative_to(runtime):
raise ValueError("runtime and workspace must not overlap")
argv += ["--dir", "/opt", "--dir", "/opt/sandboxer"]
argv += ["--ro-bind", str(runtime), RUNTIME_MOUNT]
argv += ["--bind", workspace_dir, workspace_dir]
argv += ["--chdir", workspace_dir]
argv += [
@ -80,6 +88,8 @@ class BwrapExtension(SandboxExtension):
workspace_dir,
f"{workspace_dir}/{self.control_socket_name}",
]
if runtime is not None:
argv.append("--runtime")
return argv
@staticmethod
@ -102,6 +112,15 @@ class BwrapExtension(SandboxExtension):
def provision(
self, profile: Profile, inputs: dict[str, str], host: str
) -> dict[str, str]:
if profile.network.default != "deny" or profile.network.egress:
raise ValueError("bwrap currently supports only default-deny with empty egress")
if profile.setup.secret_refs:
raise ValueError("bwrap has no setup credential delivery contract")
runtime = verified_runtime(self.config)
if runtime is not None and inputs.get("repo"):
source = Path(inputs["repo"]).resolve()
if source.is_relative_to(runtime) or runtime.is_relative_to(source):
raise ValueError("runtime and source checkout must not overlap")
sandbox_id = self.new_sandbox_id(inputs)
workspace_dir = f"{self.base_dir}/{sandbox_id}"
Path(workspace_dir).mkdir(parents=True, exist_ok=True)