diff --git a/src/dippy/cli/python.py b/src/dippy/cli/python.py index 1dcb5e7..b626937 100644 --- a/src/dippy/cli/python.py +++ b/src/dippy/cli/python.py @@ -98,6 +98,11 @@ "html", "html.parser", "html.entities", + # Argv parsing (pure string processing; sys.exit/--help to stdout is + # part of the CLI contract, not a security concern — print is already + # whitelisted, and termination is not in the threat model). + "argparse", + "getopt", } ) @@ -205,8 +210,6 @@ "cmd", "shlex", "getpass", - "getopt", - "argparse", # Can sys.exit "logging", # Can write to files "atexit", # CGI (Bandit B412: httpoxy vulnerabilities) diff --git a/tests/cli/test_python.py b/tests/cli/test_python.py index b8fec89..4ac2072 100644 --- a/tests/cli/test_python.py +++ b/tests/cli/test_python.py @@ -125,6 +125,34 @@ class Person: result = check(f"python {script}") assert is_approved(result), "Dataclass script should be approved" + def test_safe_script_argparse_approved(self, check, tmp_path): + """Script using argparse should be approved (pure argv parsing).""" + script = tmp_path / "argparse_script.py" + script.write_text(""" +import argparse + +parser = argparse.ArgumentParser(description="Demo") +parser.add_argument("--count", type=int, default=1) +parser.add_argument("name") +args = parser.parse_args() +print(f"Hello {args.name} x{args.count}") +""") + result = check(f"python {script}") + assert is_approved(result), "argparse script should be approved" + + def test_safe_script_getopt_approved(self, check, tmp_path): + """Script using getopt should be approved (pure argv parsing).""" + script = tmp_path / "getopt_script.py" + script.write_text(""" +import getopt + +opts, args = getopt.getopt(["--name", "Alice"], "", ["name="]) +for opt, val in opts: + print(opt, val) +""") + result = check(f"python {script}") + assert is_approved(result), "getopt script should be approved" + def test_dangerous_import_os_blocked(self, check, tmp_path): """Script importing os should be blocked.""" script = tmp_path / "dangerous_os.py"