fix: execute local reins through the sandbox owner
Some checks failed
ci / validate (push) Has been cancelled

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
This commit is contained in:
tegwick 2026-09-05 19:16:22 +02:00
parent 92392f75f1
commit 63a7f9f160
14 changed files with 463 additions and 81 deletions

View file

@ -1,6 +1,8 @@
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch
import pytest
from sandboxer.models import Reachability, SandboxState, SandboxStatus
from glas_harness.contract import (
@ -14,6 +16,7 @@ from glas_harness.contract import (
)
from glas_harness.gateway import run_execution, run_task_through_rein
from glas_harness.profiles import ProfileCatalog
from glas_harness.transport import transport_from_sandbox
PROFILE = "harness.agent-dev-local@1.0.0"
@ -127,6 +130,81 @@ def test_run_execution_creates_and_destroys_sandbox() -> None:
assert result.tool_output == "sensitive direct output"
@pytest.mark.parametrize("request_id", ["exact-request", None])
def test_owner_commands_preserve_create_identity_routes_and_timeout(request_id):
manager = MagicMock()
manager.create.return_value = _fake_status()
manager.execute.return_value = MagicMock(
timed_out=False, output_truncated=False, exit_code=0, stdout="", stderr=""
)
catalog = _catalog_with_readiness()
profile, _ = catalog.resolve(PROFILE)
refs = ["proof-route-a", "proof-route-b"]
catalog.profiles()[(profile.id, profile.version)] = profile.model_copy(update={
"credential_route_refs": refs,
"limits": profile.limits.model_copy(update={"timeout_seconds": 7}),
})
class OwnerRein(_FakeRein):
def start_session(self, profile, inputs, sandbox):
assert "_owner_execute" not in sandbox.model_dump()
transport = transport_from_sandbox(sandbox)
transport.run(["printf", "%s", "non-secret"], timeout=30)
path = transport.write_task_file({"title": "private prompt"})
transport.remove_file(path)
return super().start_session(profile, inputs, sandbox)
request = _request().model_copy(update={
"request_id": request_id, "actor": "atm", "project": "exact-project"
})
result = run_execution(request, catalog=catalog, rein=OwnerRein(), manager=manager)
assert result.ok
create_consumer = manager.create.call_args.args[0].consumer
assert create_consumer.actor.value == "atm"
assert create_consumer.project == "exact-project"
assert create_consumer.run_id == result.evidence.request_id
assert create_consumer.session_id is None
assert manager.execute.call_count == 3
for call in manager.execute.call_args_list:
sandbox_id, command = call.args
assert sandbox_id == "sbx1"
assert command.consumer == create_consumer
assert command.credential_route_refs == refs
assert command.timeout_seconds == 7
writer = manager.execute.call_args_list[1].args[1]
assert writer.stdin_text == '{"title": "private prompt"}'
assert "private prompt" not in str(writer.command)
manager.destroy.assert_called_once_with("sbx1")
@pytest.mark.parametrize("failure", ["refused", "timeout", "truncated", "write"])
def test_owner_session_failure_still_destroys_sandbox(failure):
from glas_harness.reins.rein_aharness import ReinAharness
manager = MagicMock()
manager.create.return_value = _fake_status()
good = MagicMock(timed_out=False, output_truncated=False, exit_code=0,
stdout="head-before", stderr="")
if failure == "refused":
manager.execute.side_effect = PermissionError("owner denied execution")
elif failure == "write":
manager.execute.side_effect = [good, MagicMock(
timed_out=False, output_truncated=False, exit_code=1,
stdout="", stderr="task write refused",
)]
else:
manager.execute.return_value = MagicMock(
timed_out=failure == "timeout", output_truncated=failure == "truncated"
)
with patch("glas_harness.transport.subprocess.run") as host_run:
result = run_execution(_request(), catalog=_catalog_with_readiness(),
rein=ReinAharness(), manager=manager)
assert not result.ok
assert result.evidence.failure_stage == "session_start"
manager.destroy.assert_called_once_with("sbx1")
host_run.assert_not_called()
def test_run_execution_normalizes_execution_failure_and_tears_down() -> None:
manager = MagicMock()
manager.create.return_value = _fake_status()

View file

@ -45,6 +45,10 @@ def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
host="localhost",
reachability={"pid": str(os.getpid()), "workspace_dir": str(repo)},
)
sandbox._owner_execute = MagicMock(
return_value=MagicMock(timed_out=False, output_truncated=False, exit_code=0,
stdout="", stderr="")
)
with patch.object(ExecutionTransport, "git_head", return_value="abc123"):
session = rein.start_session(
profile=ProfileCatalog().resolve("harness.agent-dev-local@1.0.0")[0],
@ -55,11 +59,11 @@ def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
assert session["target_repo"] == str(repo)
assert session["head_before"] == "abc123"
assert session["transport"].workspace == str(repo)
task_spec = json.loads(open(session["task_file"]).read())
task_spec = json.loads(sandbox._owner_execute.call_args.args[1])
assert task_spec["title"] == "t"
assert task_spec["target_repo"] == str(repo)
rein.cleanup_session(session)
assert not os.path.exists(session["task_file"])
assert sandbox._owner_execute.call_args.args[0] == ["rm", "-f", "--", session["task_file"]]
def test_start_session_requires_resolvable_target_repo() -> None:

View file

@ -45,6 +45,10 @@ def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
host="localhost",
reachability={"pid": str(os.getpid()), "workspace_dir": str(repo)},
)
sandbox._owner_execute = MagicMock(
return_value=MagicMock(timed_out=False, output_truncated=False, exit_code=0,
stdout="", stderr="")
)
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],
@ -55,11 +59,11 @@ def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
assert session["target_repo"] == str(repo)
assert session["head_before"] == "abc123"
assert session["transport"].workspace == str(repo)
task_spec = json.loads(open(session["task_file"]).read())
task_spec = json.loads(sandbox._owner_execute.call_args.args[1])
assert task_spec["title"] == "t"
assert task_spec["target_repo"] == str(repo)
rein.cleanup_session(session)
assert not os.path.exists(session["task_file"])
assert sandbox._owner_execute.call_args.args[0] == ["rm", "-f", "--", session["task_file"]]
def test_start_session_requires_resolvable_target_repo() -> None:

