Skip to content

Commit 3dde585

Browse files
authored
Added Independent Finding Execution Behavior (#1030)
* Codemods will now execute once for each finding independently * Adjusted tests to account for individual changes diff * Fixed a few more unit tests * Fixed a few more tests * Fixed more unit tests * Fixed more unit tests * Added hardening script * Added integration tests for new behavior * Added file rewritten check * Fixed some integration tests * More integration tests converted * Fixed a few more tests * Remove leftover debugging code * Fixed broken dependency and some docstrings * Fixed pygoat workflow file * Fixed some intermittent tests * Some refactoring * More refactoring * Small refactoring * Reverted default behavior to hardening * Fixed pygoat test * Refactored hardening and remediation behavior * Fixed skipping logic * Fixed asserts in integration tests with sonar issues * Removed debugging code * Downgraded pydantic version and bumped sarif-pydantic * Added method to create ResultSets from the same type
1 parent 6097a30 commit 3dde585

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1299
-363
lines changed
Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,17 @@
1-
from codemodder.codemods.test import SonarIntegrationTest
1+
from codemodder.codemods.test.integration_utils import SonarRemediationIntegrationTest
22
from core_codemods.fix_missing_self_or_cls import FixMissingSelfOrClsTransformer
33
from core_codemods.sonar.sonar_fix_missing_self_or_cls import SonarFixMissingSelfOrCls
44

55

6-
class TestSonarFixMissingSelfOrCls(SonarIntegrationTest):
6+
class TestSonarFixMissingSelfOrCls(SonarRemediationIntegrationTest):
77
codemod = SonarFixMissingSelfOrCls
88
code_path = "tests/samples/fix_missing_self_or_cls.py"
9-
replacement_lines = [
10-
(
11-
2,
12-
""" def instance_method(self):\n""",
13-
),
14-
(
15-
6,
16-
""" def class_method(cls):\n""",
17-
),
9+
10+
expected_diff_per_change = [
11+
'--- \n+++ \n@@ -1,5 +1,5 @@\n class MyClass:\n- def instance_method():\n+ def instance_method(self):\n print("instance_method")\n \n @classmethod\n',
12+
'--- \n+++ \n@@ -3,5 +3,5 @@\n print("instance_method")\n \n @classmethod\n- def class_method():\n+ def class_method(cls):\n print("class_method")\n',
1813
]
19-
# fmt: off
20-
expected_diff = (
21-
"""--- \n"""
22-
"""+++ \n"""
23-
"""@@ -1,7 +1,7 @@\n"""
24-
""" class MyClass:\n"""
25-
"""- def instance_method():\n"""
26-
"""+ def instance_method(self):\n"""
27-
""" print("instance_method")\n"""
28-
""" \n"""
29-
""" @classmethod\n"""
30-
"""- def class_method():\n"""
31-
"""+ def class_method(cls):\n"""
32-
""" print("class_method")\n"""
33-
)
34-
# fmt: on
3514

36-
expected_line_change = "2"
15+
expected_lines_changed = [2, 6]
3716
change_description = FixMissingSelfOrClsTransformer.change_description
3817
num_changes = 2
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
from codemodder.codemods.test import SonarIntegrationTest
1+
from codemodder.codemods.test.integration_utils import SonarRemediationIntegrationTest
22
from core_codemods.enable_jinja2_autoescape import EnableJinja2AutoescapeTransformer
33
from core_codemods.sonar.sonar_enable_jinja2_autoescape import (
44
SonarEnableJinja2Autoescape,
55
)
66

77

8-
class TestSonarEnableJinja2Autoescape(SonarIntegrationTest):
8+
class TestSonarEnableJinja2Autoescape(SonarRemediationIntegrationTest):
99
codemod = SonarEnableJinja2Autoescape
1010
code_path = "tests/samples/jinja2_autoescape.py"
11-
replacement_lines = [
12-
(3, "env = Environment(autoescape=True)\n"),
13-
(4, "env = Environment(autoescape=True)\n"),
11+
expected_diff_per_change = [
12+
"--- \n+++ \n@@ -1,4 +1,4 @@\n from jinja2 import Environment\n \n-env = Environment()\n+env = Environment(autoescape=True)\n env = Environment(autoescape=False)\n",
13+
"--- \n+++ \n@@ -1,4 +1,4 @@\n from jinja2 import Environment\n \n env = Environment()\n-env = Environment(autoescape=False)\n+env = Environment(autoescape=True)\n",
1414
]
15-
expected_diff = "--- \n+++ \n@@ -1,4 +1,4 @@\n from jinja2 import Environment\n \n-env = Environment()\n-env = Environment(autoescape=False)\n+env = Environment(autoescape=True)\n+env = Environment(autoescape=True)\n"
16-
expected_line_change = "3"
15+
16+
expected_lines_changed = [3, 4]
1717
num_changes = 2
1818
change_description = EnableJinja2AutoescapeTransformer.change_description
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
from codemodder.codemods.test import SonarIntegrationTest
1+
from codemodder.codemods.test.integration_utils import SonarRemediationIntegrationTest
22
from core_codemods.sonar.sonar_jwt_decode_verify import (
33
JwtDecodeVerifySASTTransformer,
44
SonarJwtDecodeVerify,
55
)
66

77

8-
class TestJwtDecodeVerify(SonarIntegrationTest):
8+
class TestJwtDecodeVerify(SonarRemediationIntegrationTest):
99
codemod = SonarJwtDecodeVerify
1010
code_path = "tests/samples/jwt_decode_verify.py"
11-
replacement_lines = [
12-
(
13-
11,
14-
"""decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)\n""",
15-
),
16-
(
17-
12,
18-
"""decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True})\n""",
19-
),
11+
12+
expected_diff_per_change = [
13+
'--- \n+++ \n@@ -8,7 +8,7 @@\n \n encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")\n \n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)\n decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False})\n \n var = "something"\n',
14+
'--- \n+++ \n@@ -9,6 +9,6 @@\n encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")\n \n decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)\n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False})\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True})\n \n var = "something"\n',
2015
]
2116

22-
expected_diff = '--- \n+++ \n@@ -8,7 +8,7 @@\n \n encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")\n \n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)\n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False})\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True})\n \n var = "something"\n'
23-
expected_line_change = "11"
17+
expected_lines_changed = [11, 12]
2418
num_changes = 2
2519
change_description = JwtDecodeVerifySASTTransformer.change_description
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
from codemodder.codemods.test import SonarIntegrationTest
1+
from codemodder.codemods.test.integration_utils import SonarRemediationIntegrationTest
22
from core_codemods.sonar.sonar_secure_cookie import (
33
SonarSecureCookie,
44
SonarSecureCookieTransformer,
55
)
66

77

8-
class TestSonarSecureCookie(SonarIntegrationTest):
8+
class TestSonarSecureCookie(SonarRemediationIntegrationTest):
99
codemod = SonarSecureCookie
1010
code_path = "tests/samples/secure_cookie.py"
11-
replacement_lines = [
12-
(
13-
8,
14-
""" resp.set_cookie('custom_cookie', 'value', secure=True, httponly=True, samesite='Lax')\n""",
15-
),
11+
expected_diff_per_change = [
12+
"--- \n+++ \n@@ -5,5 +5,5 @@\n @app.route('/')\n def index():\n resp = make_response('Custom Cookie Set')\n- resp.set_cookie('custom_cookie', 'value')\n+ resp.set_cookie('custom_cookie', 'value', secure=True, httponly=True, samesite='Lax')\n return resp\n",
13+
"--- \n+++ \n@@ -5,5 +5,5 @@\n @app.route('/')\n def index():\n resp = make_response('Custom Cookie Set')\n- resp.set_cookie('custom_cookie', 'value')\n+ resp.set_cookie('custom_cookie', 'value', secure=True, httponly=True, samesite='Lax')\n return resp\n",
1614
]
17-
expected_diff = "--- \n+++ \n@@ -5,5 +5,5 @@\n @app.route('/')\n def index():\n resp = make_response('Custom Cookie Set')\n- resp.set_cookie('custom_cookie', 'value')\n+ resp.set_cookie('custom_cookie', 'value', secure=True, httponly=True, samesite='Lax')\n return resp\n"
18-
expected_line_change = "8"
15+
16+
expected_lines_changed = [8, 8]
17+
num_changes = 2
1918
change_description = SonarSecureCookieTransformer.change_description
Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
1-
from codemodder.codemods.test import SonarIntegrationTest
1+
from codemodder.codemods.test.integration_utils import SonarRemediationIntegrationTest
22
from core_codemods.secure_random import SecureRandomTransformer
33
from core_codemods.sonar.sonar_secure_random import SonarSecureRandom
44

55

6-
class TestSonarDjangoJsonResponseType(SonarIntegrationTest):
6+
class TestSonarSecureRandom(SonarRemediationIntegrationTest):
77
codemod = SonarSecureRandom
88
code_path = "tests/samples/secure_random.py"
9-
replacement_lines = [
10-
(1, """import secrets\n"""),
11-
(3, """secrets.SystemRandom().random()\n"""),
12-
(4, """secrets.SystemRandom().getrandbits(1)\n"""),
9+
expected_diff_per_change = [
10+
"--- \n+++ \n@@ -1,4 +1,5 @@\n import random\n+import secrets\n \n-random.random()\n+secrets.SystemRandom().random()\n random.getrandbits(1)\n",
11+
"--- \n+++ \n@@ -1,4 +1,5 @@\n import random\n+import secrets\n \n random.random()\n-random.getrandbits(1)\n+secrets.SystemRandom().getrandbits(1)\n",
1312
]
14-
# fmt: off
15-
expected_diff = (
16-
"""--- \n"""
17-
"""+++ \n"""
18-
"""@@ -1,4 +1,4 @@\n"""
19-
"""-import random\n"""
20-
"""+import secrets\n"""
21-
""" \n"""
22-
"""-random.random()\n"""
23-
"""-random.getrandbits(1)\n"""
24-
"""+secrets.SystemRandom().random()\n"""
25-
"""+secrets.SystemRandom().getrandbits(1)\n""")
26-
# fmt: on
27-
expected_line_change = "3"
13+
14+
expected_lines_changed = [3, 4]
2815
change_description = SecureRandomTransformer.change_description
2916
num_changes = 2
Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from requests.exceptions import ConnectionError
22

3-
from codemodder.codemods.test import BaseIntegrationTest
3+
from codemodder.codemods.test.integration_utils import BaseRemediationIntegrationTest
44
from core_codemods.add_requests_timeouts import (
55
AddRequestsTimeouts,
66
TransformAddRequestsTimeouts,
77
)
88

99

10-
class TestAddRequestsTimeouts(BaseIntegrationTest):
10+
class TestAddRequestsTimeouts(BaseRemediationIntegrationTest):
1111
codemod = AddRequestsTimeouts
1212
original_code = """
1313
import requests
@@ -18,27 +18,13 @@ class TestAddRequestsTimeouts(BaseIntegrationTest):
1818
requests.post("https://example.com", verify=False)
1919
"""
2020

21-
replacement_lines = [
22-
(3, 'requests.get("https://example.com", timeout=60)\n'),
23-
(6, 'requests.post("https://example.com", verify=False, timeout=60)\n'),
21+
expected_diff_per_change = [
22+
'--- \n+++ \n@@ -1,6 +1,6 @@\n import requests\n \n-requests.get("https://example.com")\n+requests.get("https://example.com", timeout=60)\n requests.get("https://example.com", timeout=1)\n requests.get("https://example.com", timeout=(1, 10), verify=False)\n requests.post("https://example.com", verify=False)',
23+
'--- \n+++ \n@@ -3,4 +3,4 @@\n requests.get("https://example.com")\n requests.get("https://example.com", timeout=1)\n requests.get("https://example.com", timeout=(1, 10), verify=False)\n-requests.post("https://example.com", verify=False)\n+requests.post("https://example.com", verify=False, timeout=60)',
2424
]
2525

26-
expected_diff = """\
27-
---
28-
+++
29-
@@ -1,6 +1,6 @@
30-
import requests
31-
32-
-requests.get("https://example.com")
33-
+requests.get("https://example.com", timeout=60)
34-
requests.get("https://example.com", timeout=1)
35-
requests.get("https://example.com", timeout=(1, 10), verify=False)
36-
-requests.post("https://example.com", verify=False)
37-
+requests.post("https://example.com", verify=False, timeout=60)
38-
"""
39-
4026
num_changes = 2
41-
expected_line_change = "3"
27+
expected_lines_changed = [3, 6]
4228
change_description = TransformAddRequestsTimeouts.change_description
4329
# expected because requests are made
4430
allowed_exceptions = (ConnectionError,)
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
from codemodder.codemods.test import BaseIntegrationTest
1+
from codemodder.codemods.test.integration_utils import BaseRemediationIntegrationTest
22
from core_codemods.harden_ruamel import HardenRuamel
33

44

5-
class TestHardenRuamel(BaseIntegrationTest):
5+
class TestHardenRuamel(BaseRemediationIntegrationTest):
66
codemod = HardenRuamel
77
original_code = """
88
from ruamel.yaml import YAML
99
1010
serializer = YAML(typ="unsafe")
1111
serializer = YAML(typ="base")
1212
"""
13-
replacement_lines = [
14-
(3, 'serializer = YAML(typ="safe")\n'),
15-
(4, 'serializer = YAML(typ="safe")\n'),
13+
expected_diff_per_change = [
14+
'--- \n+++ \n@@ -1,4 +1,4 @@\n from ruamel.yaml import YAML\n \n-serializer = YAML(typ="unsafe")\n+serializer = YAML(typ="safe")\n serializer = YAML(typ="base")',
15+
'--- \n+++ \n@@ -1,4 +1,4 @@\n from ruamel.yaml import YAML\n \n serializer = YAML(typ="unsafe")\n-serializer = YAML(typ="base")\n+serializer = YAML(typ="safe")',
1616
]
17-
expected_diff = '--- \n+++ \n@@ -1,4 +1,4 @@\n from ruamel.yaml import YAML\n \n-serializer = YAML(typ="unsafe")\n-serializer = YAML(typ="base")\n+serializer = YAML(typ="safe")\n+serializer = YAML(typ="safe")\n'
18-
expected_line_change = "3"
17+
18+
expected_lines_changed = [3, 4]
1919
num_changes = 2
2020
change_description = HardenRuamel.change_description
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
from codemodder.codemods.test import BaseIntegrationTest
1+
from codemodder.codemods.test.integration_utils import BaseRemediationIntegrationTest
22
from core_codemods.enable_jinja2_autoescape import (
33
EnableJinja2Autoescape,
44
EnableJinja2AutoescapeTransformer,
55
)
66

77

8-
class TestEnableJinja2Autoescape(BaseIntegrationTest):
8+
class TestEnableJinja2Autoescape(BaseRemediationIntegrationTest):
99
codemod = EnableJinja2Autoescape
1010
original_code = """
1111
from jinja2 import Environment
1212
1313
env = Environment()
1414
env = Environment(autoescape=False)
1515
"""
16-
replacement_lines = [
17-
(3, "env = Environment(autoescape=True)\n"),
18-
(4, "env = Environment(autoescape=True)\n"),
16+
17+
expected_diff_per_change = [
18+
"--- \n+++ \n@@ -1,4 +1,4 @@\n from jinja2 import Environment\n \n-env = Environment()\n+env = Environment(autoescape=True)\n env = Environment(autoescape=False)",
19+
"--- \n+++ \n@@ -1,4 +1,4 @@\n from jinja2 import Environment\n \n env = Environment()\n-env = Environment(autoescape=False)\n+env = Environment(autoescape=True)",
1920
]
20-
expected_diff = "--- \n+++ \n@@ -1,4 +1,4 @@\n from jinja2 import Environment\n \n-env = Environment()\n-env = Environment(autoescape=False)\n+env = Environment(autoescape=True)\n+env = Environment(autoescape=True)\n"
21-
expected_line_change = "3"
21+
22+
expected_lines_changed = [3, 4]
2223
num_changes = 2
2324
change_description = EnableJinja2AutoescapeTransformer.change_description
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from codemodder.codemods.test import BaseIntegrationTest
1+
from codemodder.codemods.test.integration_utils import BaseRemediationIntegrationTest
22
from core_codemods.jwt_decode_verify import JwtDecodeVerify, JwtDecodeVerifyTransformer
33

44

5-
class TestJwtDecodeVerify(BaseIntegrationTest):
5+
class TestJwtDecodeVerify(BaseRemediationIntegrationTest):
66
codemod = JwtDecodeVerify
77
original_code = """
88
import jwt
@@ -20,17 +20,11 @@ class TestJwtDecodeVerify(BaseIntegrationTest):
2020
2121
var = "something"
2222
"""
23-
replacement_lines = [
24-
(
25-
11,
26-
"""decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)\n""",
27-
),
28-
(
29-
12,
30-
"""decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True})\n""",
31-
),
23+
expected_diff_per_change = [
24+
'--- \n+++ \n@@ -8,7 +8,7 @@\n \n encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")\n \n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)\n decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False})\n \n var = "something"',
25+
'--- \n+++ \n@@ -9,6 +9,6 @@\n encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")\n \n decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)\n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False})\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True})\n \n var = "something"',
3226
]
33-
expected_diff = '--- \n+++ \n@@ -8,7 +8,7 @@\n \n encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")\n \n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)\n-decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False})\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)\n+decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True})\n \n var = "something"\n'
34-
expected_line_change = "11"
27+
28+
expected_lines_changed = [11, 12]
3529
num_changes = 2
3630
change_description = JwtDecodeVerifyTransformer.change_description
Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,20 @@
1-
from codemodder.codemods.test import BaseIntegrationTest
1+
from codemodder.codemods.test.integration_utils import BaseRemediationIntegrationTest
22
from core_codemods.lazy_logging import LazyLogging
33

44

5-
class TestLazyLogging(BaseIntegrationTest):
5+
class TestLazyLogging(BaseRemediationIntegrationTest):
66
codemod = LazyLogging
77
original_code = """
88
import logging
99
e = "Some error"
1010
logging.error("Error occurred: %s" % e)
1111
logging.error("Error occurred: " + e)
1212
"""
13-
replacement_lines = [
14-
(3, """logging.error("Error occurred: %s", e)\n"""),
15-
(4, """logging.error("Error occurred: %s", e)\n"""),
13+
expected_diff_per_change = [
14+
'--- \n+++ \n@@ -1,4 +1,4 @@\n import logging\n e = "Some error"\n-logging.error("Error occurred: %s" % e)\n+logging.error("Error occurred: %s", e)\n logging.error("Error occurred: " + e)',
15+
'--- \n+++ \n@@ -1,4 +1,4 @@\n import logging\n e = "Some error"\n logging.error("Error occurred: %s" % e)\n-logging.error("Error occurred: " + e)\n+logging.error("Error occurred: %s", e)',
1616
]
17-
# fmt: off
18-
expected_diff = (
19-
"""--- \n"""
20-
"""+++ \n"""
21-
"""@@ -1,4 +1,4 @@\n"""
22-
""" import logging\n"""
23-
""" e = "Some error"\n"""
24-
"""-logging.error("Error occurred: %s" % e)\n"""
25-
"""-logging.error("Error occurred: " + e)\n"""
26-
"""+logging.error("Error occurred: %s", e)\n"""
27-
"""+logging.error("Error occurred: %s", e)\n""")
28-
# fmt: on
2917

30-
expected_line_change = "3"
18+
expected_lines_changed = [3, 4]
3119
change_description = LazyLogging.change_description
3220
num_changes = 2

0 commit comments

Comments
 (0)