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.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +50 -9
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
- crackerjack-0.31.13.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
59
|
-
if
|
|
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.
|
|
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
|
|
159
|
-
"Avoid
|
|
160
|
-
"Use
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
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
|
-
|
|
191
|
-
if re.search(pattern, message, re.IGNORECASE):
|
|
192
|
-
return pattern_name
|
|
289
|
+
return None
|
|
193
290
|
|
|
194
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
522
|
-
|
|
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)
|