gabriel / muse public

test_security_test_runner.py file-level

at sha256:f · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 πŸ’₯ blast risk
sha256:4 Merge branch 'dev' into main · gabriel · Jun 17, 2026
1 """Security tests: test_cmd extra_args injection into pytest subprocess.
2
3 muse/core/test_runner.py runs pytest as an isolated subprocess with
4 shell=False (safe) but passes user-supplied extra_args directly to cmd.
5 An attacker can inject pytest flags that alter test discovery and import
6 semantics:
7
8 --rootdir=/ β†’ changes pytest's root, may discover and run tests
9 outside the repo tree
10 --import-mode=importlib β†’ alters Python import resolution
11 --override-ini=key=value β†’ override pyproject.toml settings
12 --confcutdir=/ β†’ expands conftest search beyond the repo
13
14 The fix: filter extra_args through _SAFE_PYTEST_FLAG_PREFIXES allowlist
15 before building the subprocess command.
16 """
17
18 from __future__ import annotations
19
20 from muse.core.test_runner import (
21 RunConfig,
22 _SAFE_PYTEST_FLAG_PREFIXES,
23 _filter_extra_args,
24 )
25
26
27 # ---------------------------------------------------------------------------
28 # Β§ 1 β€” _filter_extra_args strips dangerous pytest flags
29 # ---------------------------------------------------------------------------
30
31 class TestFilterExtraArgs:
32 """_filter_extra_args must strip dangerous pytest flags."""
33
34 def test_function_is_importable(self) -> None:
35 assert callable(_filter_extra_args)
36
37 def test_rootdir_stripped(self) -> None:
38 result = _filter_extra_args(["--rootdir=/", "-v"])
39 assert "--rootdir=/" not in result
40 assert "-v" in result
41
42 def test_rootdir_with_value_stripped(self) -> None:
43 result = _filter_extra_args(["--rootdir", "/etc", "-x"])
44 flat = " ".join(result)
45 assert "--rootdir" not in flat
46 assert "/etc" not in flat
47 assert "-x" in result
48
49 def test_import_mode_stripped(self) -> None:
50 result = _filter_extra_args(["--import-mode=importlib", "-v"])
51 flat = " ".join(result)
52 assert "--import-mode" not in flat
53 assert "-v" in result
54
55 def test_override_ini_stripped(self) -> None:
56 result = _filter_extra_args(["--override-ini=addopts=--forked"])
57 assert not any("override-ini" in a for a in result)
58
59 def test_confcutdir_stripped(self) -> None:
60 result = _filter_extra_args(["--confcutdir=/"])
61 assert not any("confcutdir" in a for a in result)
62
63 def test_safe_flags_preserved(self) -> None:
64 safe = ["-v", "-x", "-s", "-k", "test_foo", "--tb=short", "-m", "fast"]
65 result = _filter_extra_args(safe)
66 for flag in safe:
67 assert flag in result, f"safe flag {flag!r} was stripped"
68
69 def test_empty_list_preserved(self) -> None:
70 assert _filter_extra_args([]) == []
71
72 def test_plugin_flag_stripped(self) -> None:
73 """-p (plugin flag) should be blocked; its value must also be dropped."""
74 result = _filter_extra_args(["-p", "no:cacheprovider", "-v"])
75 flat = " ".join(result)
76 assert "cacheprovider" not in flat, "value after dropped -p must be dropped"
77 assert "-p" not in result
78 assert "-v" in result
79
80 def test_dangerous_combination_stripped(self) -> None:
81 """Realistic injection string after muse code test -- --rootdir=/ ..."""
82 injection = [
83 "--rootdir=/",
84 "--import-mode=importlib",
85 "--override-ini=addopts=--forked",
86 "-v",
87 "-k", "malicious",
88 ]
89 result = _filter_extra_args(injection)
90 flat = " ".join(result)
91 assert "rootdir" not in flat
92 assert "import-mode" not in flat
93 assert "override-ini" not in flat
94 assert "-v" in result
95 assert "-k" in result
96
97
98 # ---------------------------------------------------------------------------
99 # Β§ 2 β€” RunConfig + _run_partition apply _filter_extra_args at subprocess time
100 # ---------------------------------------------------------------------------
101
102 class TestRunConfigFiltersExtraArgs:
103 """The subprocess cmd list built from RunConfig must not contain banned flags."""
104
105 def test_subprocess_cmd_does_not_contain_rootdir(self) -> None:
106 """Even if extra_args has --rootdir=/, _filter_extra_args strips it."""
107 # _filter_extra_args is the gate β€” verify it strips before the call
108 dangerous = ["--rootdir=/", "-v"]
109 safe = _filter_extra_args(dangerous)
110 flat = " ".join(safe)
111 assert "--rootdir=/" not in flat, (
112 "The subprocess command contains --rootdir=/ β€” filtering is not applied"
113 )
114
115 def test_run_config_accepts_extra_args_field(self) -> None:
116 """RunConfig TypedDict must have an extra_args field."""
117 config = RunConfig(
118 extra_args=["--rootdir=/", "-v"],
119 workers=1,
120 timeout_s=60.0,
121 env_allowlist=[],
122 cwd=None,
123 )
124 assert "extra_args" in config
125
126
127 # ---------------------------------------------------------------------------
128 # Β§ 3 β€” _SAFE_PYTEST_FLAG_PREFIXES is defined and covers expected flags
129 # ---------------------------------------------------------------------------
130
131 class TestSafeFlagPrefixes:
132 def test_constant_defined(self) -> None:
133 assert isinstance(_SAFE_PYTEST_FLAG_PREFIXES, (list, tuple, frozenset, set))
134 prefixes = set(_SAFE_PYTEST_FLAG_PREFIXES)
135 for flag in ("-v", "-x", "-s", "--tb", "-k"):
136 assert any(flag.startswith(p) or p.startswith(flag) for p in prefixes), (
137 f"Safe flag {flag!r} is not covered by _SAFE_PYTEST_FLAG_PREFIXES"
138 )
139
140 def test_rootdir_not_in_safe_prefixes(self) -> None:
141 prefixes = set(_SAFE_PYTEST_FLAG_PREFIXES)
142 assert not any("rootdir" in p for p in prefixes)
143
144 def test_import_mode_not_in_safe_prefixes(self) -> None:
145 prefixes = set(_SAFE_PYTEST_FLAG_PREFIXES)
146 assert not any("import-mode" in p for p in prefixes)