@@ -367,22 +367,30 @@ async def validate(self, parameters: dict[str, Any], event: dict[str, Any]) -> b
367367 return bool (_ISSUE_REF_PATTERN .search (combined ))
368368
369369
370- class DiffPatternCondition (BaseCondition ):
371- """Validates that a PR diff does not contain specified restricted patterns."""
370+ class _PatchPatternCondition (BaseCondition ):
371+ """Base class for conditions that match regex patterns against PR diff patches.
372372
373- name = "diff_pattern"
374- description = "Checks if code changes contain restricted patterns or fail to contain required patterns."
375- parameter_patterns = ["diff_restricted_patterns" ]
376- event_types = ["pull_request" ]
377- examples = [{"diff_restricted_patterns" : ["console\\ .log" , "TODO:" ]}]
373+ Subclasses configure the parameter key, violation severity, and message format.
374+ """
375+
376+ _pattern_param_key : str = ""
377+ _violation_severity : Severity = Severity .MEDIUM
378+
379+ def _make_message (self , matched : list [str ], filename : str ) -> str :
380+ """Return the violation message. Override for custom wording."""
381+ return f"Patterns { matched } found in added lines of { filename } "
382+
383+ def _make_how_to_fix (self ) -> str :
384+ """Return the how_to_fix text. Override for custom wording."""
385+ return "Remove the matched patterns from your code changes."
378386
379387 async def evaluate (self , context : Any ) -> list [Violation ]:
380- """Evaluate diff -pattern condition."""
388+ """Evaluate patch -pattern condition."""
381389 parameters = context .get ("parameters" , {})
382390 event = context .get ("event" , {})
383391
384- restricted_patterns = parameters .get ("diff_restricted_patterns" )
385- if not restricted_patterns or not isinstance (restricted_patterns , list ):
392+ patterns = parameters .get (self . _pattern_param_key )
393+ if not patterns or not isinstance (patterns , list ):
386394 return []
387395
388396 changed_files = event .get ("changed_files" , [])
@@ -397,98 +405,73 @@ async def evaluate(self, context: Any) -> list[Violation]:
397405 if not patch :
398406 continue
399407
400- matched = match_patterns_in_patch (patch , restricted_patterns )
408+ matched = match_patterns_in_patch (patch , patterns )
401409 if matched :
402410 filename = file_info .get ("filename" , "unknown" )
403411 violations .append (
404412 Violation (
405413 rule_description = self .description ,
406- severity = Severity . MEDIUM ,
407- message = f"Restricted patterns { matched } found in added lines of { filename } " ,
408- how_to_fix = "Remove the restricted patterns from your code changes." ,
414+ severity = self . _violation_severity ,
415+ message = self . _make_message ( matched , filename ) ,
416+ how_to_fix = self . _make_how_to_fix () ,
409417 )
410418 )
411419
412420 return violations
413421
414422 async def validate (self , parameters : dict [str , Any ], event : dict [str , Any ]) -> bool :
415423 """Legacy validation interface."""
416- restricted_patterns = parameters .get ("diff_restricted_patterns" )
417- if not restricted_patterns or not isinstance (restricted_patterns , list ):
424+ patterns = parameters .get (self . _pattern_param_key )
425+ if not patterns or not isinstance (patterns , list ):
418426 return True
419427
420428 changed_files = event .get ("changed_files" , [])
421429 from src .rules .utils .diff import match_patterns_in_patch
422430
423431 for file_info in changed_files :
424432 patch = file_info .get ("patch" )
425- if patch and match_patterns_in_patch (patch , restricted_patterns ):
433+ if patch and match_patterns_in_patch (patch , patterns ):
426434 return False
427435
428436 return True
429437
430438
431- class SecurityPatternCondition ( BaseCondition ):
432- """Detects security-sensitive patterns (like API keys) in code changes ."""
439+ class DiffPatternCondition ( _PatchPatternCondition ):
440+ """Validates that a PR diff does not contain specified restricted patterns ."""
433441
434- name = "security_pattern "
435- description = "Detects hardcoded secrets, API keys, or sensitive data in PR diffs ."
436- parameter_patterns = ["security_patterns " ]
442+ name = "diff_pattern "
443+ description = "Checks if code changes contain restricted patterns or fail to contain required patterns ."
444+ parameter_patterns = ["diff_restricted_patterns " ]
437445 event_types = ["pull_request" ]
438- examples = [{"security_patterns" : ["api_key" , "secret" , "password" , "token" ]}]
439-
440- async def evaluate (self , context : Any ) -> list [Violation ]:
441- """Evaluate security-pattern condition."""
442- parameters = context .get ("parameters" , {})
443- event = context .get ("event" , {})
444-
445- security_patterns = parameters .get ("security_patterns" )
446- if not security_patterns or not isinstance (security_patterns , list ):
447- return []
446+ examples = [{"diff_restricted_patterns" : ["console\\ .log" , "TODO:" ]}]
448447
449- changed_files = event .get ("changed_files" , [])
450- if not changed_files :
451- return []
448+ _pattern_param_key = "diff_restricted_patterns"
449+ _violation_severity = Severity .MEDIUM
452450
453- from src .rules .utils .diff import match_patterns_in_patch
451+ def _make_message (self , matched : list [str ], filename : str ) -> str :
452+ return f"Restricted patterns { matched } found in added lines of { filename } "
454453
455- violations = []
456- for file_info in changed_files :
457- patch = file_info .get ("patch" )
458- if not patch :
459- continue
454+ def _make_how_to_fix (self ) -> str :
455+ return "Remove the restricted patterns from your code changes."
460456
461- # In a real scenario, this would use a more robust secrets scanner.
462- # Here we just use the diff matcher with the provided regex/string patterns.
463- matched = match_patterns_in_patch (patch , security_patterns )
464- if matched :
465- filename = file_info .get ("filename" , "unknown" )
466- violations .append (
467- Violation (
468- rule_description = self .description ,
469- severity = Severity .CRITICAL ,
470- message = f"Security-sensitive patterns { matched } detected in { filename } " ,
471- how_to_fix = "Remove hardcoded secrets or sensitive patterns from the code." ,
472- )
473- )
474457
475- return violations
458+ class SecurityPatternCondition (_PatchPatternCondition ):
459+ """Detects security-sensitive patterns (like API keys) in code changes."""
476460
477- async def validate ( self , parameters : dict [ str , Any ], event : dict [ str , Any ]) -> bool :
478- """Legacy validation interface."" "
479- security_patterns = parameters . get ( "security_patterns" )
480- if not security_patterns or not isinstance ( security_patterns , list ):
481- return True
461+ name = "security_pattern"
462+ description = "Detects hardcoded secrets, API keys, or sensitive data in PR diffs. "
463+ parameter_patterns = [ "security_patterns" ]
464+ event_types = [ "pull_request" ]
465+ examples = [{ "security_patterns" : [ "api_key" , "secret" , "password" , "token" ]}]
482466
483- changed_files = event . get ( "changed_files" , [])
484- from src . rules . utils . diff import match_patterns_in_patch
467+ _pattern_param_key = "security_patterns"
468+ _violation_severity = Severity . CRITICAL
485469
486- for file_info in changed_files :
487- patch = file_info .get ("patch" )
488- if patch and match_patterns_in_patch (patch , security_patterns ):
489- return False
470+ def _make_message (self , matched : list [str ], filename : str ) -> str :
471+ return f"Security-sensitive patterns { matched } detected in { filename } "
490472
491- return True
473+ def _make_how_to_fix (self ) -> str :
474+ return "Remove hardcoded secrets or sensitive patterns from the code."
492475
493476
494477class UnresolvedCommentsCondition (BaseCondition ):
0 commit comments