from __future__ import annotations import json import os import sys import types from pathlib import Path import pytest import EvoScientist.native_sandbox as sandbox def _installation(tmp_path: Path) -> sandbox.NativeSandboxInstallation: package = tmp_path / "package" package.mkdir() srt = tmp_path / "srt" srt.touch(mode=0o700) return sandbox.NativeSandboxInstallation( srt=srt, package_root=package, path_env="/usr/bin:/bin", system_read_paths=("/usr", "/bin", "/dev/null"), ) def test_existing_read_paths_resolve_and_deduplicate_symlink_aliases(tmp_path: Path): usr = tmp_path / "usr" usr.mkdir() bin_alias = tmp_path / "bin" bin_alias.symlink_to(usr, target_is_directory=True) missing = tmp_path / "missing" paths = sandbox._existing_resolved_paths((str(usr), str(bin_alias), str(missing))) assert paths == (str(usr.resolve()),) def test_packaged_runtime_assets_drive_user_data_install(monkeypatch, tmp_path: Path): monkeypatch.delenv("EVOSCIENTIST_NATIVE_SANDBOX_ROOT", raising=False) monkeypatch.setattr("EvoScientist.paths.DATA_DIR", tmp_path) assets = sandbox._runtime_assets_root() assert (assets / "package.json").is_file() assert (assets / "package-lock.json").is_file() assert (assets / "patch_merged_usr.py").is_file() assert sandbox._runtime_root() == (tmp_path / "runtime" / "native-sandbox") def test_runtime_installer_uses_lockfile_and_applies_packaged_patch( monkeypatch, tmp_path: Path ): root = tmp_path / "runtime" / "native-sandbox" calls: list[list[str]] = [] def fake_run(command, *, cwd, **_kwargs): calls.append(command) package = ( Path(cwd) / "node_modules" / "@anthropic-ai" / "sandbox-runtime" ) target = package / "dist" / "sandbox" / "linux-sandbox-utils.js" target.parent.mkdir(parents=True) package.joinpath("package.json").write_text( json.dumps({"version": sandbox._PINNED_SRT_VERSION}), encoding="utf-8" ) source = sandbox._runtime_assets_root().joinpath("patch_merged_usr.py") namespace: dict[str, object] = {} exec(source.read_text(encoding="utf-8"), namespace) target.write_text( namespace["_ORIGINAL"] + namespace["_TMPFS_ORIGINAL"], encoding="utf-8", ) binary = Path(cwd) / "node_modules" / ".bin" / "srt" binary.parent.mkdir(parents=True) binary.write_text("#!/bin/sh\n", encoding="ascii") binary.chmod(0o700) return types.SimpleNamespace(returncode=0, stdout="", stderr="") monkeypatch.setattr(sandbox, "_runtime_root", lambda: root) monkeypatch.setattr(sandbox.shutil, "which", lambda name, path=None: f"/bin/{name}") monkeypatch.setattr(sandbox.subprocess, "run", fake_run) installed = sandbox._ensure_runtime_installed() assert installed == root assert calls == [ [ "/bin/npm", "ci", "--ignore-scripts", "--omit=dev", "--no-audit", "--no-fund", ] ] assert json.loads((root / "package-lock.json").read_text(encoding="utf-8"))[ "lockfileVersion" ] == 3 patched = root.joinpath( "node_modules/@anthropic-ai/sandbox-runtime/dist/sandbox/" "linux-sandbox-utils.js" ).read_text(encoding="utf-8") assert "rootChildIsAllowedSymlink" in patched assert "--remount-ro" in patched assert os.access(root / "node_modules" / ".bin" / "srt", os.X_OK) def test_runtime_installer_preserves_existing_runtime_when_reinstall_fails( monkeypatch, tmp_path: Path ): root = tmp_path / "runtime" / "native-sandbox" root.mkdir(parents=True) sentinel = root / "keep.txt" sentinel.write_text("existing", encoding="utf-8") monkeypatch.setattr(sandbox, "_runtime_root", lambda: root) monkeypatch.setattr(sandbox, "_runtime_install_complete", lambda candidate: False) monkeypatch.setattr(sandbox.shutil, "which", lambda name, path=None: f"/bin/{name}") def failed_run(*_args, **_kwargs): return types.SimpleNamespace(returncode=1, stdout="", stderr="install failed") monkeypatch.setattr(sandbox.subprocess, "run", failed_run) with pytest.raises(sandbox.NativeSandboxUnavailable, match="npm ci failed"): sandbox._ensure_runtime_installed() assert sentinel.read_text(encoding="utf-8") == "existing" assert not list(root.parent.glob(".native-sandbox-install-*")) def test_policy_denies_root_and_only_writes_scope_and_command_tmp(tmp_path: Path): files = tmp_path / "files" command_tmp = tmp_path / "runtime" / "tmp" / "run" files.mkdir() command_tmp.mkdir(parents=True) policy = sandbox._sandbox_settings(_installation(tmp_path), files, command_tmp) assert policy["filesystem"]["denyRead"] == ["/"] assert str(files) in policy["filesystem"]["allowRead"] assert str(command_tmp) in policy["filesystem"]["allowRead"] assert policy["filesystem"]["allowWrite"] == [ str(files), str(command_tmp), "/dev/null", ] assert policy["filesystem"]["denyWrite"] == [ str(files / "uploads"), "/tmp/claude", "/private/tmp/claude", "/dev/tty", "/dev/dtracehelper", "/dev/autofs_nowait", ] assert policy["network"]["allowedDomains"] == [] assert policy["network"]["allowAllUnixSockets"] is False assert policy["allowAppleEvents"] is False assert "control" not in json.dumps(policy) def test_weaker_nested_mode_requires_explicit_environment_opt_in(tmp_path: Path, monkeypatch): monkeypatch.delenv("EVOSCIENTIST_NATIVE_SANDBOX_WEAKER_NESTED", raising=False) files = tmp_path / "files" command_tmp = tmp_path / "runtime" / "tmp" / "run" files.mkdir() command_tmp.mkdir(parents=True) installation = _installation(tmp_path) assert sandbox._sandbox_settings(installation, files, command_tmp)[ "enableWeakerNestedSandbox" ] is False monkeypatch.setenv("EVOSCIENTIST_NATIVE_SANDBOX_WEAKER_NESTED", "true") assert sandbox._sandbox_settings(installation, files, command_tmp)[ "enableWeakerNestedSandbox" ] is True def test_network_preflight_probe_accepts_kernel_denied_unix_socket(monkeypatch): monkeypatch.delenv("OPENAI_API_KEY", raising=False) class FakeSocket: def __init__(self, family=None, *_args): if family == 1: raise PermissionError("blocked by seccomp") def connect_ex(self, _address): return 1 def close(self): return None fake_socket = types.SimpleNamespace( AF_UNIX=1, socket=lambda family=None, *args: FakeSocket(family, *args), ) monkeypatch.setitem(sys.modules, "socket", fake_socket) namespace: dict[str, object] = {} exec(sandbox._network_preflight_probe(1234, Path("/blocked.sock")), namespace) def test_clean_environment_does_not_inherit_secrets(tmp_path: Path, monkeypatch): command_tmp = tmp_path / "tmp" (command_tmp / "home").mkdir(parents=True) (command_tmp / "tmp").mkdir() monkeypatch.setenv("OPENAI_API_KEY", "secret") environment = sandbox._clean_environment(_installation(tmp_path), command_tmp) assert set(environment) == {"PATH", "HOME", "TMPDIR", "WORKSPACE", "LANG", "LC_ALL"} assert "OPENAI_API_KEY" not in environment assert environment["WORKSPACE"] == "." def test_executor_requires_control_directory_outside_files(tmp_path: Path): files = tmp_path / "files" files.mkdir() runtime = files / "runtime" runtime.mkdir() with pytest.raises(sandbox.NativeSandboxUnavailable): sandbox.NativeSandboxExecutor(files, runtime) def test_readiness_is_cached_and_failure_is_fail_closed(monkeypatch): sandbox._reset_native_sandbox_readiness_for_tests() calls = {"install": 0, "preflight": 0} def install(): calls["install"] += 1 return object() def preflight(_installation): calls["preflight"] += 1 monkeypatch.setattr(sandbox, "_assert_install_contract", install) monkeypatch.setattr(sandbox, "_run_preflight", preflight) sandbox.ensure_native_sandbox_ready() sandbox.ensure_native_sandbox_ready() assert calls == {"install": 1, "preflight": 1} sandbox._reset_native_sandbox_readiness_for_tests() monkeypatch.setattr( sandbox, "_run_preflight", lambda _installation: (_ for _ in ()).throw( sandbox.NativeSandboxUnavailable("failed once") ), ) with pytest.raises(sandbox.NativeSandboxUnavailable, match="failed once"): sandbox.ensure_native_sandbox_ready() with pytest.raises(sandbox.NativeSandboxUnavailable, match="failed once"): sandbox.ensure_native_sandbox_ready() assert calls["install"] == 2 sandbox._reset_native_sandbox_readiness_for_tests()