core/tools: review a wrapper option that carries a command line

`env -S "rm -rf /"` ran a command nothing looked at: the option belongs to the
wrapper, so the scan skipped it and the denylist found no program to block while
the soft confirm had no interpreter to review. Collect those values — `env
-S`/`--split-string` and `su -c`/`--command`/`--session-command`, jammed
`--opt=value` and `-Svalue` forms included — and confirm the call the way an
interpreter's `-c` one-liner is confirmed, printing the text under its own
`env -S` label. Recursing into the text for a hard denylist is left out for now:
the review asks the human, who sees the command.
This commit is contained in:
2026-10-06 21:54:18 +08:00
parent 2b3b2073a0
commit 135c28a622
4 changed files with 124 additions and 6 deletions
+25
View File
@@ -92,6 +92,31 @@ def test_unwrap_value_free_flags_do_not_swallow_the_program() -> None:
assert scan.command[0] == base, argv
def test_unwrap_collects_command_line_option_values() -> None:
"""A wrapper option that takes a command line is reported, not just skipped.
``env -S "rm -rf /"`` hands over a command the review must see; the value is
code, so it must not be treated as an argument (or as the program).
"""
scan = unwrap_command(["env", "-S", "rm -rf /"])
assert scan.base == "env"
assert [(span.wrapper, span.option, span.text) for span in scan.command_strings] == [("env", "-S", "rm -rf /")]
assert scan.command_strings[0].label == "env -S"
for argv, option, text in (
(["env", "--split-string=rm -rf /"], "--split-string", "rm -rf /"),
(["env", "-Srm -rf /"], "-S", "rm -rf /"),
(["su", "-c", "rm -rf /"], "-c", "rm -rf /"),
(["su", "--session-command=rm -rf /"], "--session-command", "rm -rf /"),
):
spans = unwrap_command(argv).command_strings
assert [(span.option, span.text) for span in spans] == [(option, text)], argv
# Plain wrapper options stay arguments: no command line to review.
assert unwrap_command(["env", "-u", "FOO=1", "rm", "-rf", "/"]).command_strings == ()
assert unwrap_command(["env", "FOO=1", "git", "-c", "x=y", "log"]).command_strings == ()
def test_unwrap_empty_argv() -> None:
scan = unwrap_command([])
assert scan.base == ""
+20
View File
@@ -144,6 +144,26 @@ def test_value_free_flags_do_not_hide_the_interpreter() -> None:
assert "print(1)" in reason
def test_wrapper_command_line_option_is_reviewed() -> None:
"""`env -S` / `su -c` carry a command line, so they are confirmed like `-c`."""
reason = classify_danger("run_argv", {"argv": ["env", "-S", "rm -rf /"]})
assert reason is not None
assert "env -S runs a command line" in reason
assert " env -S:\n rm -rf /" in reason
# Plain wrapper options are not a review trigger.
assert classify_danger("run_argv", {"argv": ["env", "-u", "FOO=1", "git", "status"]}) is None
assert classify_danger("run_argv", {"argv": ["env", "FOO=1", "git", "-c", "x=y", "log"]}) is None
def test_wrapper_command_line_value_needs_no_interpreter() -> None:
"""The wrapper's value is reviewed even when no interpreter is resolved."""
reason = classify_danger("run_argv", {"argv": ["env", "-S", "python -O script.py"]})
assert reason is not None
assert "env -S runs a command line" in reason
assert "python -O script.py" in reason
def test_only_the_interpreter_dash_c_becomes_a_placeholder() -> None:
reason = classify_danger("run_argv", {"argv": ["ionice", "-c", "2", "python", "-c", "print(1)"]})
assert reason is not None