crackerjack 0.31.10__py3-none-any.whl → 0.31.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +50 -9
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
- crackerjack-0.31.13.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Status Security Manager for comprehensive security controls.
|
|
3
|
+
|
|
4
|
+
Provides authentication, authorization, rate limiting, and resource protection
|
|
5
|
+
for status collection operations to prevent security vulnerabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
import typing as t
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .security_logger import SecurityEventLevel, SecurityEventType, get_security_logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StatusSecurityError(Exception):
|
|
19
|
+
"""Base exception for status security violations."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AccessDeniedError(StatusSecurityError):
|
|
25
|
+
"""Raised when access is denied to status information."""
|
|
26
|
+
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ResourceLimitExceededError(StatusSecurityError):
|
|
31
|
+
"""Raised when resource limits are exceeded."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RateLimitExceededError(StatusSecurityError):
|
|
37
|
+
"""Raised when rate limits are exceeded."""
|
|
38
|
+
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StatusSecurityManager:
|
|
43
|
+
"""
|
|
44
|
+
Comprehensive security manager for status operations.
|
|
45
|
+
|
|
46
|
+
Provides:
|
|
47
|
+
- Authentication and authorization controls
|
|
48
|
+
- Rate limiting for status requests
|
|
49
|
+
- Resource usage monitoring and limits
|
|
50
|
+
- Concurrent operation tracking
|
|
51
|
+
- Path traversal protection
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
max_concurrent_requests: int = 5,
|
|
57
|
+
rate_limit_per_minute: int = 60,
|
|
58
|
+
max_resource_usage_mb: int = 100,
|
|
59
|
+
allowed_paths: set[str] | None = None,
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Initialize status security manager.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
max_concurrent_requests: Maximum concurrent status requests
|
|
66
|
+
rate_limit_per_minute: Maximum requests per minute per client
|
|
67
|
+
max_resource_usage_mb: Maximum memory usage in MB
|
|
68
|
+
allowed_paths: Set of allowed paths for file operations
|
|
69
|
+
"""
|
|
70
|
+
self.max_concurrent_requests = max_concurrent_requests
|
|
71
|
+
self.rate_limit_per_minute = rate_limit_per_minute
|
|
72
|
+
self.max_resource_usage_mb = max_resource_usage_mb
|
|
73
|
+
self.allowed_paths = allowed_paths or set()
|
|
74
|
+
|
|
75
|
+
# Thread-safe tracking
|
|
76
|
+
self._lock = threading.RLock()
|
|
77
|
+
self._concurrent_requests = 0
|
|
78
|
+
self._rate_limit_tracker: dict[str, list[float]] = defaultdict(list)
|
|
79
|
+
self._resource_usage = 0.0
|
|
80
|
+
|
|
81
|
+
# Security logging
|
|
82
|
+
self.security_logger = get_security_logger()
|
|
83
|
+
|
|
84
|
+
# Active request tracking for resource cleanup
|
|
85
|
+
self._active_requests: set[str] = set()
|
|
86
|
+
|
|
87
|
+
def validate_request(
|
|
88
|
+
self,
|
|
89
|
+
client_id: str,
|
|
90
|
+
operation: str,
|
|
91
|
+
request_data: dict[str, t.Any] | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Validate status request for security compliance.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
client_id: Unique client identifier
|
|
98
|
+
operation: Operation being requested
|
|
99
|
+
request_data: Additional request data to validate
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
AccessDeniedError: If access is denied
|
|
103
|
+
RateLimitExceededError: If rate limit exceeded
|
|
104
|
+
ResourceLimitExceededError: If resource limits exceeded
|
|
105
|
+
"""
|
|
106
|
+
with self._lock:
|
|
107
|
+
# Check concurrent request limit
|
|
108
|
+
if self._concurrent_requests >= self.max_concurrent_requests:
|
|
109
|
+
self.security_logger.log_security_event(
|
|
110
|
+
event_type=SecurityEventType.RATE_LIMIT_EXCEEDED,
|
|
111
|
+
level=SecurityEventLevel.WARNING,
|
|
112
|
+
message=f"Concurrent request limit exceeded: {self._concurrent_requests}",
|
|
113
|
+
client_id=client_id,
|
|
114
|
+
operation=operation,
|
|
115
|
+
)
|
|
116
|
+
raise ResourceLimitExceededError(
|
|
117
|
+
f"Too many concurrent requests: {self._concurrent_requests}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Check rate limiting
|
|
121
|
+
self._check_rate_limit(client_id, operation)
|
|
122
|
+
|
|
123
|
+
# Validate request data if provided
|
|
124
|
+
if request_data:
|
|
125
|
+
self._validate_request_data(client_id, operation, request_data)
|
|
126
|
+
|
|
127
|
+
def _check_rate_limit(self, client_id: str, operation: str) -> None:
|
|
128
|
+
"""Check if client has exceeded rate limits."""
|
|
129
|
+
|
|
130
|
+
current_time = time.time()
|
|
131
|
+
client_requests = self._rate_limit_tracker[client_id]
|
|
132
|
+
|
|
133
|
+
# Remove requests older than 1 minute
|
|
134
|
+
client_requests[:] = [
|
|
135
|
+
req_time for req_time in client_requests if current_time - req_time < 60
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
# Check if limit exceeded
|
|
139
|
+
if len(client_requests) >= self.rate_limit_per_minute:
|
|
140
|
+
self.security_logger.log_security_event(
|
|
141
|
+
event_type=SecurityEventType.RATE_LIMIT_EXCEEDED,
|
|
142
|
+
level=SecurityEventLevel.WARNING,
|
|
143
|
+
message=f"Rate limit exceeded for client {client_id}",
|
|
144
|
+
client_id=client_id,
|
|
145
|
+
operation=operation,
|
|
146
|
+
)
|
|
147
|
+
raise RateLimitExceededError(
|
|
148
|
+
f"Rate limit exceeded: {len(client_requests)} requests in last minute"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Record this request
|
|
152
|
+
client_requests.append(current_time)
|
|
153
|
+
|
|
154
|
+
def _validate_request_data(
|
|
155
|
+
self,
|
|
156
|
+
client_id: str,
|
|
157
|
+
operation: str,
|
|
158
|
+
request_data: dict[str, t.Any],
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Validate request data for security issues."""
|
|
161
|
+
|
|
162
|
+
# Check for path traversal attempts
|
|
163
|
+
for key, value in request_data.items():
|
|
164
|
+
if isinstance(value, str):
|
|
165
|
+
if self._contains_path_traversal(value):
|
|
166
|
+
self.security_logger.log_security_event(
|
|
167
|
+
event_type=SecurityEventType.PATH_TRAVERSAL_ATTEMPT,
|
|
168
|
+
level=SecurityEventLevel.CRITICAL,
|
|
169
|
+
message=f"Path traversal attempt detected in {key}: {value}",
|
|
170
|
+
client_id=client_id,
|
|
171
|
+
operation=operation,
|
|
172
|
+
additional_data={"suspicious_value": value},
|
|
173
|
+
)
|
|
174
|
+
raise AccessDeniedError(f"Invalid path in parameter: {key}")
|
|
175
|
+
|
|
176
|
+
# Validate file paths if present
|
|
177
|
+
if "path" in request_data or "file_path" in request_data:
|
|
178
|
+
path_value = request_data.get("path") or request_data.get("file_path")
|
|
179
|
+
if path_value:
|
|
180
|
+
self._validate_file_path(client_id, operation, str(path_value))
|
|
181
|
+
|
|
182
|
+
def _contains_path_traversal(self, value: str) -> bool:
|
|
183
|
+
"""Check if value contains path traversal patterns."""
|
|
184
|
+
|
|
185
|
+
# Common path traversal patterns
|
|
186
|
+
traversal_patterns = [
|
|
187
|
+
"../",
|
|
188
|
+
"..\\",
|
|
189
|
+
"..%2f",
|
|
190
|
+
"..%5c",
|
|
191
|
+
"%2e%2e%2f",
|
|
192
|
+
"%2e%2e%5c",
|
|
193
|
+
"....//",
|
|
194
|
+
"....\\\\",
|
|
195
|
+
"..\\/",
|
|
196
|
+
"../\\",
|
|
197
|
+
"%252e%252e%252f", # Double URL encoded
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
value_lower = value.lower()
|
|
201
|
+
return any(pattern in value_lower for pattern in traversal_patterns)
|
|
202
|
+
|
|
203
|
+
def _validate_file_path(
|
|
204
|
+
self, client_id: str, operation: str, file_path: str
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Validate file path for security compliance."""
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
path = Path(file_path).resolve()
|
|
210
|
+
|
|
211
|
+
# Check if path is within allowed paths
|
|
212
|
+
if self.allowed_paths:
|
|
213
|
+
path_allowed = any(
|
|
214
|
+
path.is_relative_to(Path(allowed_path).resolve())
|
|
215
|
+
for allowed_path in self.allowed_paths
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if not path_allowed:
|
|
219
|
+
self.security_logger.log_security_event(
|
|
220
|
+
event_type=SecurityEventType.UNAUTHORIZED_ACCESS_ATTEMPT,
|
|
221
|
+
level=SecurityEventLevel.HIGH,
|
|
222
|
+
message=f"Access to unauthorized path: {path}",
|
|
223
|
+
client_id=client_id,
|
|
224
|
+
operation=operation,
|
|
225
|
+
additional_data={"requested_path": str(path)},
|
|
226
|
+
)
|
|
227
|
+
raise AccessDeniedError(f"Access denied to path: {file_path}")
|
|
228
|
+
|
|
229
|
+
except (OSError, ValueError) as e:
|
|
230
|
+
self.security_logger.log_security_event(
|
|
231
|
+
event_type=SecurityEventType.INVALID_INPUT,
|
|
232
|
+
level=SecurityEventLevel.WARNING,
|
|
233
|
+
message=f"Invalid file path: {file_path} - {e}",
|
|
234
|
+
client_id=client_id,
|
|
235
|
+
operation=operation,
|
|
236
|
+
)
|
|
237
|
+
raise AccessDeniedError(f"Invalid file path: {file_path}")
|
|
238
|
+
|
|
239
|
+
async def acquire_request_lock(
|
|
240
|
+
self,
|
|
241
|
+
client_id: str,
|
|
242
|
+
operation: str,
|
|
243
|
+
timeout: float = 30.0,
|
|
244
|
+
) -> "RequestLock":
|
|
245
|
+
"""
|
|
246
|
+
Acquire a request lock for concurrent operation tracking.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
client_id: Client identifier
|
|
250
|
+
operation: Operation being performed
|
|
251
|
+
timeout: Maximum wait time for lock acquisition
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
RequestLock context manager
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
ResourceLimitExceededError: If unable to acquire lock within timeout
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
request_id = f"{client_id}:{operation}:{int(time.time())}"
|
|
261
|
+
|
|
262
|
+
# Try to acquire lock with timeout
|
|
263
|
+
start_time = time.time()
|
|
264
|
+
while time.time() - start_time < timeout:
|
|
265
|
+
with self._lock:
|
|
266
|
+
if self._concurrent_requests < self.max_concurrent_requests:
|
|
267
|
+
self._concurrent_requests += 1
|
|
268
|
+
self._active_requests.add(request_id)
|
|
269
|
+
|
|
270
|
+
self.security_logger.log_security_event(
|
|
271
|
+
event_type=SecurityEventType.REQUEST_START,
|
|
272
|
+
level=SecurityEventLevel.INFO,
|
|
273
|
+
message=f"Request lock acquired: {request_id}",
|
|
274
|
+
client_id=client_id,
|
|
275
|
+
operation=operation,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return RequestLock(self, request_id, client_id, operation)
|
|
279
|
+
|
|
280
|
+
# Wait briefly before retrying
|
|
281
|
+
await asyncio.sleep(0.1)
|
|
282
|
+
|
|
283
|
+
# Timeout exceeded
|
|
284
|
+
self.security_logger.log_security_event(
|
|
285
|
+
event_type=SecurityEventType.REQUEST_TIMEOUT,
|
|
286
|
+
level=SecurityEventLevel.ERROR,
|
|
287
|
+
message=f"Request lock timeout: {request_id}",
|
|
288
|
+
client_id=client_id,
|
|
289
|
+
operation=operation,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
raise ResourceLimitExceededError(
|
|
293
|
+
f"Unable to acquire request lock within {timeout}s"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def _release_request_lock(
|
|
297
|
+
self, request_id: str, client_id: str, operation: str
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Release a request lock."""
|
|
300
|
+
|
|
301
|
+
with self._lock:
|
|
302
|
+
if request_id in self._active_requests:
|
|
303
|
+
self._active_requests.remove(request_id)
|
|
304
|
+
self._concurrent_requests = max(0, self._concurrent_requests - 1)
|
|
305
|
+
|
|
306
|
+
self.security_logger.log_security_event(
|
|
307
|
+
event_type=SecurityEventType.REQUEST_END,
|
|
308
|
+
level=SecurityEventLevel.INFO,
|
|
309
|
+
message=f"Request lock released: {request_id}",
|
|
310
|
+
client_id=client_id,
|
|
311
|
+
operation=operation,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def get_security_status(self) -> dict[str, t.Any]:
|
|
315
|
+
"""Get current security status and metrics."""
|
|
316
|
+
|
|
317
|
+
with self._lock:
|
|
318
|
+
current_time = time.time()
|
|
319
|
+
|
|
320
|
+
# Calculate recent request rates
|
|
321
|
+
recent_requests = 0
|
|
322
|
+
for client_requests in self._rate_limit_tracker.values():
|
|
323
|
+
recent_requests += len(
|
|
324
|
+
[
|
|
325
|
+
req_time
|
|
326
|
+
for req_time in client_requests
|
|
327
|
+
if current_time - req_time < 60
|
|
328
|
+
]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
"concurrent_requests": self._concurrent_requests,
|
|
333
|
+
"active_request_ids": list(self._active_requests),
|
|
334
|
+
"recent_requests_per_minute": recent_requests,
|
|
335
|
+
"rate_limit_clients": len(self._rate_limit_tracker),
|
|
336
|
+
"max_concurrent_limit": self.max_concurrent_requests,
|
|
337
|
+
"rate_limit_per_minute": self.rate_limit_per_minute,
|
|
338
|
+
"resource_usage_mb": self._resource_usage,
|
|
339
|
+
"max_resource_limit_mb": self.max_resource_usage_mb,
|
|
340
|
+
"allowed_paths_count": len(self.allowed_paths),
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class RequestLock:
|
|
345
|
+
"""Context manager for request lock acquisition and release."""
|
|
346
|
+
|
|
347
|
+
def __init__(
|
|
348
|
+
self,
|
|
349
|
+
security_manager: StatusSecurityManager,
|
|
350
|
+
request_id: str,
|
|
351
|
+
client_id: str,
|
|
352
|
+
operation: str,
|
|
353
|
+
):
|
|
354
|
+
self.security_manager = security_manager
|
|
355
|
+
self.request_id = request_id
|
|
356
|
+
self.client_id = client_id
|
|
357
|
+
self.operation = operation
|
|
358
|
+
|
|
359
|
+
async def __aenter__(self) -> "RequestLock":
|
|
360
|
+
return self
|
|
361
|
+
|
|
362
|
+
async def __aexit__(self, exc_type: t.Any, exc_val: t.Any, exc_tb: t.Any) -> None:
|
|
363
|
+
self.security_manager._release_request_lock(
|
|
364
|
+
self.request_id,
|
|
365
|
+
self.client_id,
|
|
366
|
+
self.operation,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# Global instance for singleton pattern
|
|
371
|
+
_security_manager: StatusSecurityManager | None = None
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def get_status_security_manager() -> StatusSecurityManager:
|
|
375
|
+
"""Get the global status security manager instance."""
|
|
376
|
+
|
|
377
|
+
global _security_manager
|
|
378
|
+
if _security_manager is None:
|
|
379
|
+
# Initialize with project-specific paths
|
|
380
|
+
import tempfile
|
|
381
|
+
from pathlib import Path
|
|
382
|
+
|
|
383
|
+
project_root = Path.cwd()
|
|
384
|
+
temp_dir = Path(tempfile.gettempdir())
|
|
385
|
+
allowed_paths = {
|
|
386
|
+
str(project_root),
|
|
387
|
+
str(project_root / "temp"),
|
|
388
|
+
str(
|
|
389
|
+
temp_dir / "crackerjack-mcp-progress"
|
|
390
|
+
), # B108: Use tempfile.gettempdir()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
_security_manager = StatusSecurityManager(
|
|
394
|
+
allowed_paths=allowed_paths,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
return _security_manager
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
async def validate_status_request(
|
|
401
|
+
client_id: str,
|
|
402
|
+
operation: str,
|
|
403
|
+
request_data: dict[str, t.Any] | None = None,
|
|
404
|
+
) -> None:
|
|
405
|
+
"""
|
|
406
|
+
Convenience function to validate status requests.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
client_id: Client identifier
|
|
410
|
+
operation: Operation being requested
|
|
411
|
+
request_data: Optional request data to validate
|
|
412
|
+
|
|
413
|
+
Raises:
|
|
414
|
+
StatusSecurityError: If security validation fails
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
security_manager = get_status_security_manager()
|
|
418
|
+
security_manager.validate_request(client_id, operation, request_data)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
async def secure_status_operation(
|
|
422
|
+
client_id: str,
|
|
423
|
+
operation: str,
|
|
424
|
+
timeout: float = 30.0,
|
|
425
|
+
) -> RequestLock:
|
|
426
|
+
"""
|
|
427
|
+
Acquire security lock for status operations.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
client_id: Client identifier
|
|
431
|
+
operation: Operation being performed
|
|
432
|
+
timeout: Maximum wait time for lock
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
RequestLock context manager
|
|
436
|
+
|
|
437
|
+
Raises:
|
|
438
|
+
ResourceLimitExceededError: If unable to acquire lock
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
security_manager = get_status_security_manager()
|
|
442
|
+
return await security_manager.acquire_request_lock(client_id, operation, timeout)
|