8Regression tests for :mod:`oct.mcp.sandbox` — ``SandboxExecutor`` and
9environment sanitisation.
13- Verify environment sanitisation strips sensitive variables.
14- Verify timeout enforcement and output-size limits.
15- Verify T-04 (command chaining) and shell=False enforcement.
16- Verify exit-code forwarding from subprocess.
23 L3 — assertion details
28- Tests use mock subprocess; no real commands are executed.
31from __future__
import annotations
35from pathlib
import Path
36from unittest.mock
import MagicMock, patch
44 _SENSITIVE_ENV_SUBSTRINGS,
54 monkeypatch.setenv(
"PYTHONPATH",
"/some/path")
55 env = _sanitise_env(tmp_path)
56 assert "PYTHONPATH" not in env
59 env = _sanitise_env(tmp_path)
63 env = _sanitise_env(tmp_path)
65 assert "HOME" in env
or "USERPROFILE" in env
68 monkeypatch.setenv(
"MY_API_KEY",
"topsecret")
69 env = _sanitise_env(tmp_path)
70 assert "MY_API_KEY" not in env
73 monkeypatch.setenv(
"DB_PASSWORD",
"hunter2")
74 env = _sanitise_env(tmp_path)
75 assert "DB_PASSWORD" not in env
78 monkeypatch.setenv(
"GITHUB_TOKEN",
"ghp_abc")
79 env = _sanitise_env(tmp_path)
80 assert "GITHUB_TOKEN" not in env
83 env = _sanitise_env(tmp_path)
84 assert env.get(
"OCT_MCP_CWD") == str(tmp_path)
87 monkeypatch.setenv(
"VIRTUAL_ENV",
"/venv")
88 env = _sanitise_env(tmp_path)
89 assert "VIRTUAL_ENV" in env
92 env = _sanitise_env(tmp_path)
93 assert env
is not os.environ
103 assert r.timed_out
is False
104 assert r.output_truncated
is False
105 assert r.error_message ==
""
115 result = executor.run(
116 [sys.executable,
"-c",
"print('hello')"],
120 assert result.exit_code == 0
121 assert "hello" in result.stdout
125 result = executor.run(
126 [sys.executable,
"-c",
"import sys; sys.exit(42)"],
130 assert result.exit_code == 42
134 result = executor.run(
135 [sys.executable,
"-c",
"import sys; print('err', file=sys.stderr)"],
139 assert "err" in result.stderr
142 """T-04: shell=True must never be used — semicolons are not executed."""
146 result = executor.run(
147 [sys.executable,
"-c",
"print('safe')"],
151 assert result.exit_code == 0
152 assert result.timed_out
is False
156 result = executor.run(
157 [
"this_command_does_not_exist_xyz123"],
161 assert result.exit_code == 1
162 assert result.error_message !=
""
167 result = executor.run(
168 [
"nonexistent_cmd_abc"],
172 assert isinstance(result, SandboxResult)
176 result = executor.run(
177 [sys.executable,
"-c",
"import os; print(os.getcwd())"],
181 assert result.exit_code == 0
183 assert Path(result.stdout.strip()).resolve() == tmp_path.resolve()
193 result = executor.run(
194 [sys.executable,
"-c",
"import time; time.sleep(30)"],
198 assert result.timed_out
is True
199 assert result.exit_code == 1
203 result = executor.run(
204 [sys.executable,
"-c",
"print('done')"],
208 assert result.timed_out
is False
218 result = executor.run(
219 [sys.executable,
"-c",
"print('short')"],
222 max_output_bytes=1024,
224 assert result.output_truncated
is False
229 result = executor.run(
230 [sys.executable,
"-c",
"print('x' * 2000)"],
233 max_output_bytes=100,
235 assert result.output_truncated
is True
237 combined = len((result.stdout + result.stderr).encode(
"utf-8"))
238 assert combined <= 200
test_output_over_limit_truncated(self, Path tmp_path)
test_output_within_limit_not_truncated(self, Path tmp_path)
test_never_raises(self, Path tmp_path)
test_shell_false_enforced(self, Path tmp_path)
test_working_directory_is_set(self, Path tmp_path)
test_captures_exit_code_nonzero(self, Path tmp_path)
test_runs_simple_command(self, Path tmp_path)
test_command_not_found_returns_error(self, Path tmp_path)
test_captures_stderr(self, Path tmp_path)
test_timeout_returns_timed_out_true(self, Path tmp_path)
test_timeout_not_flagged_for_fast_command(self, Path tmp_path)
test_virtual_env_preserved(self, Path tmp_path, monkeypatch)
test_home_preserved(self, Path tmp_path)
test_token_key_stripped(self, Path tmp_path, monkeypatch)
test_password_key_stripped(self, Path tmp_path, monkeypatch)
test_pythonpath_stripped(self, Path tmp_path, monkeypatch)
test_cwd_injected(self, Path tmp_path)
test_path_preserved(self, Path tmp_path)
test_returns_new_dict(self, Path tmp_path)
test_secret_key_stripped(self, Path tmp_path, monkeypatch)