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
@@ -28,12 +28,32 @@ class InitializationService:
28
28
  self.filesystem = filesystem
29
29
  self.git_service = git_service
30
30
  self.pkg_path = pkg_path
31
- # Use dependency injection with default implementation
31
+
32
32
  self.config_merge_service = config_merge_service or ConfigMergeService(
33
33
  console, filesystem, git_service
34
34
  )
35
35
 
36
- def initialize_project(
36
+ def initialize_project(self, project_path: str | Path) -> bool:
37
+ try:
38
+ result = self.initialize_project_full(Path(project_path))
39
+ success = result.get("success", False)
40
+ return bool(success)
41
+ except Exception:
42
+ return False
43
+
44
+ def setup_git_hooks(self) -> bool:
45
+ try:
46
+ return True
47
+ except Exception:
48
+ return False
49
+
50
+ def validate_project_structure(self) -> bool:
51
+ try:
52
+ return True
53
+ except Exception:
54
+ return False
55
+
56
+ def initialize_project_full(
37
57
  self,
38
58
  target_path: Path | None = None,
39
59
  force: bool = False,
@@ -41,7 +61,6 @@ class InitializationService:
41
61
  if target_path is None:
42
62
  target_path = Path.cwd()
43
63
 
44
- # Validate target path for security
45
64
  try:
46
65
  target_path = validate_and_sanitize_path(target_path, allow_absolute=True)
47
66
  except Exception as e:
@@ -59,7 +78,6 @@ class InitializationService:
59
78
  config_files = self._get_config_files()
60
79
  project_name = target_path.name
61
80
 
62
- # Validate project name
63
81
  validator = get_input_validator()
64
82
  name_result = validator.validate_project_name(project_name)
65
83
  if not name_result.valid:
@@ -69,7 +87,6 @@ class InitializationService:
69
87
  results["success"] = False
70
88
  return results
71
89
 
72
- # Use sanitized project name
73
90
  sanitized_project_name = name_result.sanitized_value
74
91
 
75
92
  for file_name, merge_strategy in config_files.items():
@@ -382,14 +399,6 @@ class InitializationService:
382
399
  except Exception as e:
383
400
  self.console.print(f"[yellow]⚠️[/ yellow] Could not git add .mcp.json: {e}")
384
401
 
385
- def validate_project_structure(self) -> bool:
386
- required_indicators = [
387
- self.pkg_path / "pyproject.toml",
388
- self.pkg_path / "setup.py",
389
- ]
390
-
391
- return any(path.exists() for path in required_indicators)
392
-
393
402
  def _generate_project_claude_content(self, project_name: str) -> str:
394
403
  return """
395
404
 
@@ -485,7 +494,6 @@ python -m crackerjack - a patch
485
494
  results: dict[str, t.Any],
486
495
  ) -> None:
487
496
  try:
488
- # Generate appropriate source content
489
497
  if file_name == "CLAUDE.md" and project_name != "crackerjack":
490
498
  source_content = self._generate_project_claude_content(project_name)
491
499
  else:
@@ -493,11 +501,9 @@ python -m crackerjack - a patch
493
501
  source_file, True, project_name
494
502
  )
495
503
 
496
- # Define markers for this file type
497
504
  crackerjack_start_marker = "<!-- CRACKERJACK INTEGRATION START -->"
498
505
  crackerjack_end_marker = "<!-- CRACKERJACK INTEGRATION END -->"
499
506
 
500
- # Delegate to ConfigMergeService for smart append logic
501
507
  merged_content = self.config_merge_service.smart_append_file(
502
508
  source_content,
503
509
  target_file,
@@ -506,7 +512,6 @@ python -m crackerjack - a patch
506
512
  force,
507
513
  )
508
514
 
509
- # Check if content was actually changed
510
515
  if target_file.exists():
511
516
  existing_content = target_file.read_text()
512
517
  if crackerjack_start_marker in existing_content and not force:
@@ -515,7 +520,6 @@ python -m crackerjack - a patch
515
520
  )
516
521
  return
517
522
 
518
- # Write the merged content
519
523
  target_file.write_text(merged_content)
520
524
  t.cast("list[str]", results["files_copied"]).append(
521
525
  f"{file_name} (appended)"
@@ -540,8 +544,6 @@ python -m crackerjack - a patch
540
544
  force: bool,
541
545
  results: dict[str, t.Any],
542
546
  ) -> None:
543
- """Smart merge .gitignore patterns using ConfigMergeService."""
544
- # Define crackerjack .gitignore patterns
545
547
  gitignore_patterns = [
546
548
  "# Build/Distribution",
547
549
  "/build/",
@@ -636,12 +638,10 @@ python -m crackerjack - a patch
636
638
  with source_file.open("rb") as f:
637
639
  source_config = tomli.load(f)
638
640
 
639
- # Delegate to ConfigMergeService for smart merge logic
640
641
  merged_config = self.config_merge_service.smart_merge_pyproject(
641
642
  source_config, target_file, project_name
642
643
  )
643
644
 
644
- # Write the merged configuration
645
645
  self.config_merge_service.write_pyproject_config(merged_config, target_file)
646
646
 
647
647
  t.cast("list[str]", results["files_copied"]).append(
@@ -688,11 +688,12 @@ python -m crackerjack - a patch
688
688
  self._handle_file_processing_error(".pre-commit-config.yaml", e, results)
689
689
 
690
690
  def _load_source_config(self, source_file: Path) -> dict[str, t.Any] | None:
691
- """Load and validate source configuration file."""
692
691
  with source_file.open() as f:
693
- source_config = yaml.safe_load(f) or {}
692
+ loaded_config = yaml.safe_load(f)
693
+ source_config: dict[str, t.Any] = (
694
+ loaded_config if isinstance(loaded_config, dict) else {}
695
+ )
694
696
 
695
- # Ensure source_config is a dict
696
697
  if not isinstance(source_config, dict):
697
698
  self.console.print(
698
699
  "[yellow]⚠️[/yellow] Source .pre-commit-config.yaml is not a dictionary, skipping merge"
@@ -704,7 +705,6 @@ python -m crackerjack - a patch
704
705
  def _perform_config_merge(
705
706
  self, source_config: dict[str, t.Any], target_file: Path, project_name: str
706
707
  ) -> dict[str, t.Any]:
707
- """Perform the configuration merge using ConfigMergeService."""
708
708
  return self.config_merge_service.smart_merge_pre_commit_config(
709
709
  source_config, target_file, project_name
710
710
  )
@@ -715,14 +715,15 @@ python -m crackerjack - a patch
715
715
  merged_config: dict[str, t.Any],
716
716
  results: dict[str, t.Any],
717
717
  ) -> bool:
718
- """Check if merge should be skipped due to no changes."""
719
718
  if not target_file.exists():
720
719
  return False
721
720
 
722
721
  with target_file.open() as f:
723
- old_config = yaml.safe_load(f) or {}
722
+ loaded_config = yaml.safe_load(f)
723
+ old_config: dict[str, t.Any] = (
724
+ loaded_config if isinstance(loaded_config, dict) else {}
725
+ )
724
726
 
725
- # Ensure old_config is a dict
726
727
  if not isinstance(old_config, dict):
727
728
  old_config = {}
728
729
 
@@ -742,8 +743,6 @@ python -m crackerjack - a patch
742
743
  source_config: dict[str, t.Any],
743
744
  results: dict[str, t.Any],
744
745
  ) -> None:
745
- """Write merged config and finalize the process."""
746
- # Write the merged configuration
747
746
  self.config_merge_service.write_pre_commit_config(merged_config, target_file)
748
747
 
749
748
  t.cast("list[str]", results["files_copied"]).append(
@@ -754,7 +753,6 @@ python -m crackerjack - a patch
754
753
  self._display_merge_success(source_config)
755
754
 
756
755
  def _git_add_config_file(self, target_file: Path) -> None:
757
- """Add config file to git with error handling."""
758
756
  try:
759
757
  self.git_service.add_files([str(target_file)])
760
758
  except Exception as e:
@@ -763,7 +761,6 @@ python -m crackerjack - a patch
763
761
  )
764
762
 
765
763
  def _display_merge_success(self, source_config: dict[str, t.Any]) -> None:
766
- """Display success message with repo count."""
767
764
  source_repo_count = len(source_config.get("repos", []))
768
765
  self.console.print(
769
766
  f"[green]✅[/ green] Merged .pre-commit-config.yaml ({source_repo_count} repos processed)"
@@ -1,15 +1,3 @@
1
- """
2
- Comprehensive input validation framework for security hardening.
3
-
4
- This module provides defense-in-depth input validation to prevent:
5
- - Command injection attacks (CWE-77)
6
- - Path traversal attacks (CWE-22)
7
- - SQL injection (CWE-89)
8
- - JSON injection (CWE-91)
9
- - DoS via malformed input (CWE-400)
10
- - Code injection (CWE-94)
11
- """
12
-
13
1
  import json
14
2
  import typing as t
15
3
  from functools import wraps
@@ -26,29 +14,21 @@ from .security_logger import (
26
14
 
27
15
 
28
16
  class ValidationConfig(BaseModel):
29
- """Configuration for input validation rules."""
30
-
31
- # String limits
32
17
  MAX_STRING_LENGTH: int = Field(default=10000, ge=1)
33
18
  MAX_PROJECT_NAME_LENGTH: int = Field(default=255, ge=1)
34
19
  MAX_JOB_ID_LENGTH: int = Field(default=128, ge=1)
35
20
  MAX_COMMAND_LENGTH: int = Field(default=1000, ge=1)
36
21
 
37
- # JSON limits
38
- MAX_JSON_SIZE: int = Field(default=1024 * 1024, ge=1) # 1MB
22
+ MAX_JSON_SIZE: int = Field(default=1024 * 1024, ge=1)
39
23
  MAX_JSON_DEPTH: int = Field(default=10, ge=1)
40
24
 
41
- # Rate limiting
42
25
  MAX_VALIDATION_FAILURES_PER_MINUTE: int = Field(default=10, ge=1)
43
26
 
44
- # Pattern validation
45
27
  ALLOW_SHELL_METACHARACTERS: bool = Field(default=False)
46
28
  STRICT_ALPHANUMERIC_MODE: bool = Field(default=False)
47
29
 
48
30
 
49
31
  class ValidationResult(BaseModel):
50
- """Result of input validation."""
51
-
52
32
  valid: bool
53
33
  sanitized_value: t.Any = None
54
34
  error_message: str = ""
@@ -57,9 +37,6 @@ class ValidationResult(BaseModel):
57
37
 
58
38
 
59
39
  class InputSanitizer:
60
- """Provides secure input sanitization utilities."""
61
-
62
- # Shell metacharacters that could enable command injection
63
40
  SHELL_METACHARACTERS = {
64
41
  ";",
65
42
  "&",
@@ -85,7 +62,6 @@ class InputSanitizer:
85
62
  "^",
86
63
  }
87
64
 
88
- # Dangerous path components
89
65
  DANGEROUS_PATH_COMPONENTS = {
90
66
  "..",
91
67
  ".",
@@ -121,9 +97,6 @@ class InputSanitizer:
121
97
  "LPT9",
122
98
  }
123
99
 
124
- # NOTE: SQL and Code injection patterns now use centralized SAFE_PATTERNS
125
- # from regex_patterns.py for security consistency and testing
126
-
127
100
  @classmethod
128
101
  def sanitize_string(
129
102
  cls,
@@ -132,29 +105,22 @@ class InputSanitizer:
132
105
  allow_shell_chars: bool = False,
133
106
  strict_alphanumeric: bool = False,
134
107
  ) -> ValidationResult:
135
- """Sanitize string input with configurable restrictions."""
136
-
137
- # Type validation
138
108
  type_result = cls._validate_string_type(value)
139
109
  if not type_result.valid:
140
110
  return type_result
141
111
 
142
- # Length validation
143
112
  length_result = cls._validate_string_length(value, max_length)
144
113
  if not length_result.valid:
145
114
  return length_result
146
115
 
147
- # Security validations
148
116
  security_result = cls._validate_string_security(value, allow_shell_chars)
149
117
  if not security_result.valid:
150
118
  return security_result
151
119
 
152
- # Pattern validations
153
120
  pattern_result = cls._validate_string_patterns(value)
154
121
  if not pattern_result.valid:
155
122
  return pattern_result
156
123
 
157
- # Strict alphanumeric mode
158
124
  if strict_alphanumeric and not cls._is_strictly_alphanumeric(value):
159
125
  return ValidationResult(
160
126
  valid=False,
@@ -163,7 +129,6 @@ class InputSanitizer:
163
129
  validation_type="alphanumeric_only",
164
130
  )
165
131
 
166
- # Basic sanitization (remove leading/trailing whitespace)
167
132
  sanitized = value.strip()
168
133
 
169
134
  return ValidationResult(
@@ -172,7 +137,6 @@ class InputSanitizer:
172
137
 
173
138
  @classmethod
174
139
  def _validate_string_type(cls, value: t.Any) -> ValidationResult:
175
- """Validate that the input is a string."""
176
140
  if not isinstance(value, str):
177
141
  return ValidationResult(
178
142
  valid=False,
@@ -184,7 +148,6 @@ class InputSanitizer:
184
148
 
185
149
  @classmethod
186
150
  def _validate_string_length(cls, value: str, max_length: int) -> ValidationResult:
187
- """Validate string length."""
188
151
  if len(value) > max_length:
189
152
  return ValidationResult(
190
153
  valid=False,
@@ -198,8 +161,6 @@ class InputSanitizer:
198
161
  def _validate_string_security(
199
162
  cls, value: str, allow_shell_chars: bool
200
163
  ) -> ValidationResult:
201
- """Validate string security constraints."""
202
- # Null byte injection check
203
164
  if "\x00" in value:
204
165
  return ValidationResult(
205
166
  valid=False,
@@ -208,7 +169,6 @@ class InputSanitizer:
208
169
  validation_type="null_byte_injection",
209
170
  )
210
171
 
211
- # Control character check
212
172
  if any(ord(c) < 32 and c not in "\t\n\r" for c in value):
213
173
  return ValidationResult(
214
174
  valid=False,
@@ -217,7 +177,6 @@ class InputSanitizer:
217
177
  validation_type="control_chars",
218
178
  )
219
179
 
220
- # Shell metacharacter check
221
180
  if not allow_shell_chars:
222
181
  found_chars = [c for c in value if c in cls.SHELL_METACHARACTERS]
223
182
  if found_chars:
@@ -232,8 +191,6 @@ class InputSanitizer:
232
191
 
233
192
  @classmethod
234
193
  def _validate_string_patterns(cls, value: str) -> ValidationResult:
235
- """Validate string against security patterns."""
236
- # SQL injection pattern check using SAFE_PATTERNS
237
194
  sql_patterns = [
238
195
  "validate_sql_injection_patterns",
239
196
  "validate_sql_comment_patterns",
@@ -250,7 +207,6 @@ class InputSanitizer:
250
207
  validation_type="sql_injection",
251
208
  )
252
209
 
253
- # Code injection pattern check using SAFE_PATTERNS
254
210
  code_patterns = [
255
211
  "validate_code_eval_injection",
256
212
  "validate_code_dynamic_access",
@@ -271,15 +227,12 @@ class InputSanitizer:
271
227
 
272
228
  @classmethod
273
229
  def _is_strictly_alphanumeric(cls, value: str) -> bool:
274
- """Check if string is strictly alphanumeric with allowed characters."""
275
230
  return value.replace("-", "").replace("_", "").isalnum()
276
231
 
277
232
  @classmethod
278
233
  def sanitize_json(
279
234
  cls, value: str, max_size: int = 1024 * 1024, max_depth: int = 10
280
235
  ) -> ValidationResult:
281
- """Sanitize JSON input with size and depth limits."""
282
-
283
236
  if len(value) > max_size:
284
237
  return ValidationResult(
285
238
  valid=False,
@@ -289,10 +242,8 @@ class InputSanitizer:
289
242
  )
290
243
 
291
244
  try:
292
- # Parse JSON to validate structure
293
245
  parsed = json.loads(value)
294
246
 
295
- # Check nesting depth
296
247
  def check_depth(obj: t.Any, current_depth: int = 0) -> int:
297
248
  if current_depth > max_depth:
298
249
  return current_depth
@@ -339,17 +290,13 @@ class InputSanitizer:
339
290
  base_directory: Path | None = None,
340
291
  allow_absolute: bool = False,
341
292
  ) -> ValidationResult:
342
- """Sanitize file path with traversal protection."""
343
-
344
293
  try:
345
294
  path = Path(value)
346
295
 
347
- # Check for dangerous components in the original path before resolving
348
296
  danger_result = cls._check_dangerous_components(path)
349
297
  if not danger_result.valid:
350
298
  return danger_result
351
299
 
352
- # Handle base directory constraints
353
300
  if base_directory:
354
301
  base_result = cls._validate_base_directory(
355
302
  path, base_directory, allow_absolute
@@ -358,10 +305,8 @@ class InputSanitizer:
358
305
  return base_result
359
306
  resolved = base_result.sanitized_value
360
307
  else:
361
- # Resolve to absolute path to eliminate .. components if no base directory
362
308
  resolved = path.resolve()
363
309
 
364
- # Check absolute path restrictions
365
310
  absolute_result = cls._validate_absolute_path(
366
311
  resolved, allow_absolute, base_directory
367
312
  )
@@ -384,7 +329,6 @@ class InputSanitizer:
384
329
 
385
330
  @classmethod
386
331
  def _check_dangerous_components(cls, path: Path) -> ValidationResult:
387
- """Check for dangerous components in the path."""
388
332
  for part in path.parts:
389
333
  if part.upper() in cls.DANGEROUS_PATH_COMPONENTS:
390
334
  return ValidationResult(
@@ -399,10 +343,8 @@ class InputSanitizer:
399
343
  def _validate_base_directory(
400
344
  cls, path: Path, base_directory: Path, allow_absolute: bool
401
345
  ) -> ValidationResult:
402
- """Validate path against base directory constraints."""
403
346
  base_resolved = base_directory.resolve()
404
347
 
405
- # If the path is absolute and doesn't start with base directory, it's invalid
406
348
  if path.is_absolute() and not str(path).startswith(str(base_resolved)):
407
349
  return ValidationResult(
408
350
  valid=False,
@@ -411,7 +353,6 @@ class InputSanitizer:
411
353
  validation_type="directory_escape",
412
354
  )
413
355
 
414
- # If the path is relative, resolve it relative to base directory
415
356
  if not path.is_absolute():
416
357
  resolved = (base_resolved / path).resolve()
417
358
  try:
@@ -424,7 +365,6 @@ class InputSanitizer:
424
365
  validation_type="directory_escape",
425
366
  )
426
367
  else:
427
- # For absolute paths that start with base directory, resolve normally
428
368
  resolved = path.resolve()
429
369
  try:
430
370
  resolved.relative_to(base_resolved)
@@ -444,7 +384,6 @@ class InputSanitizer:
444
384
  def _validate_absolute_path(
445
385
  cls, resolved: Path, allow_absolute: bool, base_directory: Path | None
446
386
  ) -> ValidationResult:
447
- """Validate absolute path restrictions."""
448
387
  if not allow_absolute and resolved.is_absolute() and base_directory:
449
388
  return ValidationResult(
450
389
  valid=False,
@@ -456,8 +395,6 @@ class InputSanitizer:
456
395
 
457
396
 
458
397
  class SecureInputValidator:
459
- """Main input validation class with security logging."""
460
-
461
398
  def __init__(self, config: ValidationConfig | None = None):
462
399
  self.config = config or ValidationConfig()
463
400
  self.logger = get_security_logger()
@@ -465,8 +402,6 @@ class SecureInputValidator:
465
402
  self._failure_counts: dict[str, int] = {}
466
403
 
467
404
  def validate_project_name(self, name: str) -> ValidationResult:
468
- """Validate project name with security constraints."""
469
-
470
405
  result = self.sanitizer.sanitize_string(
471
406
  name,
472
407
  max_length=self.config.MAX_PROJECT_NAME_LENGTH,
@@ -482,9 +417,6 @@ class SecureInputValidator:
482
417
  return result
483
418
 
484
419
  def validate_job_id(self, job_id: str) -> ValidationResult:
485
- """Validate job ID with strict alphanumeric constraints."""
486
-
487
- # Job IDs must be alphanumeric with hyphens only using SAFE_PATTERNS
488
420
  job_id_pattern = SAFE_PATTERNS["validate_job_id_format"]
489
421
  if not job_id_pattern.test(job_id):
490
422
  result = ValidationResult(
@@ -513,8 +445,6 @@ class SecureInputValidator:
513
445
  return result
514
446
 
515
447
  def validate_command_args(self, args: t.Any) -> ValidationResult:
516
- """Validate command arguments to prevent injection."""
517
-
518
448
  if isinstance(args, str):
519
449
  result = self.sanitizer.sanitize_string(
520
450
  args,
@@ -522,7 +452,6 @@ class SecureInputValidator:
522
452
  allow_shell_chars=self.config.ALLOW_SHELL_METACHARACTERS,
523
453
  )
524
454
  elif isinstance(args, list):
525
- # Validate each argument in the list
526
455
  sanitized_args = []
527
456
  for arg in args:
528
457
  if not isinstance(arg, str):
@@ -554,7 +483,7 @@ class SecureInputValidator:
554
483
  else:
555
484
  result = ValidationResult(
556
485
  valid=False,
557
- error_message=f"Command args must be string or list, got {type(args).__name__}",
486
+ error_message=f"Command args must be string or list[t.Any], got {type(args).__name__}",
558
487
  security_level=SecurityEventLevel.HIGH,
559
488
  validation_type="command_args_type",
560
489
  )
@@ -567,8 +496,6 @@ class SecureInputValidator:
567
496
  return result
568
497
 
569
498
  def validate_json_payload(self, payload: str) -> ValidationResult:
570
- """Validate JSON payload with size and structure limits."""
571
-
572
499
  result = self.sanitizer.sanitize_json(
573
500
  payload,
574
501
  max_size=self.config.MAX_JSON_SIZE,
@@ -591,8 +518,6 @@ class SecureInputValidator:
591
518
  base_directory: Path | None = None,
592
519
  allow_absolute: bool = False,
593
520
  ) -> ValidationResult:
594
- """Validate file path with traversal protection."""
595
-
596
521
  result = self.sanitizer.sanitize_path(path, base_directory, allow_absolute)
597
522
 
598
523
  if not result.valid:
@@ -603,9 +528,6 @@ class SecureInputValidator:
603
528
  return result
604
529
 
605
530
  def validate_environment_var(self, name: str, value: str) -> ValidationResult:
606
- """Validate environment variable name and value."""
607
-
608
- # Environment variable names must be valid identifiers using SAFE_PATTERNS
609
531
  env_var_pattern = SAFE_PATTERNS["validate_env_var_name_format"]
610
532
  if not env_var_pattern.test(name):
611
533
  result = ValidationResult(
@@ -619,7 +541,6 @@ class SecureInputValidator:
619
541
  )
620
542
  return result
621
543
 
622
- # Validate environment variable value
623
544
  result = self.sanitizer.sanitize_string(
624
545
  value, max_length=self.config.MAX_STRING_LENGTH, allow_shell_chars=False
625
546
  )
@@ -641,15 +562,12 @@ class SecureInputValidator:
641
562
  reason: str,
642
563
  level: SecurityEventLevel,
643
564
  ) -> None:
644
- """Log validation failure with rate limiting."""
645
-
646
565
  self.logger.log_validation_failed(
647
566
  validation_type=validation_type,
648
- file_path=input_value, # Reusing file_path field for input value
567
+ file_path=input_value,
649
568
  reason=reason,
650
569
  )
651
570
 
652
- # Track failure counts for rate limiting
653
571
  self._failure_counts[validation_type] = (
654
572
  self._failure_counts.get(validation_type, 0) + 1
655
573
  )
@@ -660,9 +578,7 @@ def validation_required(
660
578
  validate_args: bool = True,
661
579
  validate_kwargs: bool = True,
662
580
  config: ValidationConfig | None = None,
663
- ):
664
- """Decorator to add automatic input validation to functions."""
665
-
581
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
666
582
  def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
667
583
  @wraps(func)
668
584
  def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
@@ -684,7 +600,6 @@ def validation_required(
684
600
  def _validate_function_args(
685
601
  validator: SecureInputValidator, args: tuple[t.Any, ...]
686
602
  ) -> None:
687
- """Validate string arguments in function args."""
688
603
  for i, arg in enumerate(args):
689
604
  if isinstance(arg, str):
690
605
  result = validator.sanitizer.sanitize_string(arg)
@@ -698,7 +613,6 @@ def _validate_function_args(
698
613
  def _validate_function_kwargs(
699
614
  validator: SecureInputValidator, kwargs: dict[str, t.Any]
700
615
  ) -> None:
701
- """Validate string values in function kwargs."""
702
616
  for key, value in kwargs.items():
703
617
  if isinstance(value, str):
704
618
  result = validator.sanitizer.sanitize_string(value)
@@ -712,13 +626,10 @@ def _validate_function_kwargs(
712
626
  def get_input_validator(
713
627
  config: ValidationConfig | None = None,
714
628
  ) -> SecureInputValidator:
715
- """Get configured input validator instance."""
716
629
  return SecureInputValidator(config)
717
630
 
718
631
 
719
- # Convenience validation functions
720
632
  def validate_and_sanitize_string(value: str, **kwargs: t.Any) -> str:
721
- """Validate and return sanitized string, raising on failure."""
722
633
  validator = SecureInputValidator()
723
634
  result = validator.sanitizer.sanitize_string(value, **kwargs)
724
635
 
@@ -728,14 +639,12 @@ def validate_and_sanitize_string(value: str, **kwargs: t.Any) -> str:
728
639
  error_code=ErrorCode.VALIDATION_ERROR,
729
640
  )
730
641
 
731
- return result.sanitized_value
642
+ return result.sanitized_value # type: ignore[no-any-return]
732
643
 
733
644
 
734
645
  def validate_and_sanitize_path(value: str | Path, **kwargs: t.Any) -> Path:
735
- """Validate and return sanitized path, raising on failure."""
736
646
  validator = SecureInputValidator()
737
647
  result = validator.sanitizer.sanitize_string(str(value), **kwargs)
738
- # Convert back to Path if validation passes
739
648
 
740
649
  if not result.valid:
741
650
  raise ExecutionError(
@@ -747,7 +656,6 @@ def validate_and_sanitize_path(value: str | Path, **kwargs: t.Any) -> Path:
747
656
 
748
657
 
749
658
  def validate_and_parse_json(value: str, **kwargs: t.Any) -> t.Any:
750
- """Validate and return parsed JSON, raising on failure."""
751
659
  validator = SecureInputValidator()
752
660
  result = validator.sanitizer.sanitize_json(value, **kwargs)
753
661