crackerjack 0.31.10__py3-none-any.whl → 0.31.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +50 -9
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.13.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
- import re
2
1
  from pathlib import Path
3
2
 
3
+ from ..services.regex_patterns import SAFE_PATTERNS
4
4
  from .base import (
5
5
  AgentContext,
6
6
  FixResult,
@@ -14,22 +14,9 @@ from .base import (
14
14
  class SecurityAgent(SubAgent):
15
15
  def __init__(self, context: AgentContext) -> None:
16
16
  super().__init__(context)
17
- self.security_patterns = {
18
- "hardcoded_temp_paths": r"(?:/tmp/|/temp/|C:\\temp\\|C:\\tmp\\)",
19
- "shell_injection": r"shell=True|os\.system\(|subprocess\.call\([^)]*shell=True",
20
- "path_traversal": r"\.\./|\.\.\\",
21
- "hardcoded_secrets": r"(?:password|secret|key|token)\s*=\s*['\"][^'\"]+['\"]",
22
- "unsafe_yaml": r"yaml\.load\([^)]*\)",
23
- "eval_usage": r"\beval\s*\(",
24
- "exec_usage": r"\bexec\s*\(",
25
- "pickle_usage": r"\bpickle\.loads?\s*\(",
26
- "sql_injection": r"(?:execute|query)\s*\([^)]*%[sd]",
27
- "weak_crypto": r"(?:md5|sha1)\s*\(",
28
- "insecure_random": r"random\.random\(\)|random\.choice\(",
29
- }
30
17
 
31
18
  def get_supported_types(self) -> set[IssueType]:
32
- return {IssueType.SECURITY}
19
+ return {IssueType.SECURITY, IssueType.REGEX_VALIDATION}
33
20
 
34
21
  async def can_handle(self, issue: Issue) -> float:
35
22
  if issue.type not in self.get_supported_types():
@@ -37,6 +24,27 @@ class SecurityAgent(SubAgent):
37
24
 
38
25
  message_lower = issue.message.lower()
39
26
 
27
+ # High confidence (0.95) for regex validation issues
28
+ if issue.type == IssueType.REGEX_VALIDATION:
29
+ return 0.95
30
+
31
+ # High confidence (0.95) for regex validation keywords
32
+ if any(
33
+ keyword in message_lower
34
+ for keyword in (
35
+ "validate-regex-patterns",
36
+ "raw regex",
37
+ "regex pattern",
38
+ r"\g<",
39
+ "replacement",
40
+ "unsafe regex",
41
+ "regex vulnerability",
42
+ "redos",
43
+ )
44
+ ):
45
+ return 0.95
46
+
47
+ # High confidence (0.9) for critical security issues
40
48
  if any(
41
49
  keyword in message_lower
42
50
  for keyword in (
@@ -51,20 +59,36 @@ class SecurityAgent(SubAgent):
51
59
  "b506",
52
60
  "unsafe",
53
61
  "injection",
62
+ "pickle",
63
+ "yaml.load",
64
+ "md5",
65
+ "sha1",
66
+ "jwt_secret",
54
67
  )
55
68
  ):
56
- return 1.0
69
+ return 0.9
70
+
71
+ # Enhanced detection using new security patterns
72
+ enhanced_patterns = [
73
+ "detect_security_keywords",
74
+ "detect_crypto_weak_algorithms",
75
+ "detect_hardcoded_credentials_advanced",
76
+ "detect_subprocess_shell_injection",
77
+ "detect_unsafe_pickle_usage",
78
+ ]
57
79
 
58
- for pattern in self.security_patterns.values():
59
- if re.search(pattern, issue.message, re.IGNORECASE):
80
+ for pattern_name in enhanced_patterns:
81
+ if SAFE_PATTERNS[pattern_name].test(issue.message):
60
82
  return 0.9
61
83
 
84
+ # Medium confidence for security-related files
62
85
  if issue.file_path and any(
63
86
  keyword in issue.file_path.lower()
64
- for keyword in ("security", "auth", "crypto", "password")
87
+ for keyword in ("security", "auth", "crypto", "password", "token", "jwt")
65
88
  ):
66
89
  return 0.7
67
90
 
91
+ # Base confidence for security type
68
92
  if issue.type == IssueType.SECURITY:
69
93
  return 0.6
70
94
 
@@ -95,7 +119,7 @@ class SecurityAgent(SubAgent):
95
119
  )
96
120
 
97
121
  success = len(fixes_applied) > 0
98
- confidence = 0.85 if success else 0.4
122
+ confidence = 0.95 if success else 0.4
99
123
 
100
124
  if not success:
101
125
  recommendations = self._get_security_recommendations()
@@ -120,12 +144,16 @@ class SecurityAgent(SubAgent):
120
144
  files_modified: list[str],
121
145
  ) -> tuple[list[str], list[str]]:
