Files
2026-07-13 13:12:33 +08:00

998 lines
30 KiB
Python

from __future__ import annotations
import asyncio
import dataclasses
import sys
from pathlib import Path, PureWindowsPath
from types import SimpleNamespace
import pytest
import opensquilla.sandbox as sandbox
from opensquilla.gateway.routing import build_cli_route_envelope, tool_context_from_envelope
from opensquilla.sandbox import integration as integration_mod
from opensquilla.sandbox.backend import bubblewrap as bubblewrap_mod
from opensquilla.sandbox.backend.bubblewrap import BubblewrapBackend, build_bwrap_argv
from opensquilla.sandbox.backend.linux_readiness import probe_bwrap
from opensquilla.sandbox.backend.seatbelt import render_seatbelt_profile
from opensquilla.sandbox.run_context import DomainGrant, RunContext
from opensquilla.sandbox.run_mode import RunMode
from opensquilla.sandbox.types import (
MountSpec,
NetworkMode,
NetworkProxySpec,
ResourceLimits,
SandboxBackendError,
SandboxPolicy,
SandboxRequest,
SandboxResult,
SecurityLevel,
)
from opensquilla.tools.types import current_tool_context
_UNSET = object()
_BWRAP_PROXY_BRIDGE_LINUX_ONLY = pytest.mark.skipif(
not sys.platform.startswith("linux"),
reason="bubblewrap proxy bridge uses Linux namespaces and mount paths",
)
def _proxy_spec(host: str = "127.0.0.1", port: int = 8080) -> NetworkProxySpec:
return NetworkProxySpec(host=host, port=port)
def _policy(
workspace: Path,
*,
network: NetworkMode = NetworkMode.PROXY_ALLOWLIST,
network_proxy: NetworkProxySpec | object = _UNSET,
) -> SandboxPolicy:
kwargs = {
"level": SecurityLevel.STANDARD,
"network": network,
"mounts": (
MountSpec(
host_path=workspace,
sandbox_path=Path("/workspace"),
mode="rw",
required=True,
),
),
"workspace_rw": True,
"tmp_writable": True,
"limits": ResourceLimits(wall_timeout_s=0.1),
"env_allowlist": ("PATH",),
"require_approval": False,
}
if network_proxy is not _UNSET:
kwargs["network_proxy"] = network_proxy
return SandboxPolicy(**kwargs)
def _request(policy: SandboxPolicy, cwd: Path) -> SandboxRequest:
return SandboxRequest(
argv=("sh", "-lc", "echo ok"),
cwd=cwd,
action_kind="network.http",
policy=policy,
env={"PATH": "/bin"},
)
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_treats_sandbox_paths_as_posix_on_windows(
tmp_path: Path,
) -> None:
policy = SandboxPolicy(
level=SecurityLevel.STANDARD,
network=NetworkMode.NONE,
mounts=(
MountSpec(
host_path=tmp_path,
sandbox_path=PureWindowsPath("/workspace"),
mode="rw",
required=True,
),
),
workspace_rw=True,
tmp_writable=True,
limits=ResourceLimits(wall_timeout_s=0.1),
env_allowlist=("PATH",),
require_approval=False,
)
argv = build_bwrap_argv(_request(policy, tmp_path), binary="bwrap")
assert tmp_path.as_posix() in argv
assert "/workspace" not in argv
assert "\\workspace" not in argv
def test_bubblewrap_proxy_allowlist_without_proxy_fails_closed(
tmp_path: Path,
) -> None:
with pytest.raises(SandboxBackendError, match="network proxy"):
build_bwrap_argv(_request(_policy(tmp_path), tmp_path), binary="bwrap")
@pytest.mark.asyncio
async def test_noop_backend_passes_request_proxy_env_to_child(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.safety.sandbox import SandboxResult as SafetySandboxResult
from opensquilla.sandbox.backend import noop as noop_mod
policy = dataclasses.replace(
_policy(tmp_path, network_proxy=_proxy_spec()),
env_allowlist=("PATH", "HTTP_PROXY"),
)
seen: dict[str, object] = {}
def _fake_run_sandboxed(cmd, limits=None, *, stdin=None, env=None):
seen["cmd"] = tuple(cmd)
seen["stdin"] = stdin
seen["env"] = dict(env or {})
return SafetySandboxResult(returncode=0, stdout="ok\n", stderr="")
monkeypatch.setattr(noop_mod, "run_sandboxed", _fake_run_sandboxed)
request = SandboxRequest(
argv=(
sys.executable,
"-c",
"import os; print(os.environ.get('HTTP_PROXY', ''))",
),
cwd=tmp_path,
action_kind="shell.exec",
policy=policy,
env={
"PATH": "/bin",
"HTTP_PROXY": "http://127.0.0.1:18080",
},
)
result = await noop_mod.NoopBackend().run(request)
assert result.returncode == 0
assert seen["stdin"] is None
assert seen["env"] == {
"PATH": "/bin",
"HTTP_PROXY": "http://127.0.0.1:18080",
}
def test_seatbelt_proxy_allowlist_without_proxy_fails_closed(
tmp_path: Path,
) -> None:
with pytest.raises(SandboxBackendError, match="network proxy"):
render_seatbelt_profile(_request(_policy(tmp_path), tmp_path))
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_proxy_allowlist_with_proxy_builds_bridge_argv(
tmp_path: Path,
) -> None:
policy = _policy(tmp_path, network_proxy=_proxy_spec())
bridge_uds_path = tmp_path / "bridge" / "proxy.sock"
bridge_script_path = bridge_uds_path.parent / "inner_bridge.py"
argv = build_bwrap_argv(
_request(policy, tmp_path),
binary="bwrap",
bridge_uds_path=bridge_uds_path,
bridge_script_path=bridge_script_path,
)
separator = argv.index("--")
child_argv = argv[separator + 1 :]
assert "--unshare-net" in argv
assert child_argv[:3] == [
"/usr/bin/python3",
str(bridge_script_path),
"--",
]
assert child_argv[3:] == ["sh", "-lc", "echo ok"]
assert sys.executable not in child_argv
assert "-m" not in child_argv
assert "opensquilla.sandbox.backend.linux_proxy_bridge" not in child_argv
assert argv.count("echo ok") == 1
assert "OPENSQUILLA_SANDBOX_PROXY_UDS" in argv
assert "OPENSQUILLA_SANDBOX_PROXY_PORT" in argv
assert "HTTP_PROXY" in argv
assert "http://127.0.0.1:8080" in argv
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_proxy_allowlist_proxy_env_overrides_user_input(
tmp_path: Path,
) -> None:
policy = _policy(tmp_path, network_proxy=_proxy_spec())
request = _request(policy, tmp_path)
request.env["HTTP_PROXY"] = "http://attacker.invalid:1"
argv = build_bwrap_argv(request, binary="bwrap")
assert "http://127.0.0.1:8080" in argv
assert "http://attacker.invalid:1" not in argv
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_proxy_allowlist_injects_package_manager_proxy_env(
tmp_path: Path,
) -> None:
policy = _policy(tmp_path, network_proxy=_proxy_spec())
argv = build_bwrap_argv(_request(policy, tmp_path), binary="bwrap")
assert "npm_config_proxy" in argv
assert "NODE_USE_ENV_PROXY" in argv
assert "GIT_CONFIG_KEY_0" not in argv
assert "GIT_CONFIG_VALUE_0" not in argv
assert "OPENSQUILLA_SANDBOX_NETWORK" in argv
network_index = argv.index("OPENSQUILLA_SANDBOX_NETWORK")
assert argv[network_index + 1] == "proxy_allowlist"
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_preserves_home_when_workspace_mount_is_canonicalized(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("HOME", "/home/lrk")
policy = SandboxPolicy(
level=SecurityLevel.STANDARD,
network=NetworkMode.NONE,
mounts=(
MountSpec(
host_path=tmp_path,
sandbox_path=Path("/workspace"),
mode="rw",
required=True,
),
),
workspace_rw=True,
tmp_writable=True,
limits=ResourceLimits(wall_timeout_s=0.1),
env_allowlist=("PATH", "HOME"),
require_approval=False,
)
request = SandboxRequest(
argv=("sh", "-lc", "echo $HOME"),
cwd=tmp_path,
action_kind="shell.exec",
policy=policy,
env={"HOME": "/home/lrk"},
)
argv = build_bwrap_argv(request, binary="bwrap")
home_index = argv.index("HOME")
assert argv[home_index + 1] == "/home/lrk"
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_masks_protected_metadata_under_writable_roots(tmp_path: Path) -> None:
policy = SandboxPolicy(
level=SecurityLevel.STANDARD,
network=NetworkMode.NONE,
mounts=(
MountSpec(
host_path=tmp_path,
sandbox_path=Path("/workspace"),
mode="rw",
required=True,
),
MountSpec(
host_path=tmp_path,
sandbox_path=tmp_path,
mode="rw",
required=False,
),
),
workspace_rw=True,
tmp_writable=True,
limits=ResourceLimits(wall_timeout_s=0.1),
env_allowlist=("PATH",),
require_approval=False,
)
argv = build_bwrap_argv(_request(policy, tmp_path), binary="bwrap")
for protected in (
tmp_path / ".git",
tmp_path / ".codex",
tmp_path / ".agents",
):
target = protected.as_posix()
assert target in argv
index = argv.index(target)
assert argv[index - 1] == "--tmpfs"
assert any(
window == ("--remount-ro", target)
for window in zip(argv, argv[1:], strict=False)
)
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_upgrades_duplicate_host_alias_to_writable(tmp_path: Path) -> None:
policy = SandboxPolicy(
level=SecurityLevel.STANDARD,
network=NetworkMode.NONE,
mounts=(
MountSpec(
host_path=tmp_path,
sandbox_path=Path("/workspace"),
mode="rw",
required=True,
),
MountSpec(
host_path=tmp_path,
sandbox_path=tmp_path,
mode="ro",
required=False,
),
),
workspace_rw=True,
tmp_writable=True,
limits=ResourceLimits(wall_timeout_s=0.1),
env_allowlist=("PATH",),
require_approval=False,
)
argv = build_bwrap_argv(_request(policy, tmp_path), binary="bwrap")
absolute_target = tmp_path.as_posix()
assert any(
window == ("--bind", absolute_target, absolute_target)
for window in zip(argv, argv[1:], argv[2:], strict=False)
)
assert not any(
window == ("--ro-bind", absolute_target, absolute_target)
for window in zip(argv, argv[1:], argv[2:], strict=False)
)
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
def test_bubblewrap_available_uses_readiness_probe(monkeypatch) -> None:
monkeypatch.setattr(
bubblewrap_mod,
"probe_bwrap",
lambda: SimpleNamespace(available=True, message="ready"),
)
assert BubblewrapBackend().available() is True
def test_bubblewrap_unavailable_when_user_namespace_probe_fails(monkeypatch) -> None:
monkeypatch.setattr(
bubblewrap_mod,
"probe_bwrap",
lambda: SimpleNamespace(available=False, message="no user namespace"),
)
assert BubblewrapBackend().available() is False
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
@pytest.mark.asyncio
async def test_real_bubblewrap_network_none_cannot_reach_host_loopback(
tmp_path: Path,
) -> None:
probe = probe_bwrap()
if not probe.available:
pytest.skip(probe.message)
seen: list[bytes] = []
async def handle(
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
) -> None:
seen.append(await reader.read(4096))
writer.close()
await writer.wait_closed()
server = await asyncio.start_server(handle, "127.0.0.1", 0)
socket = next(iter(server.sockets or ()), None)
assert socket is not None
host, port = socket.getsockname()[:2]
policy = _policy(tmp_path, network=NetworkMode.NONE, network_proxy=None)
code = (
"import socket\n"
"s = None\n"
"try:\n"
" s = socket.socket()\n"
" s.settimeout(1)\n"
f" s.connect(({host!r}, {int(port)}))\n"
" print('NETWORK_OPEN')\n"
"except Exception as exc:\n"
" print('NETWORK_BLOCKED', type(exc).__name__)\n"
"finally:\n"
" if s is not None:\n"
" s.close()\n"
)
try:
result = await BubblewrapBackend().run(
SandboxRequest(
argv=(sys.executable, "-c", code),
cwd=tmp_path,
action_kind="shell.exec",
policy=policy,
env={},
session_id="s1",
run_mode="trusted",
)
)
finally:
server.close()
await server.wait_closed()
assert result.returncode == 0
assert "NETWORK_BLOCKED" in result.stdout
assert "NETWORK_OPEN" not in result.stdout
assert seen == []
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
@pytest.mark.asyncio
async def test_real_bubblewrap_masks_dynamic_sensitive_system_paths(
tmp_path: Path,
) -> None:
probe = probe_bwrap()
if not probe.available:
pytest.skip(probe.message)
policy = _policy(tmp_path, network=NetworkMode.NONE, network_proxy=None)
code = (
"from pathlib import Path\n"
"target = Path('/' + 'etc') / 'shadow'\n"
"try:\n"
" print(target.read_text(encoding='utf-8')[:32])\n"
"except Exception as exc:\n"
" print('SENSITIVE_BLOCKED', type(exc).__name__)\n"
)
result = await BubblewrapBackend().run(
SandboxRequest(
argv=(sys.executable, "-c", code),
cwd=tmp_path,
action_kind="shell.exec",
policy=policy,
env={},
session_id="s1",
run_mode="trusted",
)
)
assert result.returncode == 0
assert result.stdout == "\n"
assert "root:" not in result.stdout
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
@pytest.mark.asyncio
async def test_real_bubblewrap_proxy_allowlist_reaches_managed_proxy(
tmp_path: Path,
) -> None:
probe = probe_bwrap()
if not probe.available:
pytest.skip(probe.message)
seen: list[bytes] = []
async def handle(
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
) -> None:
seen.append(await reader.read(4096))
writer.write(
b"HTTP/1.1 200 OK\r\n"
b"Content-Length: 2\r\n"
b"Connection: close\r\n"
b"\r\n"
b"ok"
)
await writer.drain()
writer.close()
await writer.wait_closed()
server = await asyncio.start_server(handle, "127.0.0.1", 0)
socket = next(iter(server.sockets or ()), None)
assert socket is not None
host, port = socket.getsockname()[:2]
policy = _policy(
tmp_path,
network=NetworkMode.PROXY_ALLOWLIST,
network_proxy=NetworkProxySpec(host=str(host), port=int(port)),
)
code = (
"import os, socket, urllib.parse\n"
"proxy = os.environ.get('HTTP_PROXY') or os.environ.get('http_proxy')\n"
"print('PROXY', proxy)\n"
"print('NPM_PROXY', os.environ.get('npm_config_proxy', ''))\n"
"print('NODE_USE_ENV_PROXY', os.environ.get('NODE_USE_ENV_PROXY', ''))\n"
"print('OPENSQUILLA_SANDBOX_NETWORK', os.environ.get('OPENSQUILLA_SANDBOX_NETWORK', ''))\n"
"print('CODEX_NETWORK_PROXY_ACTIVE', os.environ.get('CODEX_NETWORK_PROXY_ACTIVE', ''))\n"
"print('CODEX_NETWORK_ALLOW_LOCAL_BINDING', "
"os.environ.get('CODEX_NETWORK_ALLOW_LOCAL_BINDING', ''))\n"
"print('GIT_SSL_KEY', os.environ.get('GIT_CONFIG_KEY_0', ''))\n"
"url = urllib.parse.urlparse(proxy)\n"
"s = socket.create_connection((url.hostname, url.port), timeout=3)\n"
"s.sendall(b'GET http://allowed.test/path HTTP/1.1\\r\\nHost: allowed.test\\r\\n\\r\\n')\n"
"print(s.recv(4096).decode('latin1'))\n"
"s.close()\n"
)
try:
result = await BubblewrapBackend().run(
SandboxRequest(
argv=(sys.executable, "-c", code),
cwd=tmp_path,
action_kind="shell.exec",
policy=policy,
env={"HTTP_PROXY": "http://attacker.invalid:1"},
session_id="s1",
run_mode="trusted",
)
)
finally:
server.close()
await server.wait_closed()
assert result.returncode == 0
assert "PROXY http://127.0.0.1:" in result.stdout
assert "NPM_PROXY http://127.0.0.1:" in result.stdout
assert "NODE_USE_ENV_PROXY 1" in result.stdout
assert "OPENSQUILLA_SANDBOX_NETWORK proxy_allowlist" in result.stdout
assert "CODEX_NETWORK_PROXY_ACTIVE 1" in result.stdout
assert "CODEX_NETWORK_ALLOW_LOCAL_BINDING 0" in result.stdout
assert "GIT_SSL_KEY \n" in result.stdout
assert "attacker.invalid" not in result.stdout
assert "HTTP/1.1 200 OK" in result.stdout
assert seen == [b"GET http://allowed.test/path HTTP/1.1\r\nHost: allowed.test\r\n\r\n"]
def test_linux_proxy_routing_rewrites_proxy_env_to_inner_loopback() -> None:
from opensquilla.sandbox.backend.linux_proxy_routing import proxy_env_for_inner_port
env = proxy_env_for_inner_port(
base_env={"HTTP_PROXY": "http://127.0.0.1:3128"},
port=18080,
)
assert env["HTTP_PROXY"] == "http://127.0.0.1:18080"
assert env["HTTPS_PROXY"] == "http://127.0.0.1:18080"
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
@pytest.mark.asyncio
async def test_bubblewrap_run_invokes_linux_helper_payload(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
policy = _policy(tmp_path, network=NetworkMode.NONE, network_proxy=None)
seen: dict[str, object] = {}
async def fake_helper(payload):
seen["payload"] = payload
return {
"returncode": 0,
"stdout": "ok\n",
"stderr": "",
"wallTimeS": 0.01,
"timedOut": False,
"truncatedStdout": False,
"truncatedStderr": False,
}
monkeypatch.setattr(
bubblewrap_mod,
"probe_bwrap",
lambda: SimpleNamespace(available=True, message="ready"),
)
monkeypatch.setattr(bubblewrap_mod, "_run_linux_helper_payload", fake_helper)
result = await BubblewrapBackend(binary="bwrap").run(_request(policy, tmp_path))
assert result.returncode == 0
assert result.stdout == "ok\n"
payload = seen["payload"]
assert getattr(payload, "operation_type") == "process"
assert getattr(payload, "process").argv == ["sh", "-lc", "echo ok"]
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
@pytest.mark.asyncio
async def test_bubblewrap_run_starts_and_stops_proxy_bridge_for_helper(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
policy = _policy(tmp_path, network_proxy=_proxy_spec(port=18080))
events: list[str] = []
captured: dict[str, object] = {}
class FakeBridge:
def __init__(self, uds_path: Path, upstream_host: str, upstream_port: int) -> None:
self.uds_path = uds_path
self.script_path = uds_path.parent / "inner_bridge.py"
self.upstream_host = upstream_host
self.upstream_port = upstream_port
captured["bridge"] = (uds_path, upstream_host, upstream_port)
async def start(self) -> None:
events.append("bridge.start")
async def stop(self) -> None:
events.append("bridge.stop")
async def fake_helper(payload):
events.append("helper.run")
captured["payload"] = payload
return {
"returncode": 0,
"stdout": "ok\n",
"stderr": "",
"wallTimeS": 0.01,
"timedOut": False,
"truncatedStdout": False,
"truncatedStderr": False,
}
monkeypatch.setattr(
bubblewrap_mod,
"probe_bwrap",
lambda: SimpleNamespace(available=True, message="ready"),
)
monkeypatch.setattr(bubblewrap_mod, "LinuxProxyBridgeHost", FakeBridge)
monkeypatch.setattr(bubblewrap_mod, "_run_linux_helper_payload", fake_helper)
result = await BubblewrapBackend(binary="bwrap").run(_request(policy, tmp_path))
assert events == ["bridge.start", "helper.run", "bridge.stop"]
assert result.returncode == 0
bridge = captured["bridge"]
assert isinstance(bridge, tuple)
assert bridge[1:] == ("127.0.0.1", 18080)
payload = captured["payload"]
bridge_payload = getattr(payload, "policy")["linuxProxyBridge"]
assert bridge_payload["udsPath"].endswith("/proxy.sock")
assert bridge_payload["scriptPath"].endswith("/inner_bridge.py")
assert "execWrapperPath" not in bridge_payload
assert bridge_payload["port"] == 18080
@pytest.mark.asyncio
async def test_linux_helper_payload_times_out_outer_helper(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
payload = bubblewrap_mod.build_process_helper_payload(
_request(_policy(tmp_path, network=NetworkMode.NONE, network_proxy=None), tmp_path)
)
payload = dataclasses.replace(
payload,
policy={**payload.policy, "wallTimeoutS": 0.01},
)
terminated: list[int] = []
class _Proc:
pid = 12345
returncode = None
stdout = None
stderr = None
async def communicate(self):
await asyncio.sleep(10)
return b"", b""
async def fake_create_subprocess_exec(*argv, **kwargs):
assert kwargs["start_new_session"] is True
return _Proc()
async def fake_terminate(proc):
terminated.append(proc.pid)
return b"", b""
monkeypatch.setattr(
bubblewrap_mod.asyncio,
"create_subprocess_exec",
fake_create_subprocess_exec,
)
monkeypatch.setattr(
bubblewrap_mod,
"_outer_helper_timeout_s",
lambda payload: 0.01,
)
monkeypatch.setattr(bubblewrap_mod, "_terminate_process_group", fake_terminate)
with pytest.raises(SandboxBackendError, match="linux helper timed out"):
await bubblewrap_mod._run_linux_helper_payload(payload)
assert terminated == [12345]
@_BWRAP_PROXY_BRIDGE_LINUX_ONLY
@pytest.mark.asyncio
async def test_linux_proxy_bridge_host_writes_and_removes_inner_script(
tmp_path: Path,
) -> None:
bridge = bubblewrap_mod.LinuxProxyBridgeHost(
tmp_path / "proxy.sock",
"127.0.0.1",
9,
)
await bridge.start()
try:
assert bridge.script_path.exists()
script = bridge.script_path.read_text(encoding="utf-8")
assert "def main(" in script
assert "asyncio.start_server" in script
finally:
await bridge.stop()
assert not bridge.script_path.exists()
def test_seatbelt_proxy_allowlist_with_proxy_renders_proxy_only_profile(
tmp_path: Path,
) -> None:
policy = _policy(tmp_path, network_proxy=_proxy_spec())
profile = render_seatbelt_profile(_request(policy, tmp_path))
assert "(allow network-outbound" in profile
assert "localhost:8080" in profile
assert "127.0.0.1:8080" not in profile
assert "(allow network*)" not in profile
assert "(deny network*)" not in profile
@pytest.mark.asyncio
async def test_run_under_backend_populates_proxy_from_current_run_context(
tmp_path: Path,
) -> None:
seen: dict[str, object] = {}
class FakeBackend:
name = "fake"
async def run(self, request: SandboxRequest) -> SandboxResult:
proxy = request.policy.network_proxy
seen["proxy"] = proxy
assert proxy is not None
reader, writer = await asyncio.open_connection(proxy.host, proxy.port)
try:
writer.write(
b"GET http://127.0.0.1/path HTTP/1.1\r\n"
b"\r\n"
)
await writer.drain()
seen["proxy_response"] = await reader.read(4096)
finally:
writer.close()
await writer.wait_closed()
return SandboxResult(
returncode=0,
stdout="ok",
stderr="",
wall_time_s=0.0,
backend_used="fake",
)
runtime = SimpleNamespace(backend=FakeBackend())
policy = _policy(tmp_path, network_proxy=None)
run_context = RunContext(
run_mode=RunMode.STANDARD,
domains=(DomainGrant(domain="allowed.test"),),
)
envelope = build_cli_route_envelope(
session_key="agent:main:webchat:abc",
run_mode="standard",
)
envelope.metadata["sandbox_run_context"] = run_context.to_origin_payload()
ctx = tool_context_from_envelope(envelope, workspace_dir=str(tmp_path))
token = current_tool_context.set(ctx)
try:
result = await integration_mod.run_under_backend(
_request(policy, tmp_path),
runtime=runtime,
)
finally:
current_tool_context.reset(token)
assert result.stdout == "ok"
assert seen["proxy_response"].startswith(b"HTTP/1.1 403")
proxy = seen["proxy"]
assert isinstance(proxy, NetworkProxySpec)
assert proxy.host == "127.0.0.1"
assert proxy.port > 0
@pytest.mark.asyncio
async def test_run_under_backend_proxy_allowlist_without_context_fails_closed(
tmp_path: Path,
) -> None:
class FakeBackend:
name = "fake"
async def run(self, request: SandboxRequest) -> SandboxResult:
raise AssertionError("backend should not run without proxy context")
runtime = SimpleNamespace(backend=FakeBackend())
with pytest.raises(SandboxBackendError, match="Run Context"):
await integration_mod.run_under_backend(
_request(_policy(tmp_path, network_proxy=None), tmp_path),
runtime=runtime,
)
@pytest.mark.asyncio
async def test_non_windows_proxy_allowlist_skips_platform_network_boundary(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls: list[str] = []
class FakeBackend:
name = "bubblewrap"
async def run(self, request: SandboxRequest) -> SandboxResult:
return SandboxResult(
returncode=0,
stdout="ok",
stderr="",
wall_time_s=0.0,
backend_used="bubblewrap",
)
async def fake_prepare_platform_network_boundary(
request: SandboxRequest,
runtime: object,
) -> object:
calls.append("prepare")
return None
monkeypatch.setattr(
integration_mod,
"_prepare_platform_network_boundary",
fake_prepare_platform_network_boundary,
)
policy = _policy(
tmp_path,
network_proxy=NetworkProxySpec(host="127.0.0.1", port=18080),
)
result = await integration_mod.run_under_backend(
_request(policy, tmp_path),
runtime=SimpleNamespace(backend=FakeBackend()),
)
assert result.stdout == "ok"
assert calls == []
def test_policy_positional_description_keeps_legacy_binding(
tmp_path: Path,
) -> None:
policy = SandboxPolicy(
SecurityLevel.STANDARD,
NetworkMode.NONE,
(
MountSpec(
host_path=tmp_path,
sandbox_path=Path("/workspace"),
mode="rw",
required=True,
),
),
True,
True,
ResourceLimits(wall_timeout_s=0.1),
("PATH",),
False,
"legacy description",
)
assert policy.description == "legacy description"
assert policy.network_proxy is None
def test_package_reexports_network_proxy_spec() -> None:
assert sandbox.NetworkProxySpec is NetworkProxySpec
def test_policy_summary_includes_network_proxy_none(tmp_path: Path) -> None:
summary = _policy(tmp_path).summary()
assert summary.get("network_proxy", _UNSET) is None
def test_policy_summary_includes_network_proxy_object(tmp_path: Path) -> None:
summary = _policy(
tmp_path,
network_proxy=_proxy_spec(host="127.0.0.1", port=18080),
).summary()
assert summary["network_proxy"] == {"host": "127.0.0.1", "port": 18080}
def test_windows_proxy_allowlist_preflight_uses_marker_ports(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.sandbox.backend import windows_default_support as support_mod
calls: list[tuple[int, ...]] = []
def _fake_probe_windows_default_support(*, proxy_ports=(), **kwargs):
calls.append(tuple(proxy_ports))
return SimpleNamespace(proxy_allowlist_enforced=tuple(proxy_ports) == (48123,))
monkeypatch.setattr(
integration_mod,
"_windows_allowed_proxy_ports",
lambda runtime: (48123,),
)
monkeypatch.setattr(
support_mod,
"probe_windows_default_support",
_fake_probe_windows_default_support,
)
assert integration_mod._windows_proxy_allowlist_enforced(
SimpleNamespace(backend=SimpleNamespace(name="windows_default")),
)
assert calls == [(48123,)]
@pytest.mark.asyncio
async def test_windows_proxy_allowlist_starts_proxy_on_allowed_marker_port(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
ports_seen: list[int] = []
class FakeBackend:
name = "windows_default"
async def run(self, request: SandboxRequest) -> SandboxResult:
assert request.policy.network_proxy is not None
ports_seen.append(request.policy.network_proxy.port)
return SandboxResult(
returncode=0,
stdout="ok",
stderr="",
wall_time_s=0.0,
backend_used="windows_default",
)
monkeypatch.setattr(
integration_mod,
"_windows_proxy_allowlist_enforced",
lambda runtime, proxy_ports=(): tuple(proxy_ports) == (48123,),
)
monkeypatch.setattr(
integration_mod,
"_windows_allowed_proxy_ports",
lambda runtime: (48123,),
)
policy = _policy(tmp_path, network_proxy=None)
run_context = RunContext(run_mode=RunMode.TRUSTED)
envelope = build_cli_route_envelope(
session_key="agent:main:webchat:abc",
run_mode="trusted",
)
envelope.metadata["sandbox_run_context"] = run_context.to_origin_payload()
ctx = tool_context_from_envelope(envelope, workspace_dir=str(tmp_path))
token = current_tool_context.set(ctx)
try:
result = await integration_mod.run_under_backend(
_request(policy, tmp_path),
runtime=SimpleNamespace(backend=FakeBackend()),
)
finally:
current_tool_context.reset(token)
assert result.stdout == "ok"
assert ports_seen == [48123]