diff --git a/src/codemodder/__main__.py b/src/codemodder/__main__.py index 12696833..6d2233e8 100644 --- a/src/codemodder/__main__.py +++ b/src/codemodder/__main__.py @@ -127,7 +127,8 @@ def compile_results(codemods): continue data = { "codemod": f"pixee:python/{name}", - "summary": codemod_kls.METADATA.DESCRIPTION, + "summary": codemod_kls.SUMMARY, + "description": codemod_kls.METADATA.DESCRIPTION, "references": [], "properties": {}, "failedFiles": [], diff --git a/src/codemodder/codemods/api/__init__.py b/src/codemodder/codemods/api/__init__.py index 9b8d7c2e..52cce15f 100644 --- a/src/codemodder/codemods/api/__init__.py +++ b/src/codemodder/codemods/api/__init__.py @@ -50,6 +50,17 @@ def __init_subclass__(cls): # TODO: if we intend to continue to check class-level attributes # using this mechanism, we should add checks (or defaults) for # NAME, DESCRIPTION, and REVIEW_GUIDANCE here. + missing_fields = [] + for field in ["SUMMARY", "DESCRIPTION", "REVIEW_GUIDANCE"]: + try: + assert hasattr(cls, field) + except AssertionError: + missing_fields.append(field) + + if missing_fields: + raise AssertionError( + f"{cls.__name__} is missing the following fields: {missing_fields}" + ) cls.METADATA = CodemodMetadata( cls.DESCRIPTION, # pylint: disable=no-member diff --git a/src/codemodder/codemods/base_codemod.py b/src/codemodder/codemods/base_codemod.py index 5fc2b31e..41d942c6 100644 --- a/src/codemodder/codemods/base_codemod.py +++ b/src/codemodder/codemods/base_codemod.py @@ -21,6 +21,7 @@ class CodemodMetadata: class BaseCodemod: # Implementation borrowed from https://stackoverflow.com/a/45250114 METADATA: ClassVar[CodemodMetadata] = NotImplemented + SUMMARY: ClassVar[str] = NotImplemented IS_SEMGREP = False def __init_subclass__(cls, **kwargs): @@ -32,8 +33,11 @@ def __init_subclass__(cls, **kwargs): # classes will. return - if cls.METADATA is NotImplemented: - raise NotImplementedError("You forgot to define METADATA") + for attr in ["SUMMARY", "METADATA"]: + if getattr(cls, attr) is NotImplemented: + raise NotImplementedError( + f"You forgot to define {attr} for {cls.__name__}" + ) for k, v in asdict(cls.METADATA).items(): if v is NotImplemented: raise NotImplementedError(f"You forgot to define METADATA.{k}") diff --git a/src/codemodder/codemods/django_debug_flag_on.py b/src/codemodder/codemods/django_debug_flag_on.py index f281eb0e..adca1909 100644 --- a/src/codemodder/codemods/django_debug_flag_on.py +++ b/src/codemodder/codemods/django_debug_flag_on.py @@ -19,7 +19,7 @@ class DjangoDebugFlagOn(SemgrepCodemod, Codemod): NAME="django-debug-flag-on", REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW, ) - CHANGE_DESCRIPTION = "Flips django's debug flag to off." + SUMMARY = CHANGE_DESCRIPTION = "Flips django's debug flag to off." YAML_FILES = [ "django-debug-flag-on.yaml", ] diff --git a/src/codemodder/codemods/django_session_cookie_secure_off.py b/src/codemodder/codemods/django_session_cookie_secure_off.py index 00f9d9e6..41b14482 100644 --- a/src/codemodder/codemods/django_session_cookie_secure_off.py +++ b/src/codemodder/codemods/django_session_cookie_secure_off.py @@ -19,6 +19,7 @@ class DjangoSessionCookieSecureOff(SemgrepCodemod, Codemod): NAME="django-session-cookie-secure-off", REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_REVIEW, ) + SUMMARY = "Sets Django's `SESSION_COOKIE_SECURE` flag if off or missing." CHANGE_DESCRIPTION = METADATA.DESCRIPTION YAML_FILES = [ "detect-django-settings.yaml", diff --git a/src/codemodder/codemods/harden_pyyaml.py b/src/codemodder/codemods/harden_pyyaml.py index 595905ad..0fde850c 100644 --- a/src/codemodder/codemods/harden_pyyaml.py +++ b/src/codemodder/codemods/harden_pyyaml.py @@ -5,6 +5,7 @@ class HardenPyyaml(SemgrepCodemod): NAME = "harden-pyyaml" REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + SUMMARY = "Use SafeLoader when loading YAML" DESCRIPTION = "Ensures all calls to yaml.load use `SafeLoader`." @classmethod diff --git a/src/codemodder/codemods/harden_ruamel.py b/src/codemodder/codemods/harden_ruamel.py index f45729ad..2b12b552 100644 --- a/src/codemodder/codemods/harden_ruamel.py +++ b/src/codemodder/codemods/harden_ruamel.py @@ -5,6 +5,7 @@ class HardenRuamel(SemgrepCodemod): NAME = "harden-ruamel" REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + SUMMARY = "Use safe YAML loading in ruamel.yaml" DESCRIPTION = "Ensures all unsafe calls to ruamel.yaml.YAML use `typ='safe'`." @classmethod diff --git a/src/codemodder/codemods/limit_readline.py b/src/codemodder/codemods/limit_readline.py index 64afc161..e0dc77dc 100644 --- a/src/codemodder/codemods/limit_readline.py +++ b/src/codemodder/codemods/limit_readline.py @@ -9,6 +9,7 @@ class LimitReadline(SemgrepCodemod): NAME = "limit-readline" REVIEW_GUIDANCE = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW + SUMMARY = "Limit the size of readline() calls" DESCRIPTION = "Adds a size limit argument to readline() calls." @classmethod diff --git a/src/codemodder/codemods/order_imports.py b/src/codemodder/codemods/order_imports.py index bd7f7520..d5d486f2 100644 --- a/src/codemodder/codemods/order_imports.py +++ b/src/codemodder/codemods/order_imports.py @@ -21,6 +21,7 @@ class OrderImports(BaseCodemod, Codemod): NAME="order-imports", REVIEW_GUIDANCE=ReviewGuidance.MERGE_WITHOUT_REVIEW, ) + SUMMARY = "Formats and orders imports by categories" CHANGE_DESCRIPTION = "Ordered and formatted import block below this line" METADATA_DEPENDENCIES = (PositionProvider,) diff --git a/src/codemodder/codemods/process_creation_sandbox.py b/src/codemodder/codemods/process_creation_sandbox.py index 3b4bfe12..b2bbd6f7 100644 --- a/src/codemodder/codemods/process_creation_sandbox.py +++ b/src/codemodder/codemods/process_creation_sandbox.py @@ -7,6 +7,7 @@ class ProcessSandbox(SemgrepCodemod): NAME = "sandbox-process-creation" REVIEW_GUIDANCE = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW + SUMMARY = "Use safe_command library to sandbox process creation." DESCRIPTION = ( "Replaces subprocess.{func} with more secure safe_command library functions." ) diff --git a/src/codemodder/codemods/remove_unnecessary_f_str.py b/src/codemodder/codemods/remove_unnecessary_f_str.py index 8c7e0097..1f993ed7 100644 --- a/src/codemodder/codemods/remove_unnecessary_f_str.py +++ b/src/codemodder/codemods/remove_unnecessary_f_str.py @@ -12,6 +12,7 @@ class RemoveUnnecessaryFStr(BaseCodemod, UnnecessaryFormatString): NAME = "remove-unnecessary-f-str" REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + SUMMARY = "Remove unnecessary f-strings." DESCRIPTION = UnnecessaryFormatString.DESCRIPTION def __init__(self, codemod_context: CodemodContext, file_context: FileContext): diff --git a/src/codemodder/codemods/remove_unused_imports.py b/src/codemodder/codemods/remove_unused_imports.py index 769aa165..0110cb7d 100644 --- a/src/codemodder/codemods/remove_unused_imports.py +++ b/src/codemodder/codemods/remove_unused_imports.py @@ -20,6 +20,7 @@ class RemoveUnusedImports(BaseCodemod, Codemod): NAME="unused-imports", REVIEW_GUIDANCE=ReviewGuidance.MERGE_WITHOUT_REVIEW, ) + SUMMARY = "Remove unused imports from a module." CHANGE_DESCRIPTION = "Unused import." METADATA_DEPENDENCIES = (PositionProvider, ScopeProvider, QualifiedNameProvider) diff --git a/src/codemodder/codemods/requests_verify.py b/src/codemodder/codemods/requests_verify.py index bd99693b..c8226f54 100644 --- a/src/codemodder/codemods/requests_verify.py +++ b/src/codemodder/codemods/requests_verify.py @@ -5,6 +5,7 @@ class RequestsVerify(SemgrepCodemod): NAME = "requests-verify" REVIEW_GUIDANCE = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW + SUMMARY = "Verify SSL certificates when making requests." DESCRIPTION = ( "Makes any calls to requests.{func} with `verify=False` to `verify=True`" ) diff --git a/src/codemodder/codemods/secure_random.py b/src/codemodder/codemods/secure_random.py index 5037af46..7aebf862 100644 --- a/src/codemodder/codemods/secure_random.py +++ b/src/codemodder/codemods/secure_random.py @@ -5,6 +5,7 @@ class SecureRandom(SemgrepCodemod): NAME = "secure-random" REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + SUMMARY = "Use secrets.SystemRandom() instead of random" DESCRIPTION = "Replaces random.{func} with more secure secrets library functions." @classmethod diff --git a/src/codemodder/codemods/tempfile_mktemp.py b/src/codemodder/codemods/tempfile_mktemp.py index a2105c96..cd9b59fa 100644 --- a/src/codemodder/codemods/tempfile_mktemp.py +++ b/src/codemodder/codemods/tempfile_mktemp.py @@ -5,6 +5,7 @@ class TempfileMktemp(SemgrepCodemod): NAME = "secure-tempfile" REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + SUMMARY = "Use `tempfile.mkstemp` instead of `tempfile.mktemp`." DESCRIPTION = "Replaces `tempfile.mktemp` with `tempfile.mkstemp`." @classmethod diff --git a/src/codemodder/codemods/upgrade_sslcontext_minimum_version.py b/src/codemodder/codemods/upgrade_sslcontext_minimum_version.py index f23aaec6..cd8763df 100644 --- a/src/codemodder/codemods/upgrade_sslcontext_minimum_version.py +++ b/src/codemodder/codemods/upgrade_sslcontext_minimum_version.py @@ -5,6 +5,7 @@ class UpgradeSSLContextMinimumVersion(SemgrepCodemod): NAME = "upgrade-sslcontext-minimum-version" REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW + SUMMARY = "Upgrade minimum SSL/TLS version for SSLContext" DESCRIPTION = "Replaces minimum SSL/TLS version for SSLContext" @classmethod diff --git a/src/codemodder/codemods/upgrade_sslcontext_tls.py b/src/codemodder/codemods/upgrade_sslcontext_tls.py index f2e9fca6..91f4a188 100644 --- a/src/codemodder/codemods/upgrade_sslcontext_tls.py +++ b/src/codemodder/codemods/upgrade_sslcontext_tls.py @@ -16,6 +16,7 @@ class UpgradeSSLContextTLS(SemgrepCodemod, BaseTransformer): NAME="upgrade-sslcontext-tls", REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW, ) + SUMMARY = "Replaces known insecure TLS/SSL protocol versions in SSLContext with secure ones" CHANGE_DESCRIPTION = "Upgrade to use a safe version of TLS in SSLContext" YAML_FILES = [ "upgrade_sslcontext_tls.yaml", diff --git a/src/codemodder/codemods/url_sandbox.py b/src/codemodder/codemods/url_sandbox.py index 93f5e6a8..1bdba767 100644 --- a/src/codemodder/codemods/url_sandbox.py +++ b/src/codemodder/codemods/url_sandbox.py @@ -31,6 +31,7 @@ class UrlSandbox(SemgrepCodemod, Codemod): NAME="url-sandbox", REVIEW_GUIDANCE=ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW, ) + SUMMARY = "Ensures that requests are made safely." CHANGE_DESCRIPTION = "Switch use of requests for security.safe_requests" YAML_FILES = [ "sandbox_url_creation.yaml", diff --git a/tests/codemods/test_base_codemod.py b/tests/codemods/test_base_codemod.py index ce2b06ef..03861bf6 100644 --- a/tests/codemods/test_base_codemod.py +++ b/tests/codemods/test_base_codemod.py @@ -16,6 +16,7 @@ class DoNothingCodemod(SemgrepCodemod, Codemod): NAME="do-nothing", REVIEW_GUIDANCE=ReviewGuidance.MERGE_WITHOUT_REVIEW, ) + SUMMARY = "An identity codemod for testing purposes." YAML_FILES = [] def __init__(self, context: CodemodContext, results_by_id):