122
146
  vulnerability_fix_map = {
147
+ "regex_validation": self._fix_regex_validation_issues,
123
148
  "hardcoded_temp_paths": self._fix_hardcoded_temp_paths,
124
149
  "shell_injection": self._fix_shell_injection,
125
150
  "hardcoded_secrets": self._fix_hardcoded_secrets,
126
151
  "unsafe_yaml": self._fix_unsafe_yaml,
127
152
  "eval_usage": self._fix_eval_usage,
128
153
  "weak_crypto": self._fix_weak_crypto,
154
+ "jwt_secrets": self._fix_jwt_secrets,
155
+ "pickle_usage": self._fix_pickle_usage,
156
+ "insecure_random": self._fix_insecure_random,
129
157
  }
130
158
 
131
159
  if fix_method := vulnerability_fix_map.get(vulnerability_type):
@@ -155,12 +183,21 @@ class SecurityAgent(SubAgent):
155
183
 
156
184
  def _get_security_recommendations(self) -> list[str]:
157
185
  return [
158
- "Use tempfile module for temporary file creation",
159
- "Avoid shell=True in subprocess calls",
160
- "Use environment variables for secrets",
161
- "Implement proper input validation",
162
- "Use safe_load() instead of load() for YAML",
163
- "Consider using cryptographically secure random module",
186
+ "Use centralized SAFE_PATTERNS for regex operations to prevent ReDoS attacks",
187
+ "Avoid raw regex patterns with vulnerable replacement syntax like \\g < 1 >",
188
+ "Use tempfile module for temporary file creation instead of hardcoded paths",
189
+ "Avoid shell=True in subprocess calls to prevent command injection",
190
+ "Store secrets in environment variables using os.getenv(), never hardcode them",
191
+ "Replace weak cryptographic algorithms (MD5, SHA1, DES, RC4) with stronger alternatives",
192
+ "Use secrets module instead of random for cryptographically secure operations",
193
+ "Replace unsafe yaml.load() with yaml.safe_load() to prevent code execution",
194
+ "Avoid pickle.load() with untrusted data as it can execute arbitrary code",
195
+ "Use JWT secrets from environment variables, never hardcode them",
196
+ "Implement proper input validation and sanitization for all user inputs",
197
+ "Add security comments to document potential risks in legacy code",
198
+ "Run bandit security scanner regularly to identify new vulnerabilities",
199
+ "Review all subprocess calls for potential injection vulnerabilities",
200
+ "Ensure all cryptographic operations use secure algorithms and proper key management",
164
201
  ]
165
202
 
166
203
  def _create_error_fix_result(self, error: Exception) -> FixResult:
@@ -178,6 +215,66 @@ class SecurityAgent(SubAgent):
178
215
  def _identify_vulnerability_type(self, issue: Issue) -> str:
179
216
  message = issue.message
180
217
 
