From 0aaa5f3ca4d416729b95b6be04b63376652ddada Mon Sep 17 00:00:00 2001 From: clavedeluna Date: Tue, 19 Mar 2024 11:28:30 -0300 Subject: [PATCH] start sonar secure random rule --- integration_tests/test_secure_random.py | 4 +- src/core_codemods/secure_random.py | 60 +++++++++++-------- .../sonar/sonar_secure_random.py | 10 ++++ tests/codemods/test_sonar_secure_random.py | 57 ++++++++++++++++++ 4 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 src/core_codemods/sonar/sonar_secure_random.py create mode 100644 tests/codemods/test_sonar_secure_random.py diff --git a/integration_tests/test_secure_random.py b/integration_tests/test_secure_random.py index a4ff87a9..66e4df6d 100644 --- a/integration_tests/test_secure_random.py +++ b/integration_tests/test_secure_random.py @@ -2,7 +2,7 @@ BaseIntegrationTest, original_and_expected_from_code_path, ) -from core_codemods.secure_random import SecureRandom +from core_codemods.secure_random import SecureRandom, SecureRandomTransformer class TestSecureRandom(BaseIntegrationTest): @@ -19,4 +19,4 @@ class TestSecureRandom(BaseIntegrationTest): expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n-import random\n+import secrets\n \n-random.random()\n+secrets.SystemRandom().random()\n var = "hello"\n' expected_line_change = "3" - change_description = SecureRandom.change_description + change_description = SecureRandomTransformer.change_description diff --git a/src/core_codemods/secure_random.py b/src/core_codemods/secure_random.py index 9f131069..7d4a48e7 100644 --- a/src/core_codemods/secure_random.py +++ b/src/core_codemods/secure_random.py @@ -1,31 +1,13 @@ +from codemodder.codemods.libcst_transformer import ( + LibcstResultTransformer, + LibcstTransformerPipeline, +) +from codemodder.codemods.semgrep import SemgrepRuleDetector from codemodder.codemods.utils_mixin import NameResolutionMixin -from core_codemods.api import Metadata, Reference, ReviewGuidance, SimpleCodemod +from core_codemods.api import CoreCodemod, Metadata, Reference, ReviewGuidance -class SecureRandom(SimpleCodemod, NameResolutionMixin): - metadata = Metadata( - name="secure-random", - review_guidance=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW, - summary="Secure Source of Randomness", - references=[ - Reference( - url="https://owasp.org/www-community/vulnerabilities/Insecure_Randomness", - ), - Reference( - url="https://docs.python.org/3/library/random.html", - ), - ], - ) - - detector_pattern = """ - - patterns: - - pattern: random.$F(...) - - pattern-not: random.SystemRandom() - - pattern-inside: | - import random - ... - """ - +class SecureRandomTransformer(LibcstResultTransformer, NameResolutionMixin): change_description = ( "Replace random.{func} with more secure secrets library functions." ) @@ -37,3 +19,31 @@ def on_result_found(self, original_node, updated_node): if self.find_base_name(original_node.func) == "random.choice": return self.update_call_target(updated_node, "secrets") return self.update_call_target(updated_node, "secrets.SystemRandom()") + + +SecureRandom = CoreCodemod( + metadata=Metadata( + name="secure-random", + review_guidance=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW, + summary="Secure Source of Randomness", + references=[ + Reference( + url="https://owasp.org/www-community/vulnerabilities/Insecure_Randomness", + ), + Reference( + url="https://docs.python.org/3/library/random.html", + ), + ], + ), + detector=SemgrepRuleDetector( + """ + - patterns: + - pattern: random.$F(...) + - pattern-not: random.SystemRandom() + - pattern-inside: | + import random + ... + """ + ), + transformer=LibcstTransformerPipeline(SecureRandomTransformer), +) diff --git a/src/core_codemods/sonar/sonar_secure_random.py b/src/core_codemods/sonar/sonar_secure_random.py new file mode 100644 index 00000000..054cfbbe --- /dev/null +++ b/src/core_codemods/sonar/sonar_secure_random.py @@ -0,0 +1,10 @@ +from codemodder.codemods.sonar import SonarCodemod +from core_codemods.secure_random import SecureRandom + +SonarSecureRandom = SonarCodemod.from_core_codemod( + name="secure-random-S2245", + other=SecureRandom, + rule_id="python:S2245", + rule_name="Using pseudorandom number generators (PRNGs) is security-sensitive", + rule_url="https://rules.sonarsource.com/python/type/Security%20Hotspot/RSPEC-2245/", +) diff --git a/tests/codemods/test_sonar_secure_random.py b/tests/codemods/test_sonar_secure_random.py new file mode 100644 index 00000000..2af71f85 --- /dev/null +++ b/tests/codemods/test_sonar_secure_random.py @@ -0,0 +1,57 @@ +import json + +from codemodder.codemods.test import BaseSASTCodemodTest +from core_codemods.sonar.sonar_secure_random import SonarSecureRandom + + +class TestSonarSecureRandom(BaseSASTCodemodTest): + codemod = SonarSecureRandom + tool = "sonar" + + def test_name(self): + assert self.codemod.name == "secure-random-S2245" + + def test_simple(self, tmpdir): + input_code = """ + import random + + random.getrandbits(1) + random.randint(0, 9) + random.random() + random.sample(["a", "b"], 1) + random.choice(["a", "b"]) + random.choices(["a", "b"]) + """ + expected_output = """ + import secrets + + secrets.SystemRandom().getrandbits(1) + secrets.SystemRandom().randint(0, 9) + secrets.SystemRandom().random() + secrets.SystemRandom().sample(["a", "b"], 1) + secrets.choice(["a", "b"]) + secrets.SystemRandom().choices(["a", "b"]) + """ + # todo: not issues, notspots + issues = { + "issues": [ + { + "rule": "python:S5905", + "status": "OPEN", + "component": "code.py", + "textRange": { + "startLine": 2, + "endLine": 2, + "startOffset": 8, + "endOffset": 15, + }, + } + ] + } + self.run_and_assert( + tmpdir, + input_code, + expected_output, + results=json.dumps(issues), + num_changes=3, + )