Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

186 lines
6.5 KiB
Python

"""Sandbox: backend selection, restricted subprocess exec, quota, level gating."""
from __future__ import annotations
import asyncio
import pytest
from deeptutor.services.sandbox.backends import BwrapBackend, RestrictedSubprocessBackend
from deeptutor.services.sandbox.config import SandboxSettings, build_backend
from deeptutor.services.sandbox.quota import QuotaExceeded, UserExecQuota
from deeptutor.services.sandbox.service import SandboxService
from deeptutor.services.sandbox.spec import ExecRequest, ExecResult, IsolationLevel, ResourceLimits
def test_backend_selection_runner_url() -> None:
from deeptutor.services.sandbox.backends import RunnerSidecarBackend
settings = SandboxSettings(runner_url="http://sandbox-runner:8900")
backend = build_backend(settings)
assert isinstance(backend, RunnerSidecarBackend)
assert backend.level is IsolationLevel.SYSTEM
def test_backend_selection_none_without_optin() -> None:
# No runner, subprocess not allowed → no backend (on non-bwrap hosts).
settings = SandboxSettings(runner_url="", allow_subprocess=False)
backend = build_backend(settings)
# On a Linux host with bwrap installed this could be BwrapBackend; the
# invariant we assert is that subprocess fallback is NOT silently used.
from deeptutor.services.sandbox.backends import RestrictedSubprocessBackend
assert not isinstance(backend, RestrictedSubprocessBackend)
def test_backend_selection_subprocess_optin() -> None:
settings = SandboxSettings(runner_url="", allow_subprocess=True)
# build_backend prefers bwrap on Linux; force the subprocess path by
# asserting only when no bwrap candidate is chosen.
backend = build_backend(settings)
assert backend is not None
def test_bwrap_binds_usr_local_when_available(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None:
usr = tmp_path / "usr"
usr_local = tmp_path / "usr" / "local"
missing = tmp_path / "missing"
usr_local.mkdir(parents=True)
monkeypatch.setattr(
BwrapBackend,
"_RO_SYSTEM_DIRS",
(str(usr), str(usr_local), str(missing)),
)
argv = BwrapBackend(bwrap_path="bwrap")._build_argv(ExecRequest(command="true"))
usr_index = argv.index(str(usr))
assert argv[usr_index - 1 : usr_index + 2] == ["--ro-bind", str(usr), str(usr)]
assert str(usr_local) in argv
assert str(missing) not in argv
@pytest.mark.asyncio
async def test_restricted_subprocess_runs() -> None:
backend = RestrictedSubprocessBackend()
result = await backend.exec(ExecRequest(command="echo hello"))
assert result.ok
assert "hello" in result.stdout
assert result.exit_code == 0
@pytest.mark.asyncio
async def test_restricted_subprocess_timeout() -> None:
backend = RestrictedSubprocessBackend()
result = await backend.exec(ExecRequest(command="sleep 5", limits=ResourceLimits(timeout_s=1)))
assert result.timed_out
assert result.exit_code == 124
@pytest.mark.asyncio
async def test_service_disabled_when_no_backend() -> None:
svc = SandboxService(SandboxSettings(runner_url="", allow_subprocess=False))
# Force the "no backend" branch deterministically.
svc._backend = None
assert await svc.isolation_level() is IsolationLevel.OFF
result = await svc.run(ExecRequest(command="echo hi"), user_id="u1")
assert not result.ok
assert result.error
@pytest.mark.asyncio
async def test_service_runs_with_subprocess() -> None:
svc = SandboxService(SandboxSettings(allow_subprocess=True))
svc._backend = RestrictedSubprocessBackend()
result = await svc.run(ExecRequest(command="echo sandboxed"), user_id="u1")
assert "sandboxed" in result.stdout
@pytest.mark.asyncio
async def test_quota_rate_limit() -> None:
quota = UserExecQuota(max_concurrent=5, max_per_minute=2)
async with await quota.acquire("u1"):
pass
async with await quota.acquire("u1"):
pass
with pytest.raises(QuotaExceeded):
await quota.acquire("u1")
# a different user is unaffected
async with await quota.acquire("u2"):
pass
@pytest.mark.asyncio
async def test_quota_concurrency_limit() -> None:
quota = UserExecQuota(max_concurrent=1, max_per_minute=100)
lease = await quota.acquire("u1")
with pytest.raises(QuotaExceeded):
await quota.acquire("u1")
await lease.__aexit__(None, None, None)
# slot freed
async with await quota.acquire("u1"):
pass
def test_exec_result_render_truncates() -> None:
result = ExecResult(stdout="x" * 1000, exit_code=0)
rendered = result.render(max_chars=100)
assert "truncated" in rendered
assert len(rendered) < 400
def test_exec_result_render_error() -> None:
assert "boom" in ExecResult(error="boom").render(100)
def test_runner_server_validates_request_shape() -> None:
from deeptutor.services.sandbox.runner import server
assert "command" in server.execute({})["error"]
assert "workdir" in server.execute({"command": "true", "workdir": 123})["error"]
assert "env" in server.execute({"command": "true", "env": ["bad"]})["error"]
assert "mounts" in server.execute({"command": "true", "mounts": {"bad": True}})["error"]
assert "limits" in server.execute({"command": "true", "limits": ["bad"]})["error"]
def test_runner_server_executes_and_truncates_output() -> None:
from deeptutor.services.sandbox.runner import server
result = server.execute(
{
"command": "python -c \"print('x' * 200)\"",
"limits": {"timeout_s": 5, "max_output_chars": 40},
}
)
assert result["exit_code"] == 0
assert result["error"] == ""
assert "truncated" in result["stdout"]
assert len(result["stdout"]) < 120
def test_runner_server_rejects_workdir_outside_allowed_roots(
tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
from deeptutor.services.sandbox.runner import server
allowed = tmp_path / "workspace"
allowed.mkdir()
monkeypatch.setattr(server, "_ALLOWED_WORKDIR_ROOTS", [str(allowed)])
outside = server.execute({"command": "true", "workdir": str(tmp_path / "elsewhere")})
assert "outside the shared workspace roots" in outside["error"]
# Symlinks that point out of the allowed tree must not slip through.
sneaky = allowed / "link"
sneaky.symlink_to(tmp_path)
via_link = server.execute({"command": "true", "workdir": str(sneaky)})
assert "outside the shared workspace roots" in via_link["error"]
inside = server.execute(
{"command": "true", "workdir": str(allowed), "limits": {"timeout_s": 5}}
)
assert inside["error"] == ""
assert inside["exit_code"] == 0