218
+ # Regex validation issues
219
+ if self._is_regex_validation_issue(issue):
220
+ return "regex_validation"
221
+
222
+ # Enhanced pattern-based detection
223
+ pattern_checks = self._check_enhanced_patterns(message)
224
+ if pattern_checks:
225
+ return pattern_checks
226
+
227
+ # Bandit code-based detection
228
+ bandit_checks = self._check_bandit_patterns(message)
229
+ if bandit_checks:
230
+ return bandit_checks
231
+
232
+ # Legacy pattern-based detection
233
+ legacy_checks = self._check_legacy_patterns(message)
234
+ if legacy_checks:
235
+ return legacy_checks
236
+
237
+ # JWT and other specific patterns
238
+ if self._is_jwt_secret_issue(message):
239
+ return "jwt_secrets"
240
+
241
+ return "unknown"
242
+
243
+ def _is_regex_validation_issue(self, issue: Issue) -> bool:
244
+ """Check if issue is related to regex validation."""
245
+ if issue.type == IssueType.REGEX_VALIDATION:
246
+ return True
247
+
248
+ message_lower = issue.message.lower()
249
+ return any(
250
+ keyword in message_lower
251
+ for keyword in (
252
+ "validate-regex-patterns",
253
+ "raw regex",
254
+ "unsafe regex",
255
+ r"\g<",
256
+ "redos",
257
+ )
258
+ )
259
+
260
+ def _check_enhanced_patterns(self, message: str) -> str | None:
261
+ """Check enhanced security patterns."""
262
+ pattern_map = {
263
+ "detect_crypto_weak_algorithms": "weak_crypto",
264
+ "detect_hardcoded_credentials_advanced": "hardcoded_secrets",
265
+ "detect_subprocess_shell_injection": "shell_injection",
266
+ "detect_unsafe_pickle_usage": "pickle_usage",
267
+ "detect_regex_redos_vulnerable": "regex_validation",
268
+ }
269
+
270
+ for pattern_name, vulnerability_type in pattern_map.items():
271
+ if SAFE_PATTERNS[pattern_name].test(message):
272
+ return vulnerability_type
273
+
274
+ return None
275
+
276
+ def _check_bandit_patterns(self, message: str) -> str | None:
277
+ """Check Bandit-specific patterns."""
181
278
  if "B108" in message:
182
279
  return "hardcoded_temp_paths"
183
280
  if "B602" in message or "shell=True" in message:
@@ -186,12 +283,131 @@ class SecurityAgent(SubAgent):
186
283
  return "pickle_usage"
187
284
  if "B506" in message or "yaml.load" in message:
188
285
  return "unsafe_yaml"
286
+ if any(crypto in message.lower() for crypto in ("md5", "sha1", "des", "rc4")):
287
+ return "weak_crypto"
189
288
 
190
- for pattern_name, pattern in self.security_patterns.items():
191
- if re.search(pattern, message, re.IGNORECASE):
192
- return pattern_name
289
+ return None
193
290
 
