Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions integration_tests/test_jinja2_autoescape.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from codemodder.codemods.test import BaseIntegrationTest
from core_codemods.enable_jinja2_autoescape import EnableJinja2Autoescape
from core_codemods.enable_jinja2_autoescape import (
EnableJinja2Autoescape,
EnableJinja2AutoescapeTransformer,
)


class TestEnableJinja2Autoescape(BaseIntegrationTest):
Expand All @@ -17,4 +20,4 @@ class TestEnableJinja2Autoescape(BaseIntegrationTest):
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"
expected_line_change = "3"
num_changes = 2
change_description = EnableJinja2Autoescape.change_description
change_description = EnableJinja2AutoescapeTransformer.change_description
18 changes: 18 additions & 0 deletions integration_tests/test_sonar_jinja2_autoescape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from codemodder.codemods.test import SonarIntegrationTest
from core_codemods.enable_jinja2_autoescape import EnableJinja2AutoescapeTransformer
from core_codemods.sonar.sonar_enable_jinja2_autoescape import (
SonarEnableJinja2Autoescape,
)


class TestSonarEnableJinja2Autoescape(SonarIntegrationTest):
codemod = SonarEnableJinja2Autoescape
code_path = "tests/samples/jinja2_autoescape.py"
replacement_lines = [
(3, "env = Environment(autoescape=True)\n"),
(4, "env = Environment(autoescape=True)\n"),
]
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"
expected_line_change = "3"
num_changes = 2
change_description = EnableJinja2AutoescapeTransformer.change_description
19 changes: 10 additions & 9 deletions src/codemodder/codemods/sonar.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from functools import cache
from pathlib import Path

from codemodder.codemods.base_codemod import Metadata, Reference, ToolMetadata
Expand Down Expand Up @@ -52,23 +53,23 @@ def from_core_codemod(


class SonarDetector(BaseDetector):
_lazy_cache = None

def apply(
self,
codemod_id: str,
context: CodemodExecutionContext,
files_to_analyze: list[Path],
) -> ResultSet:
if not self._lazy_cache:
self._lazy_cache = process_sonar_findings(
context.tool_result_files_map.get("sonar", [])
)
return self._lazy_cache
sonar_findings = process_sonar_findings(
tuple(
context.tool_result_files_map.get("sonar", ())
) # Convert list to tuple for cache hashability
)
return sonar_findings


def process_sonar_findings(sonar_json_files: list[str]) -> SonarResultSet:
@cache
def process_sonar_findings(sonar_json_files: tuple[str]) -> SonarResultSet:
combined_result_set = SonarResultSet()
for file in sonar_json_files or []:
for file in sonar_json_files or ():
combined_result_set |= SonarResultSet.from_json(file)
return combined_result_set
2 changes: 1 addition & 1 deletion src/codemodder/codemods/test/integration_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def check_sonar_issues(cls):
from codemodder.codemods.sonar import process_sonar_findings

sonar_results = process_sonar_findings(
[cls.sonar_issues_json, cls.sonar_hotspots_json]
(cls.sonar_issues_json, cls.sonar_hotspots_json)
)

assert (
Expand Down
5 changes: 5 additions & 0 deletions src/codemodder/scripts/generate_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ class DocMetadata:
guidance_explained=CORE_METADATA["secure-random"].guidance_explained,
need_sarif="Yes (Sonar)",
),
"enable-jinja2-autoescape-S5247": DocMetadata(
importance=CORE_METADATA["enable-jinja2-autoescape"].importance,
guidance_explained=CORE_METADATA["enable-jinja2-autoescape"].guidance_explained,
need_sarif="Yes (Sonar)",
),
}


Expand Down
2 changes: 2 additions & 0 deletions src/core_codemods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from .secure_random import SecureRandom
from .sonar.sonar_django_json_response_type import SonarDjangoJsonResponseType
from .sonar.sonar_django_receiver_on_top import SonarDjangoReceiverOnTop
from .sonar.sonar_enable_jinja2_autoescape import SonarEnableJinja2Autoescape
from .sonar.sonar_exception_without_raise import SonarExceptionWithoutRaise
from .sonar.sonar_fix_assert_tuple import SonarFixAssertTuple
from .sonar.sonar_fix_missing_self_or_cls import SonarFixMissingSelfOrCls
Expand Down Expand Up @@ -150,5 +151,6 @@
SonarFixMissingSelfOrCls,
SonarTempfileMktemp,
SonarSecureRandom,
SonarEnableJinja2Autoescape,
],
)
44 changes: 28 additions & 16 deletions src/core_codemods/enable_jinja2_autoescape.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
from codemodder.codemods.libcst_transformer import NewArg
from core_codemods.api import Metadata, Reference, ReviewGuidance, SimpleCodemod
from codemodder.codemods.libcst_transformer import (
LibcstResultTransformer,
LibcstTransformerPipeline,
NewArg,
)
from codemodder.codemods.semgrep import SemgrepRuleDetector
from core_codemods.api import CoreCodemod, Metadata, Reference, ReviewGuidance


