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.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +64 -6
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +257 -218
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +558 -240
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +161 -32
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +90 -105
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +18 -11
- crackerjack/services/config_merge.py +30 -85
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +41 -17
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Secure status formatter to prevent information disclosure vulnerabilities.
|
|
3
|
-
|
|
4
|
-
This module provides secure sanitization of status responses to prevent
|
|
5
|
-
leaking sensitive system information such as absolute paths, internal URLs,
|
|
6
|
-
configuration details, and other sensitive data.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
1
|
import tempfile
|
|
10
2
|
import typing as t
|
|
11
3
|
from enum import Enum
|
|
@@ -15,51 +7,37 @@ from .security_logger import get_security_logger
|
|
|
15
7
|
|
|
16
8
|
|
|
17
9
|
class StatusVerbosity(str, Enum):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
DETAILED = "detailed" # More detailed information for debugging
|
|
23
|
-
FULL = "full" # Complete information (for internal use only)
|
|
10
|
+
MINIMAL = "minimal"
|
|
11
|
+
STANDARD = "standard"
|
|
12
|
+
DETAILED = "detailed"
|
|
13
|
+
FULL = "full"
|
|
24
14
|
|
|
25
15
|
|
|
26
16
|
class SecureStatusFormatter:
|
|
27
|
-
"""
|
|
28
|
-
Secure status formatter with configurable verbosity levels.
|
|
29
|
-
|
|
30
|
-
Sanitizes sensitive information while preserving necessary operational data.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
# Patterns for sensitive information that should be masked or removed
|
|
34
17
|
SENSITIVE_PATTERNS = {
|
|
35
|
-
# Absolute system paths (replace with relative paths)
|
|
36
18
|
"absolute_paths": [
|
|
37
|
-
r"(/[^/\s]*){2,}",
|
|
38
|
-
rf"{tempfile.gettempdir()}/[^\s]*",
|
|
39
|
-
r"/var/[^\s]*",
|
|
40
|
-
r"/home/[^\s]*",
|
|
19
|
+
r"(/[^/\s]*){2, }",
|
|
20
|
+
rf"{tempfile.gettempdir()}/[^\s]*",
|
|
21
|
+
r"/var/[^\s]*",
|
|
22
|
+
r"/home/[^\s]*",
|
|
41
23
|
],
|
|
42
|
-
# URLs with localhost/internal IPs
|
|
43
24
|
"internal_urls": [
|
|
44
|
-
r"https
|
|
45
|
-
r"https
|
|
46
|
-
r"https
|
|
47
|
-
r"ws
|
|
48
|
-
r"ws
|
|
25
|
+
r"https?: //localhost: \d+",
|
|
26
|
+
r"https?: //127\.0\.0\.1: \d+",
|
|
27
|
+
r"https?: //0\.0\.0\.0: \d+",
|
|
28
|
+
r"ws: //localhost: \d+",
|
|
29
|
+
r"ws: //127\.0\.0\.1: \d+",
|
|
49
30
|
],
|
|
50
|
-
# Tokens and API keys
|
|
51
31
|
"secrets": [
|
|
52
|
-
r"[A-Za-z0-9]{20,}",
|
|
53
|
-
r"[A-Za-z0-9+/]{32,}={0,2}",
|
|
32
|
+
r"[A-Za-z0-9]{20, }",
|
|
33
|
+
r"[A-Za-z0-9+/]{32, }={0, 2}",
|
|
54
34
|
],
|
|
55
|
-
# Process IDs and system identifiers
|
|
56
35
|
"system_ids": [
|
|
57
|
-
r"pid
|
|
58
|
-
r"process_id
|
|
36
|
+
r"pid: \d+",
|
|
37
|
+
r"process_id: \s*\d+",
|
|
59
38
|
],
|
|
60
39
|
}
|
|
61
40
|
|
|
62
|
-
# Sensitive keys that should be masked or removed
|
|
63
41
|
SENSITIVE_KEYS = {
|
|
64
42
|
"remove_minimal": {
|
|
65
43
|
"progress_dir",
|
|
@@ -83,12 +61,6 @@ class SecureStatusFormatter:
|
|
|
83
61
|
}
|
|
84
62
|
|
|
85
63
|
def __init__(self, project_root: Path | None = None):
|
|
86
|
-
"""
|
|
87
|
-
Initialize secure status formatter.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
project_root: Project root path for relative path conversion
|
|
91
|
-
"""
|
|
92
64
|
self.project_root = project_root or Path.cwd()
|
|
93
65
|
self.security_logger = get_security_logger()
|
|
94
66
|
|
|
@@ -98,17 +70,6 @@ class SecureStatusFormatter:
|
|
|
98
70
|
verbosity: StatusVerbosity = StatusVerbosity.STANDARD,
|
|
99
71
|
user_context: str | None = None,
|
|
100
72
|
) -> dict[str, t.Any]:
|
|
101
|
-
"""
|
|
102
|
-
Format status data with security sanitization.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
status_data: Raw status data to sanitize
|
|
106
|
-
verbosity: Output verbosity level
|
|
107
|
-
user_context: Optional user context for logging
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
Sanitized status data appropriate for the verbosity level
|
|
111
|
-
"""
|
|
112
73
|
self._log_status_access(status_data, verbosity, user_context)
|
|
113
74
|
sanitized = self._prepare_data_for_sanitization(status_data)
|
|
114
75
|
sanitized = self._apply_all_sanitization_steps(sanitized, verbosity)
|
|
@@ -120,31 +81,27 @@ class SecureStatusFormatter:
|
|
|
120
81
|
verbosity: StatusVerbosity,
|
|
121
82
|
user_context: str | None,
|
|
122
83
|
) -> None:
|
|
123
|
-
"""Log status access attempt for security monitoring."""
|
|
124
84
|
self.security_logger.log_status_access_attempt(
|
|
125
85
|
endpoint="status_data",
|
|
126
86
|
verbosity_level=verbosity.value,
|
|
127
87
|
user_context=user_context,
|
|
128
|
-
data_keys=list(status_data.keys()),
|
|
88
|
+
data_keys=list[t.Any](status_data.keys()),
|
|
129
89
|
)
|
|
130
90
|
|
|
131
91
|
def _prepare_data_for_sanitization(
|
|
132
92
|
self, status_data: dict[str, t.Any]
|
|
133
93
|
) -> dict[str, t.Any]:
|
|
134
|
-
|
|
135
|
-
return self._deep_copy_dict(status_data)
|
|
94
|
+
return self._deep_copy_dict(status_data) # type: ignore[no-any-return]
|
|
136
95
|
|
|
137
96
|
def _apply_all_sanitization_steps(
|
|
138
97
|
self, data: dict[str, t.Any], verbosity: StatusVerbosity
|
|
139
98
|
) -> dict[str, t.Any]:
|
|
140
|
-
"""Apply all sanitization steps in proper order."""
|
|
141
99
|
data = self._apply_verbosity_filter(data, verbosity)
|
|
142
100
|
return self._sanitize_sensitive_data(data, verbosity)
|
|
143
101
|
|
|
144
102
|
def _add_security_metadata(
|
|
145
103
|
self, data: dict[str, t.Any], verbosity: StatusVerbosity
|
|
146
104
|
) -> dict[str, t.Any]:
|
|
147
|
-
"""Add security metadata to sanitized response."""
|
|
148
105
|
data["_security"] = {
|
|
149
106
|
"sanitized": True,
|
|
150
107
|
"verbosity": verbosity.value,
|
|
@@ -155,22 +112,17 @@ class SecureStatusFormatter:
|
|
|
155
112
|
def _apply_verbosity_filter(
|
|
156
113
|
self, data: dict[str, t.Any], verbosity: StatusVerbosity
|
|
157
114
|
) -> dict[str, t.Any]:
|
|
158
|
-
"""Apply verbosity-based key filtering."""
|
|
159
|
-
|
|
160
|
-
# Keys to remove based on verbosity level
|
|
161
115
|
if verbosity == StatusVerbosity.MINIMAL:
|
|
162
116
|
remove_keys = self.SENSITIVE_KEYS["remove_minimal"]
|
|
163
117
|
elif verbosity == StatusVerbosity.STANDARD:
|
|
164
118
|
remove_keys = self.SENSITIVE_KEYS["remove_standard"]
|
|
165
119
|
else:
|
|
166
|
-
remove_keys = set()
|
|
120
|
+
remove_keys = set()
|
|
167
121
|
|
|
168
|
-
|
|
169
|
-
for key in list(data.keys()):
|
|
122
|
+
for key in list[t.Any](data.keys()):
|
|
170
123
|
if key in remove_keys:
|
|
171
124
|
del data[key]
|
|
172
125
|
|
|
173
|
-
# Recursively apply to nested dictionaries
|
|
174
126
|
for key, value in data.items():
|
|
175
127
|
if isinstance(value, dict):
|
|
176
128
|
data[key] = self._apply_verbosity_filter(value, verbosity)
|
|
@@ -180,20 +132,14 @@ class SecureStatusFormatter:
|
|
|
180
132
|
def _sanitize_sensitive_data(
|
|
181
133
|
self, data: dict[str, t.Any], verbosity: StatusVerbosity
|
|
182
134
|
) -> dict[str, t.Any]:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return self._sanitize_recursive(data, verbosity)
|
|
135
|
+
return self._sanitize_recursive(data, verbosity) # type: ignore[no-any-return]
|
|
186
136
|
|
|
187
137
|
def _sanitize_recursive(self, obj: t.Any, verbosity: StatusVerbosity) -> t.Any:
|
|
188
|
-
"""Recursively sanitize data structures."""
|
|
189
|
-
|
|
190
138
|
if isinstance(obj, dict):
|
|
191
139
|
sanitized = {}
|
|
192
140
|
for key, value in obj.items():
|
|
193
|
-
# Sanitize the key
|
|
194
141
|
sanitized_key = self._sanitize_string(key, verbosity)
|
|
195
142
|
|
|
196
|
-
# Sanitize the value
|
|
197
143
|
if (
|
|
198
144
|
key in self.SENSITIVE_KEYS["mask"]
|
|
199
145
|
and verbosity != StatusVerbosity.FULL
|
|
@@ -215,16 +161,14 @@ class SecureStatusFormatter:
|
|
|
215
161
|
return obj
|
|
216
162
|
|
|
217
163
|
def _sanitize_string(self, text: str, verbosity: StatusVerbosity) -> str:
|
|
218
|
-
"""Sanitize string content based on verbosity level."""
|
|
219
164
|
if verbosity == StatusVerbosity.FULL:
|
|
220
|
-
return text
|
|
165
|
+
return text
|
|
221
166
|
|
|
222
167
|
return self._apply_string_sanitization_pipeline(text, verbosity)
|
|
223
168
|
|
|
224
169
|
def _apply_string_sanitization_pipeline(
|
|
225
170
|
self, text: str, verbosity: StatusVerbosity
|
|
226
171
|
) -> str:
|
|
227
|
-
"""Apply string sanitization steps in correct order."""
|
|
228
172
|
sanitized = self._sanitize_internal_urls(text)
|
|
229
173
|
sanitized = self._sanitize_paths(sanitized)
|
|
230
174
|
return self._apply_secret_masking_if_needed(sanitized, verbosity)
|
|
@@ -232,16 +176,13 @@ class SecureStatusFormatter:
|
|
|
232
176
|
def _apply_secret_masking_if_needed(
|
|
233
177
|
self, text: str, verbosity: StatusVerbosity
|
|
234
178
|
) -> str:
|
|
235
|
-
"""Apply secret masking for minimal verbosity only."""
|
|
236
179
|
if verbosity == StatusVerbosity.MINIMAL:
|
|
237
180
|
return self._mask_potential_secrets(text)
|
|
238
181
|
return text
|
|
239
182
|
|
|
240
183
|
def _sanitize_paths(self, text: str) -> str:
|
|
241
|
-
"""Convert absolute paths to relative paths where possible."""
|
|
242
184
|
from .regex_patterns import SAFE_PATTERNS
|
|
243
185
|
|
|
244
|
-
# Use validated patterns for path detection
|
|
245
186
|
unix_path_pattern = SAFE_PATTERNS.get("detect_absolute_unix_paths")
|
|
246
187
|
|
|
247
188
|
if not unix_path_pattern:
|
|
@@ -250,10 +191,9 @@ class SecureStatusFormatter:
|
|
|
250
191
|
return text
|
|
251
192
|
|
|
252
193
|
def _sanitize_paths_fallback(self, text: str) -> str:
|
|
253
|
-
"""Fallback path sanitization when patterns don't exist."""
|
|
254
194
|
path_patterns = [
|
|
255
|
-
r"/[a-zA-Z0-9_\-\.\/]+",
|
|
256
|
-
r"[A-Z]:[\\\/][a-zA-Z0-9_\-\.\\\/]+",
|
|
195
|
+
r"/[a-zA-Z0-9_\-\.\/]+",
|
|
196
|
+
r"[A-Z]: [\\\/][a-zA-Z0-9_\-\.\\\/]+",
|
|
257
197
|
]
|
|
258
198
|
|
|
259
199
|
for pattern_str in path_patterns:
|
|
@@ -262,7 +202,6 @@ class SecureStatusFormatter:
|
|
|
262
202
|
return text
|
|
263
203
|
|
|
264
204
|
def _process_path_pattern(self, text: str, pattern_str: str) -> str:
|
|
265
|
-
"""Process a single path pattern safely."""
|
|
266
205
|
from contextlib import suppress
|
|
267
206
|
|
|
268
207
|
from .regex_patterns import CompiledPatternCache
|
|
@@ -272,19 +211,17 @@ class SecureStatusFormatter:
|
|
|
272
211
|
matches = compiled.findall(text)
|
|
273
212
|
|
|
274
213
|
for match in matches:
|
|
275
|
-
if len(match) > 3:
|
|
214
|
+
if len(match) > 3:
|
|
276
215
|
text = self._replace_path_match(text, match)
|
|
277
216
|
|
|
278
217
|
return text
|
|
279
218
|
|
|
280
219
|
def _replace_path_match(self, text: str, match: str) -> str:
|
|
281
|
-
"""Replace a single path match with relative or redacted version."""
|
|
282
220
|
try:
|
|
283
221
|
abs_path = Path(match)
|
|
284
222
|
if abs_path.is_absolute():
|
|
285
223
|
return self._convert_to_relative_or_redact(text, match, abs_path)
|
|
286
224
|
except (ValueError, OSError):
|
|
287
|
-
# If path operations fail, mask it
|
|
288
225
|
text = text.replace(match, "[REDACTED_PATH]")
|
|
289
226
|
|
|
290
227
|
return text
|
|
@@ -292,22 +229,18 @@ class SecureStatusFormatter:
|
|
|
292
229
|
def _convert_to_relative_or_redact(
|
|
293
230
|
self, text: str, match: str, abs_path: Path
|
|
294
231
|
) -> str:
|
|
295
|
-
"""Convert absolute path to relative or redact if outside project."""
|
|
296
232
|
try:
|
|
297
233
|
rel_path = abs_path.relative_to(self.project_root)
|
|
298
234
|
return text.replace(match, f"./{rel_path}")
|
|
299
235
|
except (ValueError, OSError):
|
|
300
|
-
# If not within project, mask it
|
|
301
236
|
return text.replace(match, "[REDACTED_PATH]")
|
|
302
237
|
|
|
303
238
|
def _sanitize_internal_urls(self, text: str) -> str:
|
|
304
|
-
"""Replace internal URLs with generic placeholders."""
|
|
305
239
|
from .regex_patterns import sanitize_internal_urls
|
|
306
240
|
|
|
307
241
|
return sanitize_internal_urls(text)
|
|
308
242
|
|
|
309
243
|
def _mask_potential_secrets(self, text: str) -> str:
|
|
310
|
-
"""Mask strings that might be secrets."""
|
|
311
244
|
if self._should_skip_secret_masking(text):
|
|
312
245
|
return text
|
|
313
246
|
|
|
@@ -318,11 +251,9 @@ class SecureStatusFormatter:
|
|
|
318
251
|
return self._apply_fallback_secret_patterns(text)
|
|
319
252
|
|
|
320
253
|
def _should_skip_secret_masking(self, text: str) -> bool:
|
|
321
|
-
"""Check if secret masking should be skipped for already sanitized content."""
|
|
322
254
|
return "[INTERNAL_URL]" in text or "[REDACTED_PATH]" in text
|
|
323
255
|
|
|
324
256
|
def _get_validated_secret_patterns(self) -> list[t.Any]:
|
|
325
|
-
"""Get validated patterns from the safe patterns registry."""
|
|
326
257
|
from .regex_patterns import SAFE_PATTERNS
|
|
327
258
|
|
|
328
259
|
patterns = []
|
|
@@ -337,16 +268,14 @@ class SecureStatusFormatter:
|
|
|
337
268
|
return patterns
|
|
338
269
|
|
|
339
270
|
def _apply_validated_secret_patterns(self, text: str, patterns: list[t.Any]) -> str:
|
|
340
|
-
"""Apply validated patterns for secret detection."""
|
|
341
271
|
for pattern in patterns:
|
|
342
272
|
try:
|
|
343
273
|
text = self._mask_pattern_matches(text, pattern.findall(text))
|
|
344
274
|
except Exception:
|
|
345
|
-
continue
|
|
275
|
+
continue
|
|
346
276
|
return text
|
|
347
277
|
|
|
348
278
|
def _apply_fallback_secret_patterns(self, text: str) -> str:
|
|
349
|
-
"""Apply fallback patterns when validated patterns don't exist."""
|
|
350
279
|
for pattern_str in self.SENSITIVE_PATTERNS["secrets"]:
|
|
351
280
|
try:
|
|
352
281
|
from .regex_patterns import CompiledPatternCache
|
|
@@ -354,11 +283,10 @@ class SecureStatusFormatter:
|
|
|
354
283
|
compiled = CompiledPatternCache.get_compiled_pattern(pattern_str)
|
|
355
284
|
text = self._mask_pattern_matches(text, compiled.findall(text))
|
|
356
285
|
except Exception:
|
|
357
|
-
continue
|
|
286
|
+
continue
|
|
358
287
|
return text
|
|
359
288
|
|
|
360
289
|
def _mask_pattern_matches(self, text: str, matches: list[str]) -> str:
|
|
361
|
-
"""Mask matches from pattern matching."""
|
|
362
290
|
for match in matches:
|
|
363
291
|
if self._should_mask_match(match):
|
|
364
292
|
masked = self._create_masked_string(match)
|
|
@@ -366,19 +294,15 @@ class SecureStatusFormatter:
|
|
|
366
294
|
return text
|
|
367
295
|
|
|
368
296
|
def _should_mask_match(self, match: str) -> bool:
|
|
369
|
-
"""Determine if a match should be masked."""
|
|
370
297
|
if len(match) <= 16:
|
|
371
298
|
return False
|
|
372
|
-
|
|
373
|
-
return not any(x in match for x in ("
|
|
299
|
+
|
|
300
|
+
return not any(x in match for x in (": //", "/", "\\", "."))
|
|
374
301
|
|
|
375
302
|
def _create_masked_string(self, text: str) -> str:
|
|
376
|
-
"""Create a masked version of the text."""
|
|
377
303
|
return text[:4] + "*" * (len(text) - 8) + text[-4:]
|
|
378
304
|
|
|
379
305
|
def _mask_sensitive_value(self, value: str) -> str:
|
|
380
|
-
"""Mask a known sensitive value."""
|
|
381
|
-
|
|
382
306
|
if len(value) <= 4:
|
|
383
307
|
return "***"
|
|
384
308
|
elif len(value) <= 8:
|
|
@@ -386,8 +310,6 @@ class SecureStatusFormatter:
|
|
|
386
310
|
return value[:2] + "*" * (len(value) - 4) + value[-2:]
|
|
387
311
|
|
|
388
312
|
def _deep_copy_dict(self, obj: t.Any) -> t.Any:
|
|
389
|
-
"""Deep copy a dictionary-like object safely."""
|
|
390
|
-
|
|
391
313
|
if isinstance(obj, dict):
|
|
392
314
|
return {key: self._deep_copy_dict(value) for key, value in obj.items()}
|
|
393
315
|
elif isinstance(obj, list):
|
|
@@ -395,7 +317,6 @@ class SecureStatusFormatter:
|
|
|
395
317
|
return obj
|
|
396
318
|
|
|
397
319
|
def _get_timestamp(self) -> float:
|
|
398
|
-
"""Get current timestamp."""
|
|
399
320
|
import time
|
|
400
321
|
|
|
401
322
|
return time.time()
|
|
@@ -406,17 +327,6 @@ class SecureStatusFormatter:
|
|
|
406
327
|
verbosity: StatusVerbosity = StatusVerbosity.STANDARD,
|
|
407
328
|
include_details: bool = False,
|
|
408
329
|
) -> dict[str, t.Any]:
|
|
409
|
-
"""
|
|
410
|
-
Format error responses without leaking system details.
|
|
411
|
-
|
|
412
|
-
Args:
|
|
413
|
-
error_message: Original error message
|
|
414
|
-
verbosity: Output verbosity level
|
|
415
|
-
include_details: Whether to include error details
|
|
416
|
-
|
|
417
|
-
Returns:
|
|
418
|
-
Sanitized error response
|
|
419
|
-
"""
|
|
420
330
|
error_type = self._classify_error(error_message)
|
|
421
331
|
|
|
422
332
|
if verbosity == StatusVerbosity.MINIMAL:
|
|
@@ -427,7 +337,6 @@ class SecureStatusFormatter:
|
|
|
427
337
|
)
|
|
428
338
|
|
|
429
339
|
def _create_minimal_error_response(self, error_type: str) -> dict[str, t.Any]:
|
|
430
|
-
"""Create minimal error response for production use."""
|
|
431
340
|
generic_messages = {
|
|
432
341
|
"connection": "Service temporarily unavailable. Please try again later.",
|
|
433
342
|
"validation": "Invalid request parameters.",
|
|
@@ -449,7 +358,6 @@ class SecureStatusFormatter:
|
|
|
449
358
|
verbosity: StatusVerbosity,
|
|
450
359
|
include_details: bool,
|
|
451
360
|
) -> dict[str, t.Any]:
|
|
452
|
-
"""Create detailed error response with sanitized message."""
|
|
453
361
|
sanitized_message = self._sanitize_string(error_message, verbosity)
|
|
454
362
|
|
|
455
363
|
response: dict[str, t.Any] = {
|
|
@@ -470,15 +378,12 @@ class SecureStatusFormatter:
|
|
|
470
378
|
def _should_include_error_details(
|
|
471
379
|
self, include_details: bool, verbosity: StatusVerbosity
|
|
472
380
|
) -> bool:
|
|
473
|
-
"""Determine if error details should be included."""
|
|
474
381
|
return include_details and verbosity in (
|
|
475
382
|
StatusVerbosity.DETAILED,
|
|
476
383
|
StatusVerbosity.FULL,
|
|
477
384
|
)
|
|
478
385
|
|
|
479
386
|
def _classify_error(self, error_message: str) -> str:
|
|
480
|
-
"""Classify error message type for appropriate handling."""
|
|
481
|
-
|
|
482
387
|
error_patterns = {
|
|
483
388
|
"connection": ["connection", "timeout", "refused", "unavailable"],
|
|
484
389
|
"validation": ["invalid", "validation", "format", "parameter"],
|
|
@@ -495,15 +400,12 @@ class SecureStatusFormatter:
|
|
|
495
400
|
return "internal"
|
|
496
401
|
|
|
497
402
|
|
|
498
|
-
# Singleton instance for global use
|
|
499
403
|
_secure_formatter: SecureStatusFormatter | None = None
|
|
500
404
|
|
|
501
405
|
|
|
502
406
|
def get_secure_status_formatter(
|
|
503
407
|
project_root: Path | None = None,
|
|
504
408
|
) -> SecureStatusFormatter:
|
|
505
|
-
"""Get the global secure status formatter instance."""
|
|
506
|
-
|
|
507
409
|
global _secure_formatter
|
|
508
410
|
if _secure_formatter is None:
|
|
509
411
|
_secure_formatter = SecureStatusFormatter(project_root)
|
|
@@ -516,19 +418,6 @@ def format_secure_status(
|
|
|
516
418
|
project_root: Path | None = None,
|
|
517
419
|
user_context: str | None = None,
|
|
518
420
|
) -> dict[str, t.Any]:
|
|
519
|
-
"""
|
|
520
|
-
Convenience function for secure status formatting.
|
|
521
|
-
|
|
522
|
-
Args:
|
|
523
|
-
status_data: Raw status data to sanitize
|
|
524
|
-
verbosity: Output verbosity level
|
|
525
|
-
project_root: Project root for relative path conversion
|
|
526
|
-
user_context: Optional user context for logging
|
|
527
|
-
|
|
528
|
-
Returns:
|
|
529
|
-
Sanitized status data
|
|
530
|
-
"""
|
|
531
|
-
|
|
532
421
|
return get_secure_status_formatter(project_root).format_status(
|
|
533
422
|
status_data, verbosity, user_context
|
|
534
423
|
)
|