Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script to automate codemod docs #73

Merged
merged 8 commits into from
Oct 16, 2023
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
6 changes: 5 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ repos:
rev: 23.9.1
hooks:
- id: black
exclude: samples/
exclude: |
(?x)^(
samples/.*|
core_codemods/docs/.*
)$
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1
hooks:
Expand Down
6 changes: 6 additions & 0 deletions integration_tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ def _assert_results_fields(self, results, output_path):
assert len(results) == 1
result = results[0]
assert result["codemod"] == self.codemod_wrapper.id
assert result["references"] == self.codemod_wrapper.references

# TODO: once we add description for each url.
for reference in result["references"]:
assert reference["url"] == reference["description"]

assert len(result["changeset"]) == self.num_changed_files

# A codemod may change multiple files. For now we will
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ dependencies = [

[project.scripts]
codemodder = "codemodder.codemodder:main"
generate-docs = 'scripts.generate_docs:main'


[project.entry-points.codemods]
core = "core_codemods:registry"
Expand Down
1 change: 1 addition & 0 deletions src/codemodder/codemods/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def __init_subclass__(cls):
cls.DESCRIPTION, # pylint: disable=no-member
cls.NAME, # pylint: disable=no-member
cls.REVIEW_GUIDANCE, # pylint: disable=no-member
cls.REFERENCES, # pylint: disable=no-member
)

# This is a little bit hacky, but it also feels like the right solution?
Expand Down
18 changes: 17 additions & 1 deletion src/codemodder/codemods/base_codemod.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum
from typing import List, ClassVar

Expand All @@ -21,6 +21,22 @@ class CodemodMetadata:
DESCRIPTION: str # TODO: this field should be optional
NAME: str
REVIEW_GUIDANCE: ReviewGuidance
REFERENCES: list = field(default_factory=list)

# TODO: remove post_init update_references once we add description for each url.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire green block will be deleted once we add descriptions. I had already added the urls for each codemod so I decided to just automated and copy paste the url into the description field

def __post_init__(self):
object.__setattr__(self, "REFERENCES", self.update_references(self.REFERENCES))

@staticmethod
def update_references(references):
clavedeluna marked this conversation as resolved.
Show resolved Hide resolved
updated_references = []
for reference in references:
updated_reference = dict(
reference
) # Create a copy to avoid modifying the original dict
updated_reference["description"] = updated_reference["url"]
updated_references.append(updated_reference)
return updated_references


class BaseCodemod:
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def compile_results(self, codemods: list[CodemodExecutorWrapper]):
"codemod": codemod.id,
"summary": codemod.summary,
"description": codemod.description,
"references": [],
"references": codemod.references,
"properties": {},
"failedFiles": [],
"changeset": [change.to_json() for change in changeset],
Expand Down
8 changes: 8 additions & 0 deletions src/codemodder/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ def description(self):
# TODO: temporary workaround
return self.METADATA.DESCRIPTION

@property
def review_guidance(self):
return self.METADATA.REVIEW_GUIDANCE.name.replace("_", " ").title()

@property
def references(self):
return self.METADATA.REFERENCES

