diff --git a/src/core_codemods/__init__.py b/src/core_codemods/__init__.py index 1abf97b7..2f137ad8 100644 --- a/src/core_codemods/__init__.py +++ b/src/core_codemods/__init__.py @@ -87,6 +87,7 @@ from .sonar.sonar_remove_assertion_in_pytest_raises import ( SonarRemoveAssertionInPytestRaises, ) +from .sonar.sonar_sandbox_process_creation import SonarSandboxProcessCreation from .sonar.sonar_secure_random import SonarSecureRandom from .sonar.sonar_sql_parameterization import SonarSQLParameterization from .sonar.sonar_tempfile_mktemp import SonarTempfileMktemp @@ -201,6 +202,7 @@ SonarDisableGraphQLIntrospection, SonarInvertedBooleanCheck, SonarTimezoneAwareDatetime, + SonarSandboxProcessCreation, ], ) diff --git a/src/core_codemods/sonar/sonar_sandbox_process_creation.py b/src/core_codemods/sonar/sonar_sandbox_process_creation.py new file mode 100644 index 00000000..cfb49f3f --- /dev/null +++ b/src/core_codemods/sonar/sonar_sandbox_process_creation.py @@ -0,0 +1,9 @@ +from core_codemods.process_creation_sandbox import ProcessSandbox +from core_codemods.sonar.api import SonarCodemod + +SonarSandboxProcessCreation = SonarCodemod.from_core_codemod( + name="sandbox-process-creation", + other=ProcessSandbox(), + rule_id="pythonsecurity:S2076", + rule_name="OS commands should not be vulnerable to command injection attacks", +) diff --git a/tests/codemods/sonar/test_sonar_sandbox_process_creation.py b/tests/codemods/sonar/test_sonar_sandbox_process_creation.py new file mode 100644 index 00000000..0b41a39b --- /dev/null +++ b/tests/codemods/sonar/test_sonar_sandbox_process_creation.py @@ -0,0 +1,66 @@ +import json + +import mock + +from codemodder.codemods.test import BaseSASTCodemodTest +from codemodder.dependency import Security +from core_codemods.sonar.sonar_sandbox_process_creation import ( + SonarSandboxProcessCreation, +) + + +class TestSonarSandboxProcessCreation(BaseSASTCodemodTest): + codemod = SonarSandboxProcessCreation + tool = "sonar" + + def test_name(self): + assert self.codemod.name == "sandbox-process-creation" + + @mock.patch("codemodder.codemods.api.FileContext.add_dependency") + def test_simple(self, adds_dependency, tmpdir): + input_code = """ + import os + from flask import render_template, request + + @app.route('/vuln', methods=['GET', 'POST']) + def vuln(): + output = "" + if request.method == 'POST': + command = request.form.get('command') + output = os.popen(command).read() + return render_template('vuln.html', output=output) + """.lstrip( + "\n" + ) + expected = """ + import os + from flask import render_template, request + from security import safe_command + + @app.route('/vuln', methods=['GET', 'POST']) + def vuln(): + output = "" + if request.method == 'POST': + command = request.form.get('command') + output = safe_command.run(os.popen, command).read() + return render_template('vuln.html', output=output) + """.lstrip( + "\n" + ) + issues = { + "issues": [ + { + "rule": "pythonsecurity:S2076", + "status": "OPEN", + "component": "code.py", + "textRange": { + "startLine": 9, + "endLine": 9, + "startOffset": 17, + "endOffset": 34, + }, + } + ] + } + self.run_and_assert(tmpdir, input_code, expected, results=json.dumps(issues)) + adds_dependency.assert_called_once_with(Security)