194
- return "unknown"
291
+ def _check_legacy_patterns(self, message: str) -> str | None:
292
+ """Check legacy security patterns."""
293
+ pattern_map = {
294
+ "detect_hardcoded_temp_paths_basic": "hardcoded_temp_paths",
295
+ "detect_hardcoded_secrets": "hardcoded_secrets",
296
+ "detect_insecure_random_usage": "insecure_random",
297
+ }
298
+
299
+ for pattern_name, vulnerability_type in pattern_map.items():
300
+ if SAFE_PATTERNS[pattern_name].test(message):
301
+ return vulnerability_type
302
+
303
+ return None
304
+
305
+ def _is_jwt_secret_issue(self, message: str) -> bool:
306
+ """Check if message indicates JWT secret issue."""
307
+ message_lower = message.lower()
308
+ return "jwt" in message_lower and (
309
+ "secret" in message_lower or "hardcoded" in message_lower
310
+ )
311
+
312
+ async def _fix_regex_validation_issues(self, issue: Issue) -> dict[str, list[str]]:
313
+ """Fix unsafe regex patterns by converting them to use centralized SAFE_PATTERNS."""
314
+ fixes: list[str] = []
315
+ files: list[str] = []
316
+
317
+ if not issue.file_path:
318
+ # If no specific file path, scan all Python files in the project
319
+ await self._fix_regex_patterns_project_wide(fixes, files)
320
+ return {"fixes": fixes, "files": files}
321
+
322
+ file_path = Path(issue.file_path)
323
+ if not file_path.exists():
324
+ return {"fixes": fixes, "files": files}
325
+
326
+ content = self.context.get_file_content(file_path)
327
+ if not content:
328
+ return {"fixes": fixes, "files": files}
329
+
330
+ original_content = content
331
+ content = await self._apply_regex_pattern_fixes(content)
332
+
333
+ if content != original_content:
334
+ if self.context.write_file_content(file_path, content):
335
+ fixes.append(f"Fixed unsafe regex patterns in {issue.file_path}")
336
+ files.append(str(file_path))
337
+ self.log(f"Fixed regex patterns in {issue.file_path}")
338
+
339
+ return {"fixes": fixes, "files": files}
340
+
341
+ async def _fix_regex_patterns_project_wide(
342
+ self, fixes: list[str], files: list[str]
343
+ ) -> None:
344
+ """Fix regex patterns across the entire project."""
345
+ try:
346
+ python_files = self._get_python_files_for_security_scan()
347
+ await self._process_python_files_for_regex_fixes(python_files, fixes, files)
348
+ except Exception as e:
349
+ self.log(f"Error during project-wide regex fixes: {e}", "ERROR")
350
+
351
+ def _get_python_files_for_security_scan(self) -> list[Path]:
352
+ """Get list of Python files that should be scanned for security issues."""
353
+ python_files = list(self.context.project_path.rglob("*.py"))
354
+ return [
355
+ f for f in python_files if not self._should_skip_file_for_security_scan(f)
356
+ ]
357
+
358
+ def _should_skip_file_for_security_scan(self, file_path: Path) -> bool:
359
+ """Check if a file should be skipped during security scanning."""
360
+ skip_patterns = [".venv", "__pycache__", ".git"]
361
+ return any(part in str(file_path) for part in skip_patterns)
362
+
363
+ async def _process_python_files_for_regex_fixes(
364
+ self, python_files: list[Path], fixes: list[str], files: list[str]
365
+ ) -> None:
366
+ """Process each Python file for regex fixes."""
367
+ for file_path in python_files:
368
+ await self._process_single_file_for_regex_fixes(file_path, fixes, files)
369
+
370
+ async def _process_single_file_for_regex_fixes(
371
+ self, file_path: Path, fixes: list[str], files: list[str]
372
+ ) -> None:
373
+ """Process a single file for regex pattern fixes."""
374
+ content = self.context.get_file_content(file_path)
375
+ if not content:
376
+ return
377
+
378
+ original_content = content
379
+ content = await self._apply_regex_pattern_fixes(content)
380
+
381
+ if self._should_save_regex_fixes(content, original_content):
382
+ await self._save_regex_fixes_to_file(file_path, content, fixes, files)
383
+
384
+ def _should_save_regex_fixes(self, content: str, original_content: str) -> bool:
385
+ """Check if regex fixes should be saved."""
386
+ return content != original_content
387
+
388
+ async def _save_regex_fixes_to_file(
389
+ self, file_path: Path, content: str, fixes: list[str], files: list[str]
390
+ ) -> None:
391
+ """Save regex fixes to file and update tracking lists."""
392
+ if self.context.write_file_content(file_path, content):
393
+ fixes.append(f"Fixed unsafe regex patterns in {file_path}")
394
+ files.append(str(file_path))
395
+ self.log(f"Fixed regex patterns in {file_path}")
396
+
397
+ async def _apply_regex_pattern_fixes(self, content: str) -> str:
398
+ """Apply all regex pattern fixes using SAFE_PATTERNS."""
399
+ # Import regex fix utilities
400
+ from crackerjack.services.regex_utils import (
401
+ replace_unsafe_regex_with_safe_patterns,
402
+ )
403
+
404
+ try:
405
+ # Apply centralized regex fixes
406
+ fixed_content = replace_unsafe_regex_with_safe_patterns(content)
407
+ return fixed_content
408
+ except Exception as e:
409
+ self.log(f"Error applying regex fixes: {e}", "ERROR")
410
+ return content
195
411
 
