Skip to content

Commit b78e256

Browse files
committed
Initial version of SonarSecureCookie
1 parent 7cdff9f commit b78e256

File tree

4 files changed

+101
-26
lines changed

4 files changed

+101
-26
lines changed

src/core_codemods/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
SonarRemoveAssertionInPytestRaises,
9090
)
9191
from .sonar.sonar_sandbox_process_creation import SonarSandboxProcessCreation
92+
from .sonar.sonar_secure_cookie import SonarSecureCookie
9293
from .sonar.sonar_secure_random import SonarSecureRandom
9394
from .sonar.sonar_sql_parameterization import SonarSQLParameterization
9495
from .sonar.sonar_tempfile_mktemp import SonarTempfileMktemp
@@ -204,6 +205,7 @@
204205
SonarInvertedBooleanCheck,
205206
SonarTimezoneAwareDatetime,
206207
SonarSandboxProcessCreation,
208+
SonarSecureCookie,
207209
],
208210
)
209211

src/core_codemods/secure_flask_cookie.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1-
from core_codemods.api import Metadata, Reference, ReviewGuidance, SimpleCodemod
1+
from codemodder.codemods.libcst_transformer import (
2+
LibcstResultTransformer,
3+
LibcstTransformerPipeline,
4+
)
5+
from codemodder.codemods.semgrep import SemgrepRuleDetector
6+
from core_codemods.api import Metadata, Reference, ReviewGuidance
7+
from core_codemods.api.core_codemod import CoreCodemod
28
from core_codemods.secure_cookie_mixin import SecureCookieMixin
39

410

5-
class SecureFlaskCookie(SimpleCodemod, SecureCookieMixin):
6-
metadata = Metadata(
11+
class SecureCookieTransformer(LibcstResultTransformer, SecureCookieMixin):
12+
change_description = "Flask response `set_cookie` call should be called with `secure=True`, `httponly=True`, and `samesite='Lax'`."
13+
14+
def on_result_found(self, original_node, updated_node):
15+
new_args = self.replace_args(
16+
original_node, self._choose_new_args(original_node)
17+
)
18+
return self.update_arg_target(updated_node, new_args)
19+
20+
21+
SecureFlaskCookie = CoreCodemod(
22+
metadata=Metadata(
723
name="secure-flask-cookie",
824
summary="Use Safe Parameters in `flask` Response `set_cookie` Call",
925
review_guidance=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW,
@@ -14,11 +30,14 @@ class SecureFlaskCookie(SimpleCodemod, SecureCookieMixin):
1430
Reference(
1531
url="https://owasp.org/www-community/controls/SecureCookieAttribute"
1632
),
33+
Reference(url="https://cwe.mitre.org/data/definitions/1004"),
34+
Reference(url="https://cwe.mitre.org/data/definitions/311"),
35+
Reference(url="https://cwe.mitre.org/data/definitions/315"),
1736
Reference(url="https://cwe.mitre.org/data/definitions/614"),
1837
],
19-
)
20-
change_description = "Flask response `set_cookie` call should be called with `secure=True`, `httponly=True`, and `samesite='Lax'`."
21-
detector_pattern = """
38+
),
39+
detector=SemgrepRuleDetector(
40+
"""
2241
rules:
2342
- id: secure-flask-cookie
2443
mode: taint
@@ -39,10 +58,7 @@ class SecureFlaskCookie(SimpleCodemod, SecureCookieMixin):
3958
- pattern: $SINK.set_cookie(...)
4059
- pattern-not: $SINK.set_cookie(..., secure=True, ..., httponly=True, ..., samesite="Lax", ...)
4160
- pattern-not: $SINK.set_cookie(..., secure=True, ..., httponly=True, ..., samesite="Strict", ...)
42-
"""
43-
44-
def on_result_found(self, original_node, updated_node):
45-
new_args = self.replace_args(
46-
original_node, self._choose_new_args(original_node)
47-
)
48-
return self.update_arg_target(updated_node, new_args)
61+
"""
62+
),
63+
transformer=LibcstTransformerPipeline(SecureCookieTransformer),
64+
)