@property
def yaml_files(self):
return [
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def _validate_codemod(self, codemod):
for k, v in asdict(codemod.METADATA).items():
if v is NotImplemented:
raise NotImplementedError(f"METADATA.{k} not defined for {codemod}")
if not v:
if k != "REFERENCES" and not v:
raise NotImplementedError(
f"METADATA.{k} should not be None or empty for {codemod}"
)
Expand Down
15 changes: 13 additions & 2 deletions src/core_codemods/django_debug_flag_on.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@

class DjangoDebugFlagOn(SemgrepCodemod, Codemod):
METADATA = CodemodMetadata(
DESCRIPTION=("Flips django's debug flag if on."),
DESCRIPTION="Flip `Django` debug flag to off.",
NAME="django-debug-flag-on",
REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW,
REFERENCES=[
{
"url": "https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure",
"description": "",
},
{
"url": "https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-DEBUG",
"description": "",
},
],
)
SUMMARY = CHANGE_DESCRIPTION = "Flip Django debug flag to off"
SUMMARY = "Disable Django Debug Mode"
CHANGE_DESCRIPTION = METADATA.DESCRIPTION
YAML_FILES = [
"django-debug-flag-on.yaml",
]
Expand Down
14 changes: 12 additions & 2 deletions src/core_codemods/django_session_cookie_secure_off.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,19 @@ class DjangoSessionCookieSecureOff(SemgrepCodemod, Codemod):
METADATA = CodemodMetadata(
DESCRIPTION=("Sets Django's `SESSION_COOKIE_SECURE` flag if off or missing."),
NAME="django-session-cookie-secure-off",
REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_REVIEW,
REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW,
REFERENCES=[
{
"url": "https://owasp.org/www-community/controls/SecureCookieAttribute",
"description": "",
},
{
"url": "https://docs.djangoproject.com/en/4.2/ref/settings/#session-cookie-secure",
"description": "",
},
],
)
SUMMARY = "Secure setting for Django `SESSION_COOKIE_SECURE` flag"
SUMMARY = "Secure Setting for Django `SESSION_COOKIE_SECURE` flag"
CHANGE_DESCRIPTION = METADATA.DESCRIPTION
YAML_FILES = [
"detect-django-settings.yaml",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
This codemod enables autoescaping of HTML content in `jinja2`. Unfortunately, the jinja2
default behavior is to not autoescape when rendering templates, which makes your applications
vulnerable to Cross-Site Scripting (XSS) attacks.
potentially vulnerable to Cross-Site Scripting (XSS) attacks.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was wording in the docs repo


Our codemod checks if you forgot to enable autoescape or if you explicitly disabled it. The change looks as follows:

Expand All @@ -13,4 +13,3 @@ Our codemod checks if you forgot to enable autoescape or if you explicitly disab
+ env = Environment(autoescape=True, loader=some_loader)
...
```

8 changes: 3 additions & 5 deletions src/core_codemods/docs/pixee_python_jwt-decode-verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ Our change looks as follows:
- decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=False)
+ decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], verify=True)
...
- decoded_payload = jwt.decode(
encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False, "verify_exp": False})
+ decoded_payload = jwt.decode(
encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True, "verify_exp": True})
- decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False, "verify_exp": False})
+ decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": True, "verify_exp": True})
```

Any `verify` parameter not listed relies on the secure `True` default value.
Any `verify` parameter not listed relies on the secure `True` default value.
2 changes: 0 additions & 2 deletions src/core_codemods/docs/pixee_python_unused-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,3 @@ import b

b.function()
```

If you have feedback on this codemod, [please let us know](mailto:[email protected])!
13 changes: 10 additions & 3 deletions src/core_codemods/enable_jinja2_autoescape.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@

class EnableJinja2Autoescape(SemgrepCodemod):
NAME = "enable-jinja2-autoescape"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW
SUMMARY = "Enable jinja2 autoescape"
DESCRIPTION = "Makes the `autoescape` parameter to jinja2.Environment be `True`."
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
SUMMARY = "Enable Jinja2 Autoescape"
DESCRIPTION = "Sets the `autoescape` parameter in jinja2.Environment to `True`."
REFERENCES = [
{"url": "https://owasp.org/www-community/attacks/xss/", "description": ""},
{
"url": "https://jinja.palletsprojects.com/en/3.1.x/api/#autoescaping",
"description": "",
},
]

@classmethod
def rule(cls):
Expand Down
4 changes: 2 additions & 2 deletions src/core_codemods/fix_mutable_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class FixMutableParams(BaseCodemod):
NAME = "fix-mutable-params"
SUMMARY = "Replace Mutable Default Parameters"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
DESCRIPTION = "Replace mutable parameter with None"

DESCRIPTION = "Replace mutable parameter with `None`."
REFERENCES: list = []
_BUILTIN_TO_LITERAL = {
"list": cst.List(elements=[]),
"dict": cst.Dict(elements=[]),
Expand Down
8 changes: 7 additions & 1 deletion src/core_codemods/harden_pyyaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
class HardenPyyaml(SemgrepCodemod):
NAME = "harden-pyyaml"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
SUMMARY = "Use SafeLoader when loading YAML"
SUMMARY = "Use SafeLoader in `yaml.load()` Calls"
DESCRIPTION = "Ensures all calls to yaml.load use `SafeLoader`."
REFERENCES = [
{
"url": "https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data",
"description": "",
}
]

@classmethod
def rule(cls):
Expand Down
8 changes: 7 additions & 1 deletion src/core_codemods/harden_ruamel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@
class HardenRuamel(SemgrepCodemod):
NAME = "harden-ruamel"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
SUMMARY = "Use safe YAML loading in ruamel.yaml"
SUMMARY = "Use `typ='safe'` in ruamel.yaml() Calls"
DESCRIPTION = "Ensures all unsafe calls to ruamel.yaml.YAML use `typ='safe'`."
REFERENCES = [
{
"url": "https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data",
"description": "",
}
]

@classmethod
def rule(cls):
Expand Down
18 changes: 15 additions & 3 deletions src/core_codemods/https_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,24 @@

class HTTPSConnection(BaseCodemod, Codemod):
METADATA = CodemodMetadata(
DESCRIPTION=("Enforce HTTPS connection"),
DESCRIPTION="Enforce HTTPS connection for `urllib3`.",
NAME="https-connection",
REVIEW_GUIDANCE=ReviewGuidance.MERGE_WITHOUT_REVIEW,
REFERENCES=[
{
"url": "https://owasp.org/www-community/vulnerabilities/Insecure_Transport",
"description": "",
},
{
"url": "https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool",
"description": "",
},
],
)
CHANGE_DESCRIPTION = METADATA.DESCRIPTION
SUMMARY = (
"Changes HTTPConnectionPool to HTTPSConnectionPool to Enforce Secure Connection"
)
CHANGE_DESCRIPTION = "Enforce HTTPS connection"
SUMMARY = "Changes HTTPConnectionPool to HTTPSConnectionPool to enforce secure connection."

METADATA_DEPENDENCIES = (PositionProvider,)

Expand Down
11 changes: 9 additions & 2 deletions src/core_codemods/jwt_decode_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@
class JwtDecodeVerify(SemgrepCodemod):
NAME = "jwt-decode-verify"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
SUMMARY = "Enable all verifications in `jwt.decode` call."
DESCRIPTION = "Makes any of the multiple `verify` parameters to a `jwt.decode` call be `True`."
SUMMARY = "Verify JWT Decode"
DESCRIPTION = "Enable all verifications in `jwt.decode` call."
REFERENCES = [
{"url": "https://pyjwt.readthedocs.io/en/stable/api.html", "description": ""},
{
"url": "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/10-Testing_JSON_Web_Tokens",
"description": "",
},
]

@classmethod
def rule(cls):
Expand Down
5 changes: 4 additions & 1 deletion src/core_codemods/limit_readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
class LimitReadline(SemgrepCodemod):
NAME = "limit-readline"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW
SUMMARY = "Limit the size of readline() calls"
SUMMARY = "Limit readline()"
DESCRIPTION = "Adds a size limit argument to readline() calls."
REFERENCES = [
{"url": "https://cwe.mitre.org/data/definitions/400.html", "description": ""}
]

@classmethod
def rule(cls):
Expand Down
18 changes: 16 additions & 2 deletions src/core_codemods/lxml_safe_parser_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@
class LxmlSafeParserDefaults(SemgrepCodemod):
NAME = "safe-lxml-parser-defaults"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
SUMMARY = "Use safe defaults for lxml parsers"
DESCRIPTION = "Replace lxml parser parameters with safe defaults"
SUMMARY = "Use Safe Defaults for `lxml` Parsers"
DESCRIPTION = "Replace `lxml` parser parameters with safe defaults."
REFERENCES = [
{
"url": "https://lxml.de/apidoc/lxml.etree.html#lxml.etree.XMLParser",
"description": "",
},
{
"url": "https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing",
"description": "",
},
{
"url": "https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html",
"description": "",
},
]

@classmethod
def rule(cls):
Expand Down
18 changes: 16 additions & 2 deletions src/core_codemods/lxml_safe_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@
class LxmlSafeParsing(SemgrepCodemod):
NAME = "safe-lxml-parsing"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
SUMMARY = "Use safe parsers in lxml parsing functions"
SUMMARY = "Use Safe Parsers in `lxml` Parsing Functions"
DESCRIPTION = (
"Call `lxml.etree.parse` and `lxml.etree.fromstring` with a safe parser"
"Call `lxml.etree.parse` and `lxml.etree.fromstring` with a safe parser."
)
REFERENCES = [
{
"url": "https://lxml.de/apidoc/lxml.etree.html#lxml.etree.XMLParser",
"description": "",
},
{
"url": "https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing",
"description": "",
},
{
"url": "https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html",
"description": "",
},
]

@classmethod
def rule(cls):
Expand Down
5 changes: 3 additions & 2 deletions src/core_codemods/order_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@

class OrderImports(BaseCodemod, Codemod):
METADATA = CodemodMetadata(
DESCRIPTION=("Formats and order imports by categories"),
DESCRIPTION=("Formats and orders imports by categories."),
NAME="order-imports",
REVIEW_GUIDANCE=ReviewGuidance.MERGE_WITHOUT_REVIEW,
REFERENCES=[],
)
SUMMARY = "Order imports by categories"
SUMMARY = "Order Imports"
CHANGE_DESCRIPTION = "Ordered and formatted import block below this line"

METADATA_DEPENDENCIES = (PositionProvider,)
Expand Down
12 changes: 11 additions & 1 deletion src/core_codemods/process_creation_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@
class ProcessSandbox(SemgrepCodemod):
NAME = "sandbox-process-creation"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW
SUMMARY = "Sandbox process creation"
SUMMARY = "Sandbox Process Creation"
DESCRIPTION = (
"Replaces subprocess.{func} with more secure safe_command library functions."
)
REFERENCES = [
{
"url": "https://github.com/pixee/python-security/blob/main/src/security/safe_command/api.py",
"description": "",
},
{
"url": "https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html",
"description": "",
},
]

@classmethod
def rule(cls):
Expand Down
Loading