196
412
  async def _fix_hardcoded_temp_paths(self, issue: Issue) -> dict[str, list[str]]:
197
413
  fixes: list[str] = []
@@ -248,26 +464,26 @@ class SecurityAgent(SubAgent):
248
464
  return lines, True
249
465
 
250
466
  def _replace_hardcoded_temp_paths(self, lines: list[str]) -> tuple[list[str], bool]:
251
- replacements = [
252
- (r'Path\("/tmp/([^"]+)"\)', r'Path(tempfile.gettempdir()) / "\1"'),
253
- (r'"/tmp/([^"]+)"', r'str(Path(tempfile.gettempdir()) / "\1")'),
254
- (r"'/tmp/([^']+)'", r"str(Path(tempfile.gettempdir()) / '\1')"),
255
- (
256
- r'Path\("/test/path"\)',
257
- r"Path(tempfile.gettempdir()) / 'test-path'",
258
- ),
259
- (r'"/test/path"', r'str(Path(tempfile.gettempdir()) / "test-path")'),
260
- (r"'/test/path'", r"str(Path(tempfile.gettempdir()) / 'test-path')"),
261
- ]
262
-
263
- modified = False
264
- for pattern, replacement in replacements:
265
- new_content = "\n".join(lines)
266
- if re.search(pattern, new_content):
267
- lines = re.sub(pattern, replacement, new_content).split("\n")
268
- modified = True
467
+ # Apply temp path replacements using SAFE_PATTERNS
468
+ new_content = "\n".join(lines)
469
+
470
+ # Check if any temp paths need replacement
471
+ if SAFE_PATTERNS["detect_hardcoded_temp_paths_basic"].test(new_content):
472
+ # Apply multiple replacement patterns
473
+ new_content = SAFE_PATTERNS["replace_hardcoded_temp_paths"].apply(
474
+ new_content
475
+ )
476
+ new_content = SAFE_PATTERNS["replace_hardcoded_temp_strings"].apply(
477
+ new_content
478
+ )
479
+ new_content = SAFE_PATTERNS["replace_hardcoded_temp_single_quotes"].apply(
480
+ new_content
481
+ )
482
+ new_content = SAFE_PATTERNS["replace_test_path_patterns"].apply(new_content)
483
+ lines = new_content.split("\n")
484
+ return lines, True
269
485
 
270
- return lines, modified
486
+ return lines, False
271
487
 
272
488
  async def _fix_shell_injection(self, issue: Issue) -> dict[str, list[str]]:
273
489
  fixes: list[str] = []
@@ -283,23 +499,9 @@ class SecurityAgent(SubAgent):
283
499
 
284
500
  original_content = content
285
501
 
286
- patterns = [
287
- (
288
- r"subprocess\.run\(([^,]+),\s*shell=True\)",
289
- r"subprocess.run(\1.split())",
290
- ),
291
- (
292
- r"subprocess\.call\(([^,]+),\s*shell=True\)",
293
- r"subprocess.call(\1.split())",
294
- ),
295
- (
296
- r"subprocess\.Popen\(([^,]+),\s*shell=True\)",
297
- r"subprocess.Popen(\1.split())",
298
- ),
299
- ]
502
+ from crackerjack.services.regex_patterns import apply_security_fixes
300
503
 
301
- for pattern, replacement in patterns:
302
- content = re.sub(pattern, replacement, content)
504
+ content = apply_security_fixes(content)
303
505
 
304
506
  if content != original_content:
305
507
  if self.context.write_file_content(file_path, content):
@@ -366,18 +568,12 @@ class SecurityAgent(SubAgent):
366
568
  return lines, False
367
569
 
368
570
  def _line_contains_hardcoded_secret(self, line: str) -> bool:
369
- return bool(
370
- re.search(
371
- r'(password|secret|key|token)\s*=\s*[\'"][^\'"]+[\'"]',
372
- line,
373
- re.IGNORECASE,
374
- ),
375
- )
571
+ return SAFE_PATTERNS["detect_hardcoded_secrets"].test(line)
376
572
 
