fix: enforce sandbox execution boundary
Some checks failed
ci / validate (push) Has been cancelled

This commit is contained in:
tegwick 2026-08-21 10:40:29 +02:00
parent 1cd890d871
commit f773b5c101
19 changed files with 865 additions and 181 deletions

View file

@ -1,4 +1,5 @@
import json
import os
import subprocess
from unittest.mock import MagicMock, patch
@ -7,6 +8,7 @@ import pytest
from glas_harness.contract import Rein, SandboxHandle, ToolCall
from glas_harness.profiles import ProfileCatalog
from glas_harness.reins.rein_openweights import ReinOpenWeights, ReinOpenWeightsNotInstalled
from glas_harness.transport import ExecutionTransport, TransportError
def test_is_rein_subclass() -> None:
@ -15,8 +17,10 @@ def test_is_rein_subclass() -> None:
def test_bin_raises_if_not_installed() -> None:
rein = ReinOpenWeights(cli_bin="does-not-exist-binary")
transport = MagicMock(spec=ExecutionTransport)
transport.resolve_executable.side_effect = TransportError("missing")
with pytest.raises(ReinOpenWeightsNotInstalled):
rein._bin()
rein._bin(transport)
def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
@ -26,37 +30,45 @@ def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"], cwd=repo, check=True)
rein = ReinOpenWeights()
sandbox = SandboxHandle(sandbox_id="abc", host="localhost", reachability={"workspace_dir": str(repo)})
session = rein.start_session(
profile=ProfileCatalog().resolve("harness.agent-dev-openweights-local@1.0.0")[0],
inputs={"title": "t", "description": "d"},
sandbox=sandbox,
sandbox = SandboxHandle(
sandbox_id="abc",
host="localhost",
reachability={"pid": str(os.getpid()), "workspace_dir": str(repo)},
)
with patch.object(ExecutionTransport, "git_head", return_value="abc123"):
session = rein.start_session(
profile=ProfileCatalog().resolve("harness.agent-dev-openweights-local@1.0.0")[0],
inputs={"title": "t", "description": "d"},
sandbox=sandbox,
)
assert session["target_repo"] == str(repo)
assert session["head_before"]
assert session["head_before"] == "abc123"
assert session["transport"].workspace == str(repo)
task_spec = json.loads(open(session["task_file"]).read())
assert task_spec["title"] == "t"
assert task_spec["target_repo"] == str(repo)
rein.cleanup_session(session)
assert not os.path.exists(session["task_file"])
def test_start_session_requires_resolvable_target_repo() -> None:
rein = ReinOpenWeights()
sandbox = SandboxHandle(sandbox_id="abc", host="localhost", reachability={})
with pytest.raises(ValueError, match="target_repo"):
with pytest.raises(RuntimeError, match="reachability"):
rein.start_session(profile={}, inputs={"title": "t", "description": "d"}, sandbox=sandbox)
def test_dispatch_tool_invokes_rein_openweights_cli() -> None:
rein = ReinOpenWeights()
session = {"task_file": "/tmp/task.json"}
transport = MagicMock(spec=ExecutionTransport)
transport.run.return_value = MagicMock(returncode=0, stdout="ok", stderr="")
session = {"task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30}
fake_result = MagicMock(returncode=0, stdout="ok", stderr="")
with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"):
with patch("glas_harness.reins.rein_openweights.subprocess.run", return_value=fake_result) as run:
result = rein.dispatch_tool(session, ToolCall(name="run_task"))
result = rein.dispatch_tool(session, ToolCall(name="run_task"))
argv = run.call_args.args[0]
argv = transport.run.call_args.args[0]
assert argv[:3] == ["/usr/bin/rein-openweights", "run", "--task-file"]
assert "--no-hub" in argv
assert result.ok is True
@ -70,14 +82,14 @@ def test_dispatch_tool_passes_model_when_set() -> None:
budget_tokens=1234,
tool_profile="green-commit-only",
)
session = {"task_file": "/tmp/task.json"}
fake_result = MagicMock(returncode=0, stdout="ok", stderr="")
transport = MagicMock(spec=ExecutionTransport)
transport.run.return_value = MagicMock(returncode=0, stdout="ok", stderr="")
session = {"task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30}
with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"):
with patch("glas_harness.reins.rein_openweights.subprocess.run", return_value=fake_result) as run:
rein.dispatch_tool(session, ToolCall(name="run_task"))
rein.dispatch_tool(session, ToolCall(name="run_task"))
argv = run.call_args.args[0]
argv = transport.run.call_args.args[0]
assert "--model" in argv
assert "meta-llama/llama-3.1-70b-instruct" in argv
assert argv[argv.index("--max-turns") + 1] == "8"
@ -87,17 +99,34 @@ def test_dispatch_tool_passes_model_when_set() -> None:
def test_dispatch_tool_reports_failure() -> None:
rein = ReinOpenWeights()
session = {"task_file": "/tmp/task.json"}
transport = MagicMock(spec=ExecutionTransport)
transport.run.return_value = MagicMock(returncode=1, stdout="", stderr="boom")
session = {"task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30}
fake_result = MagicMock(returncode=1, stdout="", stderr="boom")
with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"):
with patch("glas_harness.reins.rein_openweights.subprocess.run", return_value=fake_result):
result = rein.dispatch_tool(session, ToolCall(name="run_task"))
result = rein.dispatch_tool(session, ToolCall(name="run_task"))
assert result.ok is False
assert result.error == "boom"
def test_dispatch_tool_enforces_outer_timeout() -> None:
rein = ReinOpenWeights()
transport = MagicMock(spec=ExecutionTransport)
transport.run.side_effect = subprocess.TimeoutExpired("rein-openweights", 30)
session = {
"task_file": "/tmp/task.json",
"transport": transport,
"timeout_seconds": 30,
}
with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"):
with pytest.raises(subprocess.TimeoutExpired):
rein.dispatch_tool(session, ToolCall(name="run_task"))
assert transport.run.call_args.kwargs["timeout"] == 30
def test_end_session_detects_new_commit(tmp_path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
@ -105,12 +134,31 @@ def test_end_session_detects_new_commit(tmp_path) -> None:
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"], cwd=repo, check=True)
rein = ReinOpenWeights()
from glas_harness.reins._shared import git_head
head_before = git_head(str(repo))
head_before = subprocess.run(
["git", "-C", str(repo), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "task"], cwd=repo, check=True)
summary = rein.end_session({"target_repo": str(repo), "head_before": head_before})
transport = MagicMock(spec=ExecutionTransport)
transport.git_head.return_value = subprocess.run(
["git", "-C", str(repo), "rev-parse", "HEAD"],
capture_output=True,
text=True,
check=True,
).stdout.strip()
summary = rein.end_session({"transport": transport, "head_before": head_before})
assert summary.committed is True
assert summary.outcome == "succeeded"
assert summary.commit_sha != head_before
def test_cleanup_session_removes_generated_task_file() -> None:
rein = ReinOpenWeights()
transport = MagicMock(spec=ExecutionTransport)
rein.cleanup_session(
{"transport": transport, "task_file": "/sandbox/task.json", "generated_task_file": True}
)
transport.remove_file.assert_called_once_with("/sandbox/task.json")