Add owner-mediated bwrap execution boundary

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06def-6490-7033-8448-2eab2d12ed44
This commit is contained in:
tegwick 2026-09-04 22:12:19 +02:00
parent 877676d1f1
commit d79e3fe358
23 changed files with 1321 additions and 86 deletions

View file

@ -2,15 +2,23 @@
from __future__ import annotations
from datetime import UTC, datetime
from datetime import UTC, datetime, timedelta
from pathlib import Path
from threading import Lock
from unittest.mock import patch
import pytest
from sandboxer.core.manager import SandboxManager
from sandboxer.lifecycle.store import SandboxStore
from sandboxer.models import ActorType, Consumer, SandboxCreateRequest, SandboxState, SandboxStatus
from sandboxer.models import (
ActorType,
Consumer,
SandboxCreateRequest,
SandboxExecRequest,
SandboxState,
SandboxStatus,
)
class FakeBackend:
@ -40,6 +48,26 @@ class FakeBackend:
}
class FakeExecBackend:
def supports_execution(self):
return True
def execute(self, handle, command, **kwargs):
return {
"exit_code": 0,
"timed_out": False,
"stdout": "inside\n",
"stderr": "",
"output_truncated": False,
"workspace_dir": handle["workspace_dir"],
}
class FakeNonExecBackend:
def supports_execution(self):
return False
@pytest.fixture
def store(tmp_path: Path) -> SandboxStore:
return SandboxStore(path=tmp_path / "sandboxes.json")
@ -82,4 +110,133 @@ def test_destroy_idempotent(store: SandboxStore) -> None:
store.save(status)
manager = SandboxManager(store=store)
result = manager.destroy("gone1234")
assert result.state == SandboxState.DESTROYED
assert result.state == SandboxState.DESTROYED
def _ready_bwrap_status(*, state: SandboxState = SandboxState.READY) -> SandboxStatus:
now = datetime.now(UTC)
return SandboxStatus(
sandbox_id="bwrap123",
profile_id="profile.bwrap-local",
extension_id="ext.bwrap",
state=state,
consumer=Consumer(
actor=ActorType.AGT,
project="glas-harness",
session_id="session-1",
run_id="run-1",
),
host="localhost",
inputs={"pid": "42", "workspace_dir": "/tmp/sandboxer-bwrap/bwrap123"},
expires_at=now + timedelta(hours=1),
created_at=now,
updated_at=now,
ready_at=now,
)
def _exec_request(**consumer_overrides) -> SandboxExecRequest:
consumer = {
"actor": "agt",
"project": "glas-harness",
"session_id": "session-1",
"run_id": "run-1",
**consumer_overrides,
}
return SandboxExecRequest(
command=["python3", "-V"],
consumer=Consumer.model_validate(consumer),
credential_route_refs=["rein-openweights-openrouter-approle"],
timeout_seconds=30,
)
def test_execute_binds_identity_and_restores_ready_state(store: SandboxStore) -> None:
store.save(_ready_bwrap_status())
manager = SandboxManager(store=store)
backend = FakeExecBackend()
with (
patch("sandboxer.core.manager.resolve_backend", return_value=backend),
patch("sandboxer.core.manager.emit_lifecycle_event", return_value=None),
):
result = manager.execute("bwrap123", _exec_request())
assert result.exit_code == 0
assert result.stdout == "inside\n"
assert result.network_default == "deny"
assert result.network_egress == []
assert result.credential_route_refs == ["rein-openweights-openrouter-approle"]
assert store.get("bwrap123").state == SandboxState.READY
def test_execute_refuses_identity_mismatch(store: SandboxStore) -> None:
store.save(_ready_bwrap_status())
manager = SandboxManager(store=store)
with pytest.raises(PermissionError, match="exactly match"):
manager.execute("bwrap123", _exec_request(run_id="another-run"))
def test_execute_refuses_non_ready_and_expired_sandboxes(store: SandboxStore) -> None:
active = _ready_bwrap_status(state=SandboxState.ACTIVE)
store.save(active)
manager = SandboxManager(store=store)
with pytest.raises(RuntimeError, match="must be ready"):
manager.execute("bwrap123", _exec_request())
def test_execute_refuses_concurrent_command(store: SandboxStore) -> None:
store.save(_ready_bwrap_status())
manager = SandboxManager(store=store)
lock = manager._exec_locks.setdefault("bwrap123", Lock())
lock.acquire()
try:
with pytest.raises(RuntimeError, match="already has an active"):
manager.execute("bwrap123", _exec_request())
finally:
lock.release()
expired = _ready_bwrap_status()
expired.expires_at = datetime.now(UTC) - timedelta(seconds=1)
store.save(expired)
with pytest.raises(RuntimeError, match="TTL has expired"):
manager.execute("bwrap123", _exec_request())
def test_execute_refuses_invalid_credential_route_ref(store: SandboxStore) -> None:
store.save(_ready_bwrap_status())
request = _exec_request()
request.credential_route_refs = ["looks like a secret value"]
with pytest.raises(ValueError, match="value-free catalog"):
SandboxManager(store=store).execute("bwrap123", request)
def test_execute_refuses_extension_without_owner_boundary(store: SandboxStore) -> None:
status = _ready_bwrap_status()
status.profile_id = "profile.compose-e2e"
status.extension_id = "ext.compose-ssh"
store.save(status)
with (
patch("sandboxer.core.manager.resolve_backend", return_value=FakeNonExecBackend()),
pytest.raises(RuntimeError, match="no owner-mediated execution boundary"),
):
SandboxManager(store=store).execute("bwrap123", _exec_request())
def test_execute_restores_ready_after_backend_failure(store: SandboxStore) -> None:
store.save(_ready_bwrap_status())
manager = SandboxManager(store=store)
backend = FakeExecBackend()
backend.execute = lambda *args, **kwargs: (_ for _ in ()).throw(RuntimeError("boom"))
with (
patch("sandboxer.core.manager.resolve_backend", return_value=backend),
patch("sandboxer.core.manager.emit_lifecycle_event", return_value=None),
pytest.raises(RuntimeError, match="boom"),
):
manager.execute("bwrap123", _exec_request())
assert store.get("bwrap123").state == SandboxState.READY