377
573
  def _replace_hardcoded_secret_with_env_var(self, line: str) -> str:
378
- match = re.search(r"(\w+)\s*=", line)
379
- if match:
380
- var_name = match.group(1)
574
+ # Extract variable name using safe patterns
575
+ var_name = SAFE_PATTERNS["extract_variable_name_from_assignment"].apply(line)
576
+ if var_name != line: # If pattern matched and extracted something
381
577
  env_var_name = var_name.upper()
382
578
  return f"{var_name} = os.getenv('{env_var_name}', '')"
383
579
  return line
@@ -396,7 +592,9 @@ class SecurityAgent(SubAgent):
396
592
 
397
593
  original_content = content
398
594
 
399
- content = re.sub(r"\byaml\.load\(", "yaml.safe_load(", content)
595
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS
596
+
597
+ content = SAFE_PATTERNS["fix_unsafe_yaml_load"].apply(content)
400
598
 
401
599
  if content != original_content:
402
600
  if self.context.write_file_content(file_path, content):
@@ -430,13 +628,10 @@ class SecurityAgent(SubAgent):
430
628
 
431
629
  original_content = content
432
630
 
433
- replacements = [
434
- (r"\bhashlib\.md5\(", "hashlib.sha256("),
435
- (r"\bhashlib\.sha1\(", "hashlib.sha256("),
436
- ]
631
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS
437
632
 
438
- for pattern, replacement in replacements:
439
- content = re.sub(pattern, replacement, content)
633
+ content = SAFE_PATTERNS["fix_weak_md5_hash"].apply(content)
634
+ content = SAFE_PATTERNS["fix_weak_sha1_hash"].apply(content)
440
635
 
441
636
  if content != original_content:
442
637
  if self.context.write_file_content(file_path, content):
@@ -446,6 +641,117 @@ class SecurityAgent(SubAgent):
446
641
 
447
642
  return {"fixes": fixes, "files": files}
448
643
 