src/core_codemods/sonar/api.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,53 @@ def origin(self):
1515
return "sonar"
1616

1717
@classmethod
18-
def from_core_codemod(
18+
def from_core_codemod_with_multiple_rules(
1919
cls,
2020
name: str,
2121
other: CoreCodemod,
22-
rule_id: str,
23-
rule_name: str,
22+
rules: list[ToolRule],
2423
transformer: BaseTransformerPipeline | None = None,
2524
):
26-
rule_url = sonar_url_from_id(rule_id)
2725
return SonarCodemod(
2826
metadata=Metadata(
2927
name=name,
3028
summary=other.summary,
3129
review_guidance=other._metadata.review_guidance,
3230
references=(
33-
other.references + [Reference(url=rule_url, description=rule_name)]
31+
other.references
32+
+ [Reference(url=tr.url or "", description=tr.name) for tr in rules]
3433
),
3534
description=other.description,
3635
tool=ToolMetadata(
3736
name="Sonar",
38-
rules=[
39-
ToolRule(
40-
id=rule_id,
41-
name=rule_name,
42-
url=rule_url,
43-
)
44-
],
37+
rules=rules,
4538
),
4639
),
4740
transformer=transformer if transformer else other.transformer,
4841
detector=SonarDetector(),
4942
default_extensions=other.default_extensions,
50-
requested_rules=[rule_id],
43+
requested_rules=[tr.id for tr in rules],
44+
)
45+
46+
@classmethod
47+
def from_core_codemod(
48+
cls,
49+
name: str,
50+
other: CoreCodemod,
51+
rule_id: str,
52+
rule_name: str,
53+
transformer: BaseTransformerPipeline | None = None,
54+
):
55+
rule_url = sonar_url_from_id(rule_id)
56+
rules = [
57+
ToolRule(
58+
id=rule_id,
59+
name=rule_name,
60+
url=rule_url,
61+
),
62+
]
63+
return cls.from_core_codemod_with_multiple_rules(
64+
name, other, rules, transformer
5165
)
5266

5367

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from codemodder.codemods.base_codemod import ToolRule
2+
from codemodder.codemods.libcst_transformer import (
3+
LibcstResultTransformer,
4+
LibcstTransformerPipeline,
5+
)
6+
from core_codemods.secure_cookie_mixin import SecureCookieMixin
7+
from core_codemods.secure_flask_cookie import SecureFlaskCookie
8+
from core_codemods.sonar.api import SonarCodemod
9+
10+
rules = [
11+
ToolRule(
12+
id="python:S3330",
13+
name='Creating cookies without the "HttpOnly" flag is security-sensitive',
14+
url="https://rules.sonarsource.com/python/RSPEC-3330/",
15+
),
16+
ToolRule(
17+
id="python:S2092",
18+
name='Creating cookies without the "secure" flag is security-sensitive',
19+
url="ahttps://rules.sonarsource.com/python/RSPEC-2092/",
20+
),
21+
]
22+
23+
24+
class SonarSecureCookieTransformer(LibcstResultTransformer, SecureCookieMixin):
25+
change_description = "Flask response `set_cookie` call should be called with `secure=True`, `httponly=True`, and `samesite='Lax'`."
26+
27+
def leave_Call(self, original_node, updated_node):
28+
# Try to match the func
29+
if self.node_is_selected(original_node.func):
30+
self.report_change(original_node)
31+
new_args = self.replace_args(
32+
original_node, self._choose_new_args(original_node)
33+
)
34+
return self.update_arg_target(updated_node, new_args)
35+
return updated_node
36+
37+
38+
SonarSecureCookie = SonarCodemod.from_core_codemod_with_multiple_rules(
39+
name="secure-cookie",
40+
other=SecureFlaskCookie,
41+
rules=rules,
42+
transformer=LibcstTransformerPipeline(SonarSecureCookieTransformer),
43+
)

0 commit comments

Comments
 (0)