class EnableJinja2Autoescape(SimpleCodemod):
metadata = Metadata(
class EnableJinja2AutoescapeTransformer(LibcstResultTransformer):
change_description = (
"Sets the `autoescape` parameter in jinja2.Environment to `True`."
)

def on_result_found(self, original_node, updated_node):
new_args = self.replace_args(
original_node,
[NewArg(name="autoescape", value="True", add_if_missing=True)],
)
return self.update_arg_target(updated_node, new_args)


EnableJinja2Autoescape = CoreCodemod(
metadata=Metadata(
name="enable-jinja2-autoescape",
summary="Enable Jinja2 Autoescape",
review_guidance=ReviewGuidance.MERGE_AFTER_REVIEW,
Expand All @@ -13,11 +31,9 @@ class EnableJinja2Autoescape(SimpleCodemod):
url="https://jinja.palletsprojects.com/en/3.1.x/api/#autoescaping"
),
],
)
change_description = (
"Sets the `autoescape` parameter in jinja2.Environment to `True`."
)
detector_pattern = """
),
detector=SemgrepRuleDetector(
"""
rules:
- pattern-either:
- patterns:
Expand All @@ -35,10 +51,6 @@ class EnableJinja2Autoescape(SimpleCodemod):
import aiohttp_jinja2
...
"""

def on_result_found(self, original_node, updated_node):
new_args = self.replace_args(
original_node,
[NewArg(name="autoescape", value="True", add_if_missing=True)],
)
return self.update_arg_target(updated_node, new_args)
),
transformer=LibcstTransformerPipeline(EnableJinja2AutoescapeTransformer),
)
10 changes: 10 additions & 0 deletions src/core_codemods/sonar/sonar_enable_jinja2_autoescape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from codemodder.codemods.sonar import SonarCodemod
from core_codemods.enable_jinja2_autoescape import EnableJinja2Autoescape

SonarEnableJinja2Autoescape = SonarCodemod.from_core_codemod(
name="enable-jinja2-autoescape-S5247",
other=EnableJinja2Autoescape,
rule_id="python:S5247",
rule_name="Disabling auto-escaping in template engines is security-sensitive",
rule_url="https://rules.sonarsource.com/python/RSPEC-5247/",
)
59 changes: 59 additions & 0 deletions tests/codemods/test_sonar_enable_jinja2_autoescape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import json

from codemodder.codemods.test import BaseSASTCodemodTest
from core_codemods.sonar.sonar_enable_jinja2_autoescape import (
SonarEnableJinja2Autoescape,
)


class TestEnableJinja2Autoescape(BaseSASTCodemodTest):
codemod = SonarEnableJinja2Autoescape
tool = "sonar"

def test_name(self):
assert self.codemod.name == "enable-jinja2-autoescape-S5247"

def test_simple(self, tmpdir):
input_code = """
from jinja2 import Environment
env = Environment()
env = Environment(autoescape=False)
"""
expected_output = """
from jinja2 import Environment
env = Environment(autoescape=True)
env = Environment(autoescape=True)
"""
hotspots = {
"hotspots": [
{
"rule": "python:S5247",
"status": "OPEN",
"component": "code.py",
"textRange": {
"startLine": 3,
"endLine": 3,
"startOffset": 6,
"endOffset": 19,
},
},
{
"rule": "python:S5247",
"status": "OPEN",
"component": "code.py",
"textRange": {
"startLine": 4,
"endLine": 4,
"startOffset": 6,
"endOffset": 35,
},
},
]
}
self.run_and_assert(
tmpdir,
input_code,
expected_output,
results=json.dumps(hotspots),
num_changes=2,
)
4 changes: 4 additions & 0 deletions tests/samples/jinja2_autoescape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from jinja2 import Environment

env = Environment()
env = Environment(autoescape=False)
52 changes: 51 additions & 1 deletion tests/samples/sonar_hotspots.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"paging": {
"pageIndex": 1,
"pageSize": 100,
"total": 2
"total": 4
},
"hotspots": [
{
Expand Down Expand Up @@ -46,6 +46,48 @@
},
"flows": [],
"ruleKey": "python:S2245"
},
{
"key": "AY6kv10AuTMtKzHrIief",
"component": "pixee_codemodder-python:jinja2_autoescape.py",
"project": "pixee_codemodder-python",
"securityCategory": "xss",
"vulnerabilityProbability": "HIGH",
"status": "TO_REVIEW",
"line": 3,
"message": "Make sure disabling auto-escaping feature is safe here.",
"assignee": "AY02ClhIe4Bh2XgBg3RU",
"creationDate": "2024-04-03T18:15:42+0200",
"updateDate": "2024-04-03T18:16:07+0200",
"textRange": {
"startLine": 3,
"endLine": 3,
"startOffset": 6,
"endOffset": 19
},
"flows": [],
"ruleKey": "python:S5247"
},
{
"key": "AY6kv10AuTMtKzHrIieg",
"component": "pixee_codemodder-python:jinja2_autoescape.py",
"project": "pixee_codemodder-python",
"securityCategory": "xss",
"vulnerabilityProbability": "HIGH",
"status": "TO_REVIEW",
"line": 4,
"message": "Make sure disabling auto-escaping feature is safe here.",
"assignee": "AY02ClhIe4Bh2XgBg3RU",
"creationDate": "2024-04-03T18:15:42+0200",
"updateDate": "2024-04-03T18:16:07+0200",
"textRange": {
"startLine": 4,
"endLine": 4,
"startOffset": 6,
"endOffset": 35
},
"flows": [],
"ruleKey": "python:S5247"
}
],
"components": [
Expand All @@ -63,6 +105,14 @@
"name": "secure_random.py",
"longName": "secure_random.py",
"path": "secure_random.py"
},
{
"organization": "pixee",
"key": "pixee_codemodder-python:jinja2_autoescape.py",
"qualifier": "FIL",
"name": "jinja2_autoescape.py",
"longName": "jinja2_autoescape.py",
"path": "jinja2_autoescape.py"
}
]
}