644
+ async def _fix_jwt_secrets(self, issue: Issue) -> dict[str, list[str]]:
645
+ """Fix hardcoded JWT secrets by replacing with environment variables."""
646
+ fixes: list[str] = []
647
+ files: list[str] = []
648
+
649
+ if not issue.file_path:
650
+ return {"fixes": fixes, "files": files}
651
+
652
+ file_path = Path(issue.file_path)
653
+ content = self.context.get_file_content(file_path)
654
+ if not content:
655
+ return {"fixes": fixes, "files": files}
656
+
657
+ original_content = content
658
+
659
+ # Apply JWT secret fix pattern
660
+ content = SAFE_PATTERNS["fix_hardcoded_jwt_secret"].apply(content)
661
+
662
+ # Ensure os import is present
663
+ if "os.getenv" in content and "import os" not in content:
664
+ lines = content.split("\n")
665
+ import_index = 0
666
+ for i, line in enumerate(lines):
667
+ if line.strip().startswith(("import ", "from ")):
668
+ import_index = i + 1
669
+ lines.insert(import_index, "import os")
670
+ content = "\n".join(lines)
671
+
672
+ if content != original_content:
673
+ if self.context.write_file_content(file_path, content):
674
+ fixes.append(f"Fixed hardcoded JWT secrets in {issue.file_path}")
675
+ files.append(str(file_path))
676
+ self.log(f"Fixed JWT secrets in {issue.file_path}")
677
+
678
+ return {"fixes": fixes, "files": files}
679
+
680
+ async def _fix_pickle_usage(self, issue: Issue) -> dict[str, list[str]]:
681
+ """Fix unsafe pickle usage by documenting the security risk."""
682
+ fixes: list[str] = []
683
+ files: list[str] = []
684
+
685
+ if not issue.file_path:
686
+ return {"fixes": fixes, "files": files}
687
+
688
+ file_path = Path(issue.file_path)
689
+ content = self.context.get_file_content(file_path)
690
+ if not content:
691
+ return {"fixes": fixes, "files": files}
692
+
693
+ # For pickle usage, we document the risk rather than auto-fixing
694
+ # as pickle is sometimes necessary and the fix depends on context
695
+ fixes.append(
696
+ f"Documented unsafe pickle usage in {issue.file_path} - manual review required"
697
+ )
698
+
699
+ # Add a security comment if pickle.load/loads is found
700
+ if "pickle.load" in content:
701
+ lines = content.split("\n")
702
+ for i, line in enumerate(lines):
703
+ if "pickle.load" in line and "# SECURITY:" not in line:
704
+ lines[i] = (
705
+ line + " # SECURITY: pickle.load is unsafe with untrusted data"
706
+ )
707
+ if self.context.write_file_content(file_path, "\n".join(lines)):
708
+ fixes.append(
709
+ f"Added security warning for pickle usage in {issue.file_path}"
710
+ )
711
+ files.append(str(file_path))
712
+ self.log(
713
+ f"Added security warning for pickle in {issue.file_path}"
714
+ )
715
+ break
716
+
717
+ return {"fixes": fixes, "files": files}
718
+
719
+ async def _fix_insecure_random(self, issue: Issue) -> dict[str, list[str]]:
720
+ """Fix insecure random usage by replacing with secrets module."""
721
+ fixes: list[str] = []
722
+ files: list[str] = []
723
+
724
+ if not issue.file_path:
725
+ return {"fixes": fixes, "files": files}
726
+
727
+ file_path = Path(issue.file_path)
728
+ content = self.context.get_file_content(file_path)
729
+ if not content:
730
+ return {"fixes": fixes, "files": files}
731
+
732
+ original_content = content
733
+
734
+ # Apply insecure random fix
735
+ content = SAFE_PATTERNS["fix_insecure_random_choice"].apply(content)
736
+
737
+ # Ensure secrets import
738
+ if "secrets.choice" in content and "import secrets" not in content:
739
+ lines = content.split("\n")
740
+ import_index = 0
741
+ for i, line in enumerate(lines):
742
+ if line.strip().startswith(("import ", "from ")):
743
+ import_index = i + 1
744
+ lines.insert(import_index, "import secrets")
745
+ content = "\n".join(lines)
746
+
747
+ if content != original_content:
748
+ if self.context.write_file_content(file_path, content):
749
+ fixes.append(f"Fixed insecure random usage in {issue.file_path}")
750
+ files.append(str(file_path))
751
+ self.log(f"Fixed insecure random usage in {issue.file_path}")
752
+
753
+ return {"fixes": fixes, "files": files}
754
+
449
755
  async def _run_bandit_analysis(self) -> list[str]:
450
756
  fixes: list[str] = []
451
757
 
@@ -499,12 +805,12 @@ class SecurityAgent(SubAgent):
499
805
  return self._remove_debug_prints_with_secrets(content)
500
806
 
501
807
  async def _fix_insecure_random_usage(self, content: str) -> str:
502
- if not re.search(r"random\.(?:random|choice)\(", content):
808
+ if not SAFE_PATTERNS["detect_insecure_random_usage"].test(content):
503
809
  return content
504
810
 
505
811
  content = self._add_secrets_import_if_needed(content)
506
812
 
507
- return re.sub(r"random\.choice\(([^)]+)\)", r"secrets.choice(\1)", content)
813
+ return SAFE_PATTERNS["fix_insecure_random_choice"].apply(content)
508
814
 
509
815
  def _add_secrets_import_if_needed(self, content: str) -> str:
510
816
  if "import secrets" in content:
@@ -518,12 +824,9 @@ class SecurityAgent(SubAgent):
518
824
  return "\n".join(lines)
519
825
 
520
826
  def _remove_debug_prints_with_secrets(self, content: str) -> str:
521
- return re.sub(
522
- r"print\s*\([^)]*(?:password|secret|key|token)[^)]*\)",
523
- "",
524
- content,
525
- flags=re.IGNORECASE,
526
- )
827
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS
828
+
829
+ return SAFE_PATTERNS["remove_debug_prints_with_secrets"].apply(content)
527
830
 
528
831
 
529
832
  agent_registry.register(SecurityAgent)