diff --git a/src/codemodder/scripts/generate_docs.py b/src/codemodder/scripts/generate_docs.py index 68159b2d..4250f287 100644 --- a/src/codemodder/scripts/generate_docs.py +++ b/src/codemodder/scripts/generate_docs.py @@ -326,6 +326,7 @@ class DocMetadata: "enable-jinja2-autoescape", "jwt-decode-verify", "use-defusedxml", + "subprocess-shell-false", ] SEMGREP_CODEMODS = { name: DocMetadata( diff --git a/src/core_codemods/__init__.py b/src/core_codemods/__init__.py index c72568dd..29531af3 100644 --- a/src/core_codemods/__init__.py +++ b/src/core_codemods/__init__.py @@ -56,6 +56,7 @@ from .secure_random import SecureRandom from .semgrep.semgrep_enable_jinja2_autoescape import SemgrepEnableJinja2Autoescape from .semgrep.semgrep_jwt_decode_verify import SemgrepJwtDecodeVerify +from .semgrep.semgrep_subprocess_shell_false import SemgrepSubprocessShellFalse from .semgrep.semgrep_use_defused_xml import SemgrepUseDefusedXml from .sonar.sonar_break_or_continue_out_of_loop import SonarBreakOrContinueOutOfLoop from .sonar.sonar_disable_graphql_introspection import SonarDisableGraphQLIntrospection @@ -202,5 +203,6 @@ SemgrepEnableJinja2Autoescape, SemgrepJwtDecodeVerify, SemgrepUseDefusedXml, + SemgrepSubprocessShellFalse, ], ) diff --git a/src/core_codemods/semgrep/semgrep_subprocess_shell_false.py b/src/core_codemods/semgrep/semgrep_subprocess_shell_false.py new file mode 100644 index 00000000..2f683f2c --- /dev/null +++ b/src/core_codemods/semgrep/semgrep_subprocess_shell_false.py @@ -0,0 +1,9 @@ +from core_codemods.semgrep.api import SemgrepCodemod +from core_codemods.subprocess_shell_false import SubprocessShellFalse + +SemgrepSubprocessShellFalse = SemgrepCodemod.from_core_codemod( + name="subprocess-shell-false", + other=SubprocessShellFalse, + rule_id="python.lang.security.audit.subprocess-shell-true.subprocess-shell-true", + rule_name="subprocess-shell-true", +) diff --git a/tests/codemods/semgrep/test_semgrep_subprocess_shell_false.py b/tests/codemods/semgrep/test_semgrep_subprocess_shell_false.py new file mode 100644 index 00000000..313a10bf --- /dev/null +++ b/tests/codemods/semgrep/test_semgrep_subprocess_shell_false.py @@ -0,0 +1,66 @@ +import json + +from codemodder.codemods.test import BaseSASTCodemodTest +from core_codemods.semgrep.semgrep_subprocess_shell_false import ( + SemgrepSubprocessShellFalse, +) + + +class TestSemgrepSubprocessShellFalse(BaseSASTCodemodTest): + codemod = SemgrepSubprocessShellFalse + tool = "semgrep" + + def test_name(self): + assert self.codemod.name == "subprocess-shell-false" + + def test_import(self, tmpdir): + input_code = """\ + from subprocess import run + run(args, shell=True) + """ + expexted_output = """\ + from subprocess import run + run(args, shell=False) + """ + + results = { + "runs": [ + { + "results": [ + { + "fingerprints": {"matchBasedId/v1": "123"}, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "code.py", + "uriBaseId": "%SRCROOT%", + }, + "region": { + "endColumn": 22, + "endLine": 2, + "snippet": { + "text": "run(args, shell=True)" + }, + "startColumn": 1, + "startLine": 2, + }, + } + } + ], + "message": { + "text": "Found 'subprocess' function 'run' with 'shell=True'. This is dangerous because this call will spawn the command using a shell process. Doing so propagates current shell settings and variables, which makes it much easier for a malicious actor to execute commands. Use 'shell=False' instead." + }, + "properties": {}, + "ruleId": "python.lang.security.audit.subprocess-shell-true.subprocess-shell-true", + } + ] + } + ] + } + self.run_and_assert( + tmpdir, + input_code, + expexted_output, + results=json.dumps(results), + )