View file

@ -14,28 +14,18 @@ def test_local_namespace_transport_requires_pid_and_workspace(tmp_path: Path) ->
host="localhost",
reachability={"pid": "4321", "workspace_dir": str(tmp_path)},
)
sandbox._owner_execute = MagicMock(return_value=MagicMock(
timed_out=False, output_truncated=False, exit_code=0, stdout="ok", stderr=""
))
transport = transport_from_sandbox(sandbox)
assert transport.kind == "local_namespace"
assert transport.workspace == str(tmp_path)
assert transport.command(["git", "status"]) == [
"nsenter",
"--target",
"4321",
"--mount",
"--pid",
"--net",
"--uts",
"--ipc",
"--",
"sh",
"-c",
'cd "$1" && shift && exec "$@"',
"sh",
str(tmp_path),
"git",
"status",
]
with patch("glas_harness.transport.subprocess.run") as run:
assert transport.run(["git", "status"], timeout=30).stdout == "ok"
run.assert_not_called()
sandbox._owner_execute.assert_called_once_with(["git", "status"], None, 30)
with pytest.raises(TransportError, match="owner execution"):
transport.command(["git", "status"])
def test_remote_transport_wraps_command_without_local_shell() -> None:
@ -100,20 +90,47 @@ def test_incomplete_or_ambiguous_reachability_fails_closed(reachability) -> None
)
def test_local_task_file_is_private_and_removable(tmp_path: Path) -> None:
(tmp_path / ".git").mkdir()
def test_local_task_file_uses_owner_stdin_and_cleanup(tmp_path: Path) -> None:
owner = MagicMock(return_value=MagicMock(
timed_out=False, output_truncated=False, exit_code=0, stdout="", stderr=""
))
transport = ExecutionTransport(
kind="local_namespace", workspace=str(tmp_path), pid=123
kind="local_namespace", workspace=str(tmp_path), pid=123, owner_execute=owner,
timeout_seconds=7,
)
task_path = transport.write_task_file({"title": "bounded", "description": "safe"})
path = Path(task_path)
assert path.parent == tmp_path / ".git"
assert path.stat().st_mode & 0o777 == 0o600
assert json.loads(path.read_text())["title"] == "bounded"
task_path = transport.write_task_file({"title": "private prompt"})
command, stdin, timeout = owner.call_args.args
assert "umask 077; set -C; cat" in command[2]
assert "private prompt" not in str(command)
assert json.loads(stdin) == {"title": "private prompt"}
assert timeout == 7
assert Path(task_path).parent == tmp_path / ".git"
assert not Path(task_path).exists() # No host write, even with a fake owner.
transport.remove_file(task_path)
assert not path.exists()
owner.assert_called_with(["rm", "-f", "--", task_path], None, 7)
def test_local_descriptor_without_owner_fails_closed():
with pytest.raises(TransportError, match="owner execution binding"):
transport_from_sandbox(SandboxHandle(
sandbox_id="sbx", host="localhost",
reachability={"pid": "123", "workspace_dir": "/tmp/ws"},
))
@pytest.mark.parametrize("timed_out,truncated", [(True, False), (False, True)])
def test_owner_timeout_and_truncation_fail_closed(timed_out, truncated):
import subprocess
owner = MagicMock(return_value=MagicMock(
timed_out=timed_out, output_truncated=truncated
))
transport = ExecutionTransport(
kind="local_namespace", workspace="/tmp/ws", owner_execute=owner,
timeout_seconds=3,
)
with pytest.raises(subprocess.TimeoutExpired if timed_out else TransportError):
transport.run(["true"], timeout=30)
owner.assert_called_once_with(["true"], None, 3)
def test_remote_task_file_uses_ssh_stdin_and_cleanup() -> None: