crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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 (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,3 @@
1
- """
2
- Secure subprocess execution with environment sanitization and command validation.
3
-
4
- This module provides production-ready security for all subprocess operations in Crackerjack,
5
- implementing comprehensive validation, sanitization, and logging to prevent injection attacks.
6
- """
7
-
8
1
  import os
9
2
  import re
10
3
  import subprocess
@@ -16,26 +9,18 @@ from .security_logger import SecurityEventLevel, SecurityEventType, get_security
16
9
 
17
10
 
18
11
  class SecurityError(Exception):
19
- """Raised when a security violation is detected."""
20
-
21
12
  pass
22
13
 
23
14
 
24
15
  class CommandValidationError(SecurityError):
25
- """Raised when command validation fails."""
26
-
27
16
  pass
28
17
 
29
18
 
30
19
  class EnvironmentValidationError(SecurityError):
31
- """Raised when environment validation fails."""
32
-
33
20
  pass
34
21
 
35
22
 
36
23
  class SubprocessSecurityConfig:
37
- """Configuration for secure subprocess execution."""
38
-
39
24
  def __init__(
40
25
  self,
41
26
  max_command_length: int = 10000,
@@ -44,7 +29,7 @@ class SubprocessSecurityConfig:
44
29
  max_env_vars: int = 1000,
45
30
  allowed_executables: set[str] | None = None,
46
31
  blocked_executables: set[str] | None = None,
47
- max_timeout: float = 3600, # 1 hour max
32
+ max_timeout: float = 3600,
48
33
  enable_path_validation: bool = True,
49
34
  enable_command_logging: bool = True,
50
35
  ):
@@ -92,24 +77,20 @@ class SubprocessSecurityConfig:
92
77
 
93
78
 
94
79
  class SecureSubprocessExecutor:
95
- """Secure subprocess executor with comprehensive validation and logging."""
96
-
97
80
  def __init__(self, config: SubprocessSecurityConfig | None = None):
98
81
  self.config = config or SubprocessSecurityConfig()
99
82
  self.security_logger = get_security_logger()
100
83
 
101
- # Dangerous patterns for command injection detection
102
84
  self.dangerous_patterns = [
103
- r"[;&|`$(){}[\]<>*?~]", # Shell metacharacters
104
- r"\.\./", # Path traversal
105
- r"\$\{.*\}", # Variable expansion
106
- r"`.*`", # Command substitution
107
- r"\$\(.*\)", # Command substitution
108
- r">\s*/", # Redirect to system paths
109
- r"<\s*/", # Redirect from system paths
85
+ r"[;&|`$(){}[\]<>*?~]",
86
+ r"\.\./",
87
+ r"\$\{.*\}",
88
+ r"`.*`",
89
+ r"\$\(.*\)",
90
+ r">\s*/",
91
+ r"<\s*/",
110
92
  ]
111
93
 
112
- # Environment variables that should never be passed through
113
94
  self.dangerous_env_vars = {
114
95
  "LD_PRELOAD",
115
96
  "DYLD_INSERT_LIBRARIES",
@@ -125,7 +106,6 @@ class SecureSubprocessExecutor:
125
106
  "BASHOPTS",
126
107
  }
127
108
 
128
- # Minimal safe environment variables
129
109
  self.safe_env_vars = {
130
110
  "HOME",
131
111
  "USER",
@@ -152,28 +132,6 @@ class SecureSubprocessExecutor:
152
132
  check: bool = False,
153
133
  **kwargs: t.Any,
154
134
  ) -> subprocess.CompletedProcess[str]:
155
- """
156
- Execute a subprocess with comprehensive security validation.
157
-
158
- Args:
159
- command: Command and arguments as list
160
- cwd: Working directory (validated for path traversal)
161
- env: Environment variables (will be sanitized)
162
- timeout: Maximum execution time
163
- input_data: Input to pass to subprocess
164
- capture_output: Whether to capture stdout/stderr
165
- text: Whether to use text mode
166
- check: Whether to raise on non-zero exit
167
- **kwargs: Additional subprocess.run arguments
168
-
169
- Returns:
170
- CompletedProcess result
171
-
172
- Raises:
173
- SecurityError: If security validation fails
174
- CommandValidationError: If command validation fails
175
- EnvironmentValidationError: If environment validation fails
176
- """
177
135
  start_time = time.time()
178
136
 
179
137
  try:
@@ -215,16 +173,12 @@ class SecureSubprocessExecutor:
215
173
  kwargs: dict[str, t.Any],
216
174
  start_time: float,
217
175
  ) -> subprocess.CompletedProcess[str]:
218
- """Execute subprocess with validation and logging."""
219
- # Validate and sanitize all inputs
220
176
  execution_params = self._prepare_execution_params(command, cwd, env, timeout)
221
177
 
222
- # Log and execute subprocess
223
178
  result = self._execute_subprocess(
224
179
  execution_params, input_data, capture_output, text, check, kwargs
225
180
  )
226
181
 
227
- # Log success
228
182
  self._log_successful_execution(execution_params, result, start_time)
229
183
  return result
230
184
 
@@ -235,7 +189,6 @@ class SecureSubprocessExecutor:
235
189
  env: dict[str, str] | None,
236
190
  timeout: float | None,
237
191
  ) -> dict[str, t.Any]:
238
- """Prepare and validate all execution parameters."""
239
192
  return {
240
193
  "command": self._validate_command(command),
241
194
  "cwd": self._validate_cwd(cwd),
@@ -252,7 +205,6 @@ class SecureSubprocessExecutor:
252
205
  check: bool,
253
206
  kwargs: dict[str, t.Any],
254
207
  ) -> subprocess.CompletedProcess[str]:
255
- """Execute subprocess with validated parameters."""
256
208
  if self.config.enable_command_logging:
257
209
  self.security_logger.log_subprocess_execution(
258
210
  command=params["command"],
@@ -279,13 +231,12 @@ class SecureSubprocessExecutor:
279
231
  result: subprocess.CompletedProcess[str],
280
232
  start_time: float,
281
233
  ) -> None:
282
- """Log successful subprocess execution."""
283
234
  execution_time = time.time() - start_time
284
235
  if self.config.enable_command_logging:
285
236
  self.security_logger.log_security_event(
286
237
  SecurityEventType.SUBPROCESS_EXECUTION,
287
238
  SecurityEventLevel.LOW,
288
- f"Subprocess completed successfully in {execution_time:.2f}s",
239
+ f"Subprocess completed successfully in {execution_time: .2f}s",
289
240
  command_preview=params["command"][:3],
290
241
  execution_time=execution_time,
291
242
  exit_code=result.returncode,
@@ -294,7 +245,6 @@ class SecureSubprocessExecutor:
294
245
  def _handle_timeout_error(
295
246
  self, command: list[str], timeout: float | None, start_time: float
296
247
  ) -> None:
297
- """Handle subprocess timeout errors."""
298
248
  execution_time = time.time() - start_time
299
249
  self.security_logger.log_subprocess_timeout(
300
250
  command=command,
@@ -305,7 +255,6 @@ class SecureSubprocessExecutor:
305
255
  def _handle_process_error(
306
256
  self, command: list[str], error: subprocess.CalledProcessError
307
257
  ) -> None:
308
- """Handle subprocess called process errors."""
309
258
  self.security_logger.log_subprocess_failure(
310
259
  command=command,
311
260
  exit_code=error.returncode,
@@ -313,7 +262,6 @@ class SecureSubprocessExecutor:
313
262
  )
314
263
 
315
264
  def _handle_unexpected_error(self, command: list[str], error: Exception) -> None:
316
- """Handle unexpected subprocess errors."""
317
265
  self.security_logger.log_security_event(
318
266
  SecurityEventType.SUBPROCESS_FAILURE,
319
267
  SecurityEventLevel.HIGH,
@@ -324,7 +272,6 @@ class SecureSubprocessExecutor:
324
272
  )
325
273
 
326
274
  def _validate_command(self, command: list[str]) -> list[str]:
327
- """Validate command arguments for security issues."""
328
275
  self._validate_command_structure(command)
329
276
 
330
277
  validated_command, issues = self._validate_command_arguments(command)
@@ -334,11 +281,9 @@ class SecureSubprocessExecutor:
334
281
  return validated_command
335
282
 
336
283
  def _validate_command_structure(self, command: list[str]) -> None:
337
- """Validate basic command structure."""
338
284
  if not command:
339
285
  raise CommandValidationError("Command cannot be empty")
340
286
 
341
- # Check overall command length
342
287
  total_length = sum(len(arg) for arg in command)
343
288
  if total_length > self.config.max_command_length:
344
289
  raise CommandValidationError(
@@ -348,19 +293,16 @@ class SecureSubprocessExecutor:
348
293
  def _validate_command_arguments(
349
294
  self, command: list[str]
350
295
  ) -> tuple[list[str], list[str]]:
351
- """Validate individual command arguments."""
352
296
  validated_command = []
353
297
  issues = []
354
298
 
355
299
  for i, arg in enumerate(command):
356
- # Check argument length
357
300
  if len(arg) > self.config.max_arg_length:
358
301
  issues.append(
359
302
  f"Argument {i} too long: {len(arg)} > {self.config.max_arg_length}"
360
303
  )
361
304
  continue
362
305
 
363
- # Check for injection patterns
364
306
  if self._has_dangerous_patterns(arg, i, issues):
365
307
  continue
366
308
 
@@ -369,7 +311,6 @@ class SecureSubprocessExecutor:
369
311
  return validated_command, issues
370
312
 
371
313
  def _has_dangerous_patterns(self, arg: str, index: int, issues: list[str]) -> bool:
372
- """Check if argument has dangerous patterns."""
373
314
  for pattern in self.dangerous_patterns:
374
315
  if re.search(pattern, arg):
375
316
  issues.append(
@@ -381,7 +322,6 @@ class SecureSubprocessExecutor:
381
322
  def _validate_executable_permissions(
382
323
  self, validated_command: list[str], issues: list[str]
383
324
  ) -> None:
384
- """Validate executable allowlist/blocklist."""
385
325
  if not validated_command:
386
326
  return
387
327
 
@@ -397,7 +337,6 @@ class SecureSubprocessExecutor:
397
337
  issues.append(f"Executable '{executable}' is blocked")
398
338
 
399
339
  def _handle_validation_results(self, command: list[str], issues: list[str]) -> None:
400
- """Handle validation results and logging."""
401
340
  validation_passed = len(issues) == 0
402
341
  if self.config.enable_command_logging:
403
342
  self.security_logger.log_subprocess_command_validation(
@@ -407,7 +346,6 @@ class SecureSubprocessExecutor:
407
346
  )
408
347
 
409
348
  if issues:
410
- # Block dangerous commands
411
349
  self.security_logger.log_dangerous_command_blocked(
412
350
  command=command,
413
351
  reason="Command validation failed",
@@ -418,7 +356,6 @@ class SecureSubprocessExecutor:
418
356
  )
419
357
 
420
358
  def _validate_cwd(self, cwd: Path | str | None) -> Path | None:
421
- """Validate working directory for path traversal."""
422
359
  if cwd is None:
423
360
  return None
424
361
 
@@ -428,10 +365,8 @@ class SecureSubprocessExecutor:
428
365
  cwd_path = Path(cwd) if isinstance(cwd, str) else cwd
429
366
 
430
367
  try:
431
- # Resolve to absolute path and check for traversal
432
368
  resolved_path = cwd_path.resolve()
433
369
 
434
- # Check for dangerous path components
435
370
  path_str = str(resolved_path)
436
371
  if ".." in path_str or path_str.startswith(
437
372
  ("/etc", "/usr/bin", "/bin", "/sbin")
@@ -448,13 +383,12 @@ class SecureSubprocessExecutor:
448
383
  raise CommandValidationError(f"Invalid working directory '{cwd}': {e}")
449
384
 
450
385
  def _sanitize_environment(self, env: dict[str, str] | None) -> dict[str, str]:
451
- """Sanitize environment variables."""
452
386
  if env is None:
453
387
  env = os.environ.copy()
454
388
 
455
389
  self._validate_environment_size(env)
456
390
 
457
- filtered_vars = []
391
+ filtered_vars: list[str] = []
458
392
  sanitized_env = self._filter_environment_variables(env, filtered_vars)
459
393
 
460
394
  self._add_safe_environment_variables(sanitized_env)
@@ -463,7 +397,6 @@ class SecureSubprocessExecutor:
463
397
  return sanitized_env
464
398
 
465
399
  def _validate_environment_size(self, env: dict[str, str]) -> None:
466
- """Validate environment variable count limits."""
467
400
  if len(env) > self.config.max_env_vars:
468
401
  self.security_logger.log_security_event(
469
402
  SecurityEventType.INPUT_SIZE_EXCEEDED,
@@ -479,7 +412,6 @@ class SecureSubprocessExecutor:
479
412
  def _filter_environment_variables(
480
413
  self, env: dict[str, str], filtered_vars: list[str]
481
414
  ) -> dict[str, str]:
482
- """Filter environment variables for security."""
483
415
  sanitized_env = {}
484
416
 
485
417
  for key, value in env.items():
@@ -499,7 +431,6 @@ class SecureSubprocessExecutor:
499
431
  def _is_dangerous_environment_key(
500
432
  self, key: str, value: str, filtered_vars: list[str]
501
433
  ) -> bool:
502
- """Check if environment key is dangerous."""
503
434
  if key in self.dangerous_env_vars:
504
435
  filtered_vars.append(key)
505
436
  self.security_logger.log_environment_variable_filtered(
@@ -513,7 +444,6 @@ class SecureSubprocessExecutor:
513
444
  def _is_environment_value_too_long(
514
445
  self, key: str, value: str, filtered_vars: list[str]
515
446
  ) -> bool:
516
- """Check if environment value exceeds length limits."""
517
447
  if len(value) > self.config.max_env_var_length:
518
448
  filtered_vars.append(key)
519
449
  self.security_logger.log_environment_variable_filtered(
@@ -527,8 +457,7 @@ class SecureSubprocessExecutor:
527
457
  def _has_environment_injection(
528
458
  self, key: str, value: str, filtered_vars: list[str]
529
459
  ) -> bool:
530
- """Check if environment value has injection patterns."""
531
- for pattern in self.dangerous_patterns[:3]: # Check first 3 most dangerous
460
+ for pattern in self.dangerous_patterns[:3]:
532
461
  if re.search(pattern, value):
533
462
  filtered_vars.append(key)
534
463
  self.security_logger.log_environment_variable_filtered(
@@ -540,7 +469,6 @@ class SecureSubprocessExecutor:
540
469
  return False
541
470
 
542
471
  def _add_safe_environment_variables(self, sanitized_env: dict[str, str]) -> None:
543
- """Add essential safe environment variables."""
544
472
  for safe_var in self.safe_env_vars:
545
473
  if safe_var not in sanitized_env and safe_var in os.environ:
546
474
  sanitized_env[safe_var] = os.environ[safe_var]
@@ -548,7 +476,6 @@ class SecureSubprocessExecutor:
548
476
  def _log_environment_sanitization(
549
477
  self, original_count: int, sanitized_count: int, filtered_vars: list[str]
550
478
  ) -> None:
551
- """Log environment sanitization results."""
552
479
  if self.config.enable_command_logging:
553
480
  self.security_logger.log_subprocess_environment_sanitized(
554
481
  original_count=original_count,
@@ -557,7 +484,6 @@ class SecureSubprocessExecutor:
557
484
  )
558
485
 
559
486
  def _validate_timeout(self, timeout: float | None) -> float | None:
560
- """Validate timeout value."""
561
487
  if timeout is None:
562
488
  return None
563
489
 
@@ -579,14 +505,12 @@ class SecureSubprocessExecutor:
579
505
  return timeout
580
506
 
581
507
 
582
- # Global secure executor instance
583
508
  _global_executor: SecureSubprocessExecutor | None = None
584
509
 
585
510
 
586
511
  def get_secure_executor(
587
512
  config: SubprocessSecurityConfig | None = None,
588
513
  ) -> SecureSubprocessExecutor:
589
- """Get the global secure subprocess executor."""
590
514
  global _global_executor
591
515
  if _global_executor is None:
592
516
  _global_executor = SecureSubprocessExecutor(config)
@@ -597,9 +521,4 @@ def execute_secure_subprocess(
597
521
  command: list[str],
598
522
  **kwargs: t.Any,
599
523
  ) -> subprocess.CompletedProcess[str]:
600
- """
601
- Convenience function for secure subprocess execution.
602
-
603
- This is the recommended way to execute subprocesses in Crackerjack.
604
- """
605
524
  return get_secure_executor().execute_secure(command, **kwargs)
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import tempfile
3
+ import typing as t
3
4
  from contextlib import suppress
4
5
  from pathlib import Path
5
6
 
@@ -8,7 +9,6 @@ from crackerjack.services.regex_patterns import SAFE_PATTERNS
8
9
 
9
10
 
10
11
  class SecurityService:
11
- # Security token masking patterns - now using validated patterns from regex_patterns.py
12
12
  TOKEN_PATTERN_NAMES = [
13
13
  "mask_pypi_token",
14
14
  "mask_github_token",
@@ -28,31 +28,16 @@ class SecurityService:
28
28
  }
29
29
 
30
30
  def mask_tokens(self, text: str) -> str:
31
- """
32
- Mask sensitive tokens in text using validated regex patterns.
33
-
34
- This method applies security token masking patterns to hide:
35
- - PyPI authentication tokens (pypi-*)
36
- - GitHub personal access tokens (ghp_*)
37
- - Generic long tokens (32+ characters)
38
- - Token assignments (token="value")
39
- - Password assignments (password="value")
40
- - Environment variable values
41
-
42
- Returns masked text with sensitive data replaced by "**** or similar.
43
- """
44
31
  if not text:
45
32
  return text
46
33
 
47
34
  masked_text = text
48
35
 
49
- # Apply validated token masking patterns
50
36
  for pattern_name in self.TOKEN_PATTERN_NAMES:
51
37
  if pattern_name in SAFE_PATTERNS:
52
38
  pattern = SAFE_PATTERNS[pattern_name]
53
39
  masked_text = pattern.apply(masked_text)
54
40
 
55
- # Also mask sensitive environment variable values
56
41
  for env_var in self.SENSITIVE_ENV_VARS:
57
42
  value = os.getenv(env_var)
58
43
  if value and len(value) > 8:
@@ -96,7 +81,7 @@ class SecurityService:
96
81
  with suppress(OSError):
97
82
  temp_file.unlink()
98
83
  raise FileError(
99
- message="Failed to set secure file permissions",
84
+ message="Failed to set[t.Any] secure file permissions",
100
85
  details=str(e),
101
86
  recovery="Check file system permissions and try again",
102
87
  ) from e
@@ -147,30 +132,17 @@ class SecurityService:
147
132
  return env_summary
148
133
 
149
134
  def validate_token_format(self, token: str, token_type: str | None = None) -> bool:
150
- """
151
- Validate token format for known token types.
152
-
153
- Args:
154
- token: The token string to validate
155
- token_type: Optional token type ("pypi", "github", or None)
156
-
157
- Returns:
158
- True if the token appears to be valid for the specified type
159
- """
160
135
  if not token:
161
136
  return False
162
137
  if len(token) < 8:
163
138
  return False
164
139
 
165
140
  if token_type and token_type.lower() == "pypi":
166
- # PyPI tokens start with "pypi-" (not "pypi -" which was a typo)
167
141
  return token.startswith("pypi-") and len(token) >= 16
168
142
 
169
143
  if token_type and token_type.lower() == "github":
170
- # GitHub personal access tokens: ghp_ + 36 chars = 40 total
171
144
  return token.startswith("ghp_") and len(token) == 40
172
145
 
173
- # Generic validation for unknown token types
174
146
  return len(token) >= 16 and not token.isspace()
175
147
 
176
148
  def create_secure_command_env(
@@ -198,3 +170,62 @@ class SecurityService:
198
170
  secure_env.pop(var, None)
199
171
 
200
172
  return secure_env
173
+
174
+ def validate_file_safety(self, path: str | Path) -> bool:
175
+ try:
176
+ file_path = Path(path)
177
+
178
+ if not file_path.exists():
179
+ return False
180
+
181
+ if file_path.is_symlink():
182
+ return False
183
+ return True
184
+ except Exception:
185
+ return False
186
+
187
+ def check_hardcoded_secrets(self, content: str) -> list[dict[str, t.Any]]:
188
+ secrets = []
189
+
190
+ patterns = {
191
+ "api_key": r'api[_-]?key["\s]*[: =]["\s]*([a-zA-Z0-9_-]{20, })',
192
+ "password": r'password["\s]*[: =]["\s]*([^\s"]{8, })',
193
+ "token": r'token["\s]*[: =]["\s]*([a-zA-Z0-9_-]{20, })',
194
+ }
195
+
196
+ import re
197
+
198
+ for secret_type, pattern in patterns.items():
199
+ matches = re.finditer(pattern, content, re.IGNORECASE)
200
+ for match in matches:
201
+ secrets.append(
202
+ {
203
+ "type": secret_type,
204
+ "value": match.group(1)[:10] + "...",
205
+ "line": content[: match.start()].count("\n") + 1,
206
+ }
207
+ )
208
+ return secrets
209
+
210
+ def is_safe_subprocess_call(self, cmd: list[str]) -> bool:
211
+ if not cmd:
212
+ return False
213
+
214
+ dangerous_commands = {
215
+ "rm",
216
+ "rmdir",
217
+ "del",
218
+ "format",
219
+ "fdisk",
220
+ "sudo",
221
+ "su",
222
+ "chmod",
223
+ "chown",
224
+ "curl",
225
+ "wget",
226
+ "nc",
227
+ "netcat",
228
+ }
229
+
230
+ command = cmd[0].split("/")[-1]
231
+ return command not in dangerous_commands
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import logging
3
+ import os
3
4
  import time
4
5
  import typing as t
5
6
  from enum import Enum
@@ -36,7 +37,7 @@ class SecurityEventType(str, Enum):
36
37
  STATUS_ACCESS_ATTEMPT = "status_access_attempt"
37
38
  SENSITIVE_DATA_SANITIZED = "sensitive_data_sanitized"
38
39
  STATUS_INFORMATION_DISCLOSURE = "status_information_disclosure"
39
- # Authentication and authorization events
40
+
40
41
  ACCESS_DENIED = "access_denied"
41
42
  API_KEY_CREATED = "api_key_created"
42
43
  API_KEY_REVOKED = "api_key_revoked"
@@ -45,40 +46,40 @@ class SecurityEventType(str, Enum):
45
46
  AUTH_SUCCESS = "auth_success"
46
47
  LOCAL_ACCESS_GRANTED = "local_access_granted"
47
48
  INSUFFICIENT_PRIVILEGES = "insufficient_privileges"
48
- # Circuit breaker and resilience events
49
+
49
50
  CIRCUIT_BREAKER_CLOSED = "circuit_breaker_closed"
50
51
  CIRCUIT_BREAKER_HALF_OPEN = "circuit_breaker_half_open"
51
52
  CIRCUIT_BREAKER_OPEN = "circuit_breaker_open"
52
53
  CIRCUIT_BREAKER_RESET = "circuit_breaker_reset"
53
- # Collection and data processing events
54
+
54
55
  COLLECTION_END = "collection_end"
55
56
  COLLECTION_ERROR = "collection_error"
56
57
  COLLECTION_START = "collection_start"
57
58
  STATUS_COLLECTED = "status_collected"
58
59
  FILE_READ_ERROR = "file_read_error"
59
- # Connection and network events
60
+
60
61
  CONNECTION_CLOSED = "connection_closed"
61
62
  CONNECTION_ESTABLISHED = "connection_established"
62
63
  CONNECTION_IDLE = "connection_idle"
63
64
  CONNECTION_TIMEOUT = "connection_timeout"
64
- # Request lifecycle events
65
+
65
66
  REQUEST_END = "request_end"
66
67
  REQUEST_START = "request_start"
67
68
  REQUEST_TIMEOUT = "request_timeout"
68
- # Resource and operation management events
69
+
69
70
  RESOURCE_CLEANUP = "resource_cleanup"
70
71
  RESOURCE_EXHAUSTED = "resource_exhausted"
71
72
  RESOURCE_LIMIT_EXCEEDED = "resource_limit_exceeded"
72
73
  SERVICE_CLEANUP = "service_cleanup"
73
74
  SERVICE_START = "service_start"
74
75
  SERVICE_STOP = "service_stop"
75
- # Operation monitoring events
76
+
76
77
  MONITORING_ERROR = "monitoring_error"
77
78
  OPERATION_DURATION_EXCEEDED = "operation_duration_exceeded"
78
79
  OPERATION_FAILURE = "operation_failure"
79
80
  OPERATION_SUCCESS = "operation_success"
80
81
  OPERATION_TIMEOUT = "operation_timeout"
81
- # Input validation events
82
+
82
83
  INVALID_INPUT = "invalid_input"
83
84
 
84
85
 
@@ -125,7 +126,12 @@ class SecurityLogger:
125
126
 
126
127
  if not self.logger.handlers:
127
128
  console_handler = logging.StreamHandler()
128
- console_handler.setLevel(logging.WARNING)
129
+
130
+ debug_enabled = os.environ.get("CRACKERJACK_DEBUG", "0") == "1"
131
+ if debug_enabled:
132
+ console_handler.setLevel(logging.WARNING)
133
+ else:
134
+ console_handler.setLevel(logging.CRITICAL + 1)
129
135
 
130
136
  formatter = logging.Formatter(
131
137
  "%(asctime)s - SECURITY - %(levelname)s-%(message)s"
@@ -296,12 +302,11 @@ class SecurityLogger:
296
302
  env_vars_count: int = 0,
297
303
  **kwargs: t.Any,
298
304
  ) -> None:
299
- """Log secure subprocess execution."""
300
305
  self.log_security_event(
301
306
  SecurityEventType.SUBPROCESS_EXECUTION,
302
307
  SecurityEventLevel.LOW,
303
308
  f"Subprocess executed: {' '.join(command[:3])}{'...' if len(command) > 3 else ''}",
304
- command=command[:10], # Log first 10 args only
309
+ command=command[:10],
305
310
  cwd=cwd,
306
311
  env_vars_count=env_vars_count,
307
312
  **kwargs,
@@ -314,14 +319,13 @@ class SecurityLogger:
314
319
  filtered_vars: list[str],
315
320
  **kwargs: t.Any,
316
321
  ) -> None:
317
- """Log environment sanitization for subprocess."""
318
322
  self.log_security_event(
319
323
  SecurityEventType.SUBPROCESS_ENVIRONMENT_SANITIZED,
320
324
  SecurityEventLevel.LOW,
321
325
  f"Environment sanitized: {original_count} -> {sanitized_count} vars",
322
326
  original_count=original_count,
323
327
  sanitized_count=sanitized_count,
324
- filtered_vars=filtered_vars[:20], # Log first 20 filtered vars
328
+ filtered_vars=filtered_vars[:20],
325
329
  **kwargs,
326
330
  )
327
331
 
@@ -332,7 +336,6 @@ class SecurityLogger:
332
336
  issues: list[str] | None = None,
333
337
  **kwargs: t.Any,
334
338
  ) -> None:
335
- """Log subprocess command validation results."""
336
339
  level = SecurityEventLevel.LOW if validation_result else SecurityEventLevel.HIGH
337
340
  status = "passed" if validation_result else "failed"
338
341
 
@@ -353,7 +356,6 @@ class SecurityLogger:
353
356
  actual_duration: float,
354
357
  **kwargs: t.Any,
355
358
  ) -> None:
356
- """Log subprocess timeout events."""
357
359
  self.log_security_event(
358
360
  SecurityEventType.SUBPROCESS_TIMEOUT,
359
361
  SecurityEventLevel.MEDIUM,
@@ -367,14 +369,13 @@ class SecurityLogger:
367
369
  def log_subprocess_failure(
368
370
  self, command: list[str], exit_code: int, error_output: str, **kwargs: t.Any
369
371
  ) -> None:
370
- """Log subprocess execution failures."""
371
372
  self.log_security_event(
372
373
  SecurityEventType.SUBPROCESS_FAILURE,
373
374
  SecurityEventLevel.MEDIUM,
374
375
  f"Subprocess failed (exit code {exit_code}): {' '.join(command[:2])}{'...' if len(command) > 2 else ''}",
375
376
  command_preview=command[:3],
376
377
  exit_code=exit_code,
377
- error_preview=error_output[:200], # Log first 200 chars of error
378
+ error_preview=error_output[:200],
378
379
  **kwargs,
379
380
  )
380
381
 
@@ -385,7 +386,6 @@ class SecurityLogger:
385
386
  dangerous_patterns: list[str],
386
387
  **kwargs: t.Any,
387
388
  ) -> None:
388
- """Log blocking of dangerous commands."""
389
389
  self.log_security_event(
390
390
  SecurityEventType.DANGEROUS_COMMAND_BLOCKED,
391
391
  SecurityEventLevel.CRITICAL,
@@ -403,7 +403,6 @@ class SecurityLogger:
403
403
  value_preview: str | None = None,
404
404
  **kwargs: t.Any,
405
405
  ) -> None:
406
- """Log filtering of environment variables."""
407
406
  self.log_security_event(
408
407
  SecurityEventType.ENVIRONMENT_VARIABLE_FILTERED,
409
408
  SecurityEventLevel.LOW,
@@ -422,7 +421,6 @@ class SecurityLogger:
422
421
  data_keys: list[str] | None = None,
423
422
  **kwargs: t.Any,
424
423
  ) -> None:
425
- """Log status endpoint access attempts."""
426
424
  self.log_security_event(
427
425
  SecurityEventType.STATUS_ACCESS_ATTEMPT,
428
426
  SecurityEventLevel.LOW,
@@ -442,7 +440,6 @@ class SecurityLogger:
442
440
  patterns_matched: list[str] | None = None,
443
441
  **kwargs: t.Any,
444
442
  ) -> None:
445
- """Log sensitive data sanitization events."""
446
443
  self.log_security_event(
447
444
  SecurityEventType.SENSITIVE_DATA_SANITIZED,
448
445
  SecurityEventLevel.LOW,
@@ -462,7 +459,6 @@ class SecurityLogger:
462
459
  severity: str = "medium",
463
460
  **kwargs: t.Any,
464
461
  ) -> None:
465
- """Log potential information disclosure in status responses."""
466
462
  level_map = {
467
463
  "low": SecurityEventLevel.LOW,
468
464
  "medium": SecurityEventLevel.MEDIUM,