omnimancer-cli 0.1.0__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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"""Main security manager that coordinates all security components."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
from .approval_workflow import ApprovalStatus, ApprovalWorkflow, RiskLevel
|
|
8
|
+
from .audit_logger import AuditEventType, AuditLevel, AuditLogger
|
|
9
|
+
from .permission_controller import (
|
|
10
|
+
PermissionController,
|
|
11
|
+
PermissionOperation,
|
|
12
|
+
)
|
|
13
|
+
from .sandbox_manager import ResourceLimits, SandboxManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SecurityManager:
|
|
17
|
+
"""Main security manager that coordinates all security components."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
enable_sandbox: bool = True,
|
|
22
|
+
enable_approval_workflow: bool = True,
|
|
23
|
+
enable_audit_logging: bool = True,
|
|
24
|
+
default_resource_limits: Optional[ResourceLimits] = None,
|
|
25
|
+
audit_log_file: Optional[str] = None,
|
|
26
|
+
):
|
|
27
|
+
|
|
28
|
+
# Initialize components
|
|
29
|
+
self.permissions = PermissionController()
|
|
30
|
+
|
|
31
|
+
self.sandbox = (
|
|
32
|
+
SandboxManager(default_resource_limits) if enable_sandbox else None
|
|
33
|
+
)
|
|
34
|
+
self.approval = ApprovalWorkflow() if enable_approval_workflow else None
|
|
35
|
+
self.audit = AuditLogger(audit_log_file) if enable_audit_logging else None
|
|
36
|
+
|
|
37
|
+
# Configuration
|
|
38
|
+
self.enable_sandbox = enable_sandbox
|
|
39
|
+
self.enable_approval_workflow = enable_approval_workflow
|
|
40
|
+
self.enable_audit_logging = enable_audit_logging
|
|
41
|
+
|
|
42
|
+
# Session tracking
|
|
43
|
+
self.session_id = str(uuid.uuid4())
|
|
44
|
+
self.operation_counter = 0
|
|
45
|
+
|
|
46
|
+
# Security policies
|
|
47
|
+
self.security_policies = self._get_default_security_policies()
|
|
48
|
+
|
|
49
|
+
if self.audit:
|
|
50
|
+
self.audit.log_event(
|
|
51
|
+
AuditEventType.SYSTEM_EVENT,
|
|
52
|
+
AuditLevel.INFO,
|
|
53
|
+
"SecurityManager initialized",
|
|
54
|
+
session_id=self.session_id,
|
|
55
|
+
metadata={
|
|
56
|
+
"sandbox_enabled": enable_sandbox,
|
|
57
|
+
"approval_workflow_enabled": enable_approval_workflow,
|
|
58
|
+
"audit_logging_enabled": enable_audit_logging,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _get_default_security_policies(self) -> Dict[str, Any]:
|
|
63
|
+
"""Get default security policies."""
|
|
64
|
+
return {
|
|
65
|
+
"require_approval_for_high_risk": True,
|
|
66
|
+
"sandbox_all_commands": True,
|
|
67
|
+
"log_all_operations": True,
|
|
68
|
+
"block_system_directories": True,
|
|
69
|
+
"block_credential_files": True,
|
|
70
|
+
"max_command_timeout": 300, # 5 minutes
|
|
71
|
+
"max_file_size_mb": 100,
|
|
72
|
+
"allowed_network_domains": [],
|
|
73
|
+
"blocked_network_domains": ["*.internal", "*.local"],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async def validate_operation(
|
|
77
|
+
self, operation: PermissionOperation
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""Validate if an operation is allowed and safe to execute."""
|
|
80
|
+
|
|
81
|
+
operation_id = f"op_{self.operation_counter}"
|
|
82
|
+
self.operation_counter += 1
|
|
83
|
+
|
|
84
|
+
result = {
|
|
85
|
+
"allowed": False,
|
|
86
|
+
"operation_id": operation_id,
|
|
87
|
+
"session_id": self.session_id,
|
|
88
|
+
"reasons": [],
|
|
89
|
+
"approval_required": False,
|
|
90
|
+
"approval_request_id": None,
|
|
91
|
+
"sandbox_required": False,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
# Step 1: Basic permission check
|
|
96
|
+
permission_allowed = self.permissions.validate_operation(operation)
|
|
97
|
+
|
|
98
|
+
if self.audit:
|
|
99
|
+
self.audit.log_permission_check(
|
|
100
|
+
operation.operation_type,
|
|
101
|
+
path=operation.path,
|
|
102
|
+
allowed=permission_allowed,
|
|
103
|
+
operation_id=operation_id,
|
|
104
|
+
session_id=self.session_id,
|
|
105
|
+
metadata=operation.metadata,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if not permission_allowed:
|
|
109
|
+
result["reasons"].append("Permission denied by security policy")
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
# Step 2: Risk assessment and approval workflow
|
|
113
|
+
if self.approval and self.security_policies.get(
|
|
114
|
+
"require_approval_for_high_risk", True
|
|
115
|
+
):
|
|
116
|
+
risk_level = self.approval.assess_risk_level(
|
|
117
|
+
operation.operation_type, operation.metadata
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Check if approval is required
|
|
121
|
+
if risk_level in [RiskLevel.HIGH, RiskLevel.CRITICAL]:
|
|
122
|
+
approval_request = await self.approval.request_approval(
|
|
123
|
+
operation.operation_type,
|
|
124
|
+
f"Operation: {operation.operation_type}",
|
|
125
|
+
operation.metadata,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if approval_request.status == ApprovalStatus.PENDING:
|
|
129
|
+
result["approval_required"] = True
|
|
130
|
+
result["approval_request_id"] = approval_request.id
|
|
131
|
+
result["reasons"].append(
|
|
132
|
+
f"Approval required for {risk_level.value} risk operation"
|
|
133
|
+
)
|
|
134
|
+
return result
|
|
135
|
+
elif approval_request.status != ApprovalStatus.APPROVED:
|
|
136
|
+
result["reasons"].append(
|
|
137
|
+
f"Operation not approved: {approval_request.status.value}"
|
|
138
|
+
)
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
# Step 3: Determine if sandboxing is required
|
|
142
|
+
if self.sandbox and self.security_policies.get(
|
|
143
|
+
"sandbox_all_commands", True
|
|
144
|
+
):
|
|
145
|
+
if operation.command or operation.operation_type in [
|
|
146
|
+
"command_execute",
|
|
147
|
+
"file_write",
|
|
148
|
+
"file_create",
|
|
149
|
+
]:
|
|
150
|
+
result["sandbox_required"] = True
|
|
151
|
+
|
|
152
|
+
# Step 4: All checks passed
|
|
153
|
+
result["allowed"] = True
|
|
154
|
+
result["reasons"].append("All security checks passed")
|
|
155
|
+
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
if self.audit:
|
|
160
|
+
self.audit.log_event(
|
|
161
|
+
AuditEventType.SECURITY_ALERT,
|
|
162
|
+
AuditLevel.ERROR,
|
|
163
|
+
f"Error validating operation: {str(e)}",
|
|
164
|
+
operation_id=operation_id,
|
|
165
|
+
session_id=self.session_id,
|
|
166
|
+
metadata={
|
|
167
|
+
"operation_type": operation.operation_type,
|
|
168
|
+
"error": str(e),
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
result["reasons"].append(f"Security validation error: {str(e)}")
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
async def execute_secure_command(
|
|
176
|
+
self,
|
|
177
|
+
command: Union[str, List[str]],
|
|
178
|
+
working_dir: Optional[str] = None,
|
|
179
|
+
env_vars: Optional[Dict[str, str]] = None,
|
|
180
|
+
resource_limits: Optional[ResourceLimits] = None,
|
|
181
|
+
require_approval: bool = False,
|
|
182
|
+
) -> Dict[str, Any]:
|
|
183
|
+
"""Execute a command with full security validation and sandboxing."""
|
|
184
|
+
|
|
185
|
+
# Convert command to list if it's a string
|
|
186
|
+
if isinstance(command, str):
|
|
187
|
+
command_list = command.split()
|
|
188
|
+
else:
|
|
189
|
+
command_list = command
|
|
190
|
+
command = " ".join(command_list)
|
|
191
|
+
|
|
192
|
+
operation_id = f"cmd_{self.operation_counter}"
|
|
193
|
+
self.operation_counter += 1
|
|
194
|
+
|
|
195
|
+
result = {
|
|
196
|
+
"success": False,
|
|
197
|
+
"operation_id": operation_id,
|
|
198
|
+
"session_id": self.session_id,
|
|
199
|
+
"return_code": -1,
|
|
200
|
+
"stdout": "",
|
|
201
|
+
"stderr": "",
|
|
202
|
+
"security_info": {},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
# Create operation for validation
|
|
207
|
+
operation = PermissionOperation(
|
|
208
|
+
operation_type="command_execute",
|
|
209
|
+
command=command,
|
|
210
|
+
path=working_dir,
|
|
211
|
+
env_vars=env_vars,
|
|
212
|
+
require_approval=require_approval,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Validate operation
|
|
216
|
+
validation_result = await self.validate_operation(operation)
|
|
217
|
+
result["security_info"] = validation_result
|
|
218
|
+
|
|
219
|
+
if not validation_result["allowed"]:
|
|
220
|
+
if self.audit:
|
|
221
|
+
self.audit.log_command_execution(
|
|
222
|
+
command,
|
|
223
|
+
success=False,
|
|
224
|
+
operation_id=operation_id,
|
|
225
|
+
session_id=self.session_id,
|
|
226
|
+
metadata={"validation_result": validation_result},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
result["stderr"] = (
|
|
230
|
+
f"Command blocked: {', '.join(validation_result['reasons'])}"
|
|
231
|
+
)
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
# Handle approval if required
|
|
235
|
+
if validation_result.get("approval_required"):
|
|
236
|
+
result["stderr"] = (
|
|
237
|
+
f"Approval required. Request ID: {validation_result['approval_request_id']}"
|
|
238
|
+
)
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
# Execute command (with or without sandbox)
|
|
242
|
+
if validation_result.get("sandbox_required") and self.sandbox:
|
|
243
|
+
# Execute in sandbox
|
|
244
|
+
sandbox_result = self.sandbox.execute_sandboxed_command(
|
|
245
|
+
command_list,
|
|
246
|
+
working_dir=working_dir,
|
|
247
|
+
env_vars=env_vars,
|
|
248
|
+
limits=resource_limits,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
result.update(
|
|
252
|
+
{
|
|
253
|
+
"success": sandbox_result["success"],
|
|
254
|
+
"return_code": sandbox_result["return_code"],
|
|
255
|
+
"stdout": sandbox_result["stdout"],
|
|
256
|
+
"stderr": sandbox_result["stderr"],
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
result["security_info"]["sandbox_dir"] = sandbox_result["sandbox_dir"]
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
# Execute directly (not recommended for production)
|
|
263
|
+
import subprocess
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
process = subprocess.run(
|
|
267
|
+
command_list,
|
|
268
|
+
cwd=working_dir,
|
|
269
|
+
env=env_vars,
|
|
270
|
+
capture_output=True,
|
|
271
|
+
text=True,
|
|
272
|
+
timeout=self.security_policies.get("max_command_timeout", 300),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
result.update(
|
|
276
|
+
{
|
|
277
|
+
"success": process.returncode == 0,
|
|
278
|
+
"return_code": process.returncode,
|
|
279
|
+
"stdout": process.stdout,
|
|
280
|
+
"stderr": process.stderr,
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
except subprocess.TimeoutExpired:
|
|
285
|
+
result["stderr"] = "Command timed out"
|
|
286
|
+
except Exception as e:
|
|
287
|
+
result["stderr"] = f"Execution error: {str(e)}"
|
|
288
|
+
|
|
289
|
+
# Log the execution
|
|
290
|
+
if self.audit:
|
|
291
|
+
self.audit.log_command_execution(
|
|
292
|
+
command,
|
|
293
|
+
success=result["success"],
|
|
294
|
+
exit_code=result["return_code"],
|
|
295
|
+
operation_id=operation_id,
|
|
296
|
+
session_id=self.session_id,
|
|
297
|
+
sandbox_id=result["security_info"].get("sandbox_dir"),
|
|
298
|
+
metadata={"validation_result": validation_result},
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return result
|
|
302
|
+
|
|
303
|
+
except Exception as e:
|
|
304
|
+
if self.audit:
|
|
305
|
+
self.audit.log_event(
|
|
306
|
+
AuditEventType.SECURITY_ALERT,
|
|
307
|
+
AuditLevel.ERROR,
|
|
308
|
+
f"Error executing secure command: {str(e)}",
|
|
309
|
+
operation_id=operation_id,
|
|
310
|
+
session_id=self.session_id,
|
|
311
|
+
metadata={"command": command, "error": str(e)},
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
result["stderr"] = f"Security manager error: {str(e)}"
|
|
315
|
+
return result
|
|
316
|
+
|
|
317
|
+
async def secure_file_access(
|
|
318
|
+
self, file_path: str, operation: str, content: Optional[str] = None
|
|
319
|
+
) -> Dict[str, Any]:
|
|
320
|
+
"""Securely access a file with full validation."""
|
|
321
|
+
|
|
322
|
+
operation_id = f"file_{self.operation_counter}"
|
|
323
|
+
self.operation_counter += 1
|
|
324
|
+
|
|
325
|
+
result = {
|
|
326
|
+
"success": False,
|
|
327
|
+
"operation_id": operation_id,
|
|
328
|
+
"session_id": self.session_id,
|
|
329
|
+
"content": None,
|
|
330
|
+
"error": None,
|
|
331
|
+
"security_info": {},
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
# Create operation for validation
|
|
336
|
+
op = PermissionOperation(
|
|
337
|
+
operation_type=f"file_{operation}",
|
|
338
|
+
path=file_path,
|
|
339
|
+
content=content,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Validate operation
|
|
343
|
+
validation_result = await self.validate_operation(op)
|
|
344
|
+
result["security_info"] = validation_result
|
|
345
|
+
|
|
346
|
+
if not validation_result["allowed"]:
|
|
347
|
+
if self.audit:
|
|
348
|
+
self.audit.log_file_access(
|
|
349
|
+
file_path,
|
|
350
|
+
operation,
|
|
351
|
+
allowed=False,
|
|
352
|
+
operation_id=operation_id,
|
|
353
|
+
session_id=self.session_id,
|
|
354
|
+
metadata={"validation_result": validation_result},
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
result["error"] = (
|
|
358
|
+
f"File access blocked: {', '.join(validation_result['reasons'])}"
|
|
359
|
+
)
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
# Handle approval if required
|
|
363
|
+
if validation_result.get("approval_required"):
|
|
364
|
+
result["error"] = (
|
|
365
|
+
f"Approval required. Request ID: {validation_result['approval_request_id']}"
|
|
366
|
+
)
|
|
367
|
+
return result
|
|
368
|
+
|
|
369
|
+
# Perform file operation
|
|
370
|
+
file_path_obj = Path(file_path)
|
|
371
|
+
|
|
372
|
+
if operation == "read":
|
|
373
|
+
if file_path_obj.exists() and file_path_obj.is_file():
|
|
374
|
+
with open(file_path_obj, "r", encoding="utf-8") as f:
|
|
375
|
+
result["content"] = f.read()
|
|
376
|
+
result["success"] = True
|
|
377
|
+
else:
|
|
378
|
+
result["error"] = "File does not exist"
|
|
379
|
+
|
|
380
|
+
elif operation == "write":
|
|
381
|
+
if content is None:
|
|
382
|
+
result["error"] = "No content provided for write operation"
|
|
383
|
+
else:
|
|
384
|
+
# Check file size limits
|
|
385
|
+
max_size_mb = self.security_policies.get("max_file_size_mb", 100)
|
|
386
|
+
if len(content.encode("utf-8")) > max_size_mb * 1024 * 1024:
|
|
387
|
+
result["error"] = (
|
|
388
|
+
f"Content exceeds maximum file size ({max_size_mb}MB)"
|
|
389
|
+
)
|
|
390
|
+
else:
|
|
391
|
+
# Ensure directory exists
|
|
392
|
+
file_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
393
|
+
|
|
394
|
+
with open(file_path_obj, "w", encoding="utf-8") as f:
|
|
395
|
+
f.write(content)
|
|
396
|
+
result["success"] = True
|
|
397
|
+
|
|
398
|
+
elif operation == "delete":
|
|
399
|
+
if file_path_obj.exists():
|
|
400
|
+
file_path_obj.unlink()
|
|
401
|
+
result["success"] = True
|
|
402
|
+
else:
|
|
403
|
+
result["error"] = "File does not exist"
|
|
404
|
+
|
|
405
|
+
else:
|
|
406
|
+
result["error"] = f"Unsupported file operation: {operation}"
|
|
407
|
+
|
|
408
|
+
# Log the file access
|
|
409
|
+
if self.audit:
|
|
410
|
+
self.audit.log_file_access(
|
|
411
|
+
file_path,
|
|
412
|
+
operation,
|
|
413
|
+
allowed=result["success"],
|
|
414
|
+
file_size=(len(content.encode("utf-8")) if content else None),
|
|
415
|
+
operation_id=operation_id,
|
|
416
|
+
session_id=self.session_id,
|
|
417
|
+
metadata={"validation_result": validation_result},
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return result
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
if self.audit:
|
|
424
|
+
self.audit.log_event(
|
|
425
|
+
AuditEventType.SECURITY_ALERT,
|
|
426
|
+
AuditLevel.ERROR,
|
|
427
|
+
f"Error in secure file access: {str(e)}",
|
|
428
|
+
operation_id=operation_id,
|
|
429
|
+
session_id=self.session_id,
|
|
430
|
+
metadata={
|
|
431
|
+
"file_path": file_path,
|
|
432
|
+
"operation": operation,
|
|
433
|
+
"error": str(e),
|
|
434
|
+
},
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
result["error"] = f"Security manager error: {str(e)}"
|
|
438
|
+
return result
|
|
439
|
+
|
|
440
|
+
def get_restricted_paths(self) -> List[str]:
|
|
441
|
+
"""Get list of restricted file paths."""
|
|
442
|
+
return self.permissions.get_restricted_paths()
|
|
443
|
+
|
|
444
|
+
def get_allowed_commands(self) -> List[str]:
|
|
445
|
+
"""Get list of allowed commands."""
|
|
446
|
+
return self.permissions.get_allowed_commands()
|
|
447
|
+
|
|
448
|
+
def get_security_status(self) -> Dict[str, Any]:
|
|
449
|
+
"""Get current security manager status."""
|
|
450
|
+
|
|
451
|
+
status = {
|
|
452
|
+
"session_id": self.session_id,
|
|
453
|
+
"components": {
|
|
454
|
+
"permissions": True,
|
|
455
|
+
"sandbox": self.sandbox is not None,
|
|
456
|
+
"approval_workflow": self.approval is not None,
|
|
457
|
+
"audit_logging": self.audit is not None,
|
|
458
|
+
},
|
|
459
|
+
"policies": self.security_policies,
|
|
460
|
+
"operation_count": self.operation_counter,
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
# Add component-specific status
|
|
464
|
+
if self.sandbox:
|
|
465
|
+
status["sandbox_status"] = {
|
|
466
|
+
"active_processes": self.sandbox.get_active_process_count(),
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if self.approval:
|
|
470
|
+
status["approval_status"] = self.approval.get_approval_stats()
|
|
471
|
+
|
|
472
|
+
if self.audit:
|
|
473
|
+
status["audit_status"] = self.audit.get_statistics()
|
|
474
|
+
|
|
475
|
+
return status
|
|
476
|
+
|
|
477
|
+
def update_security_policy(self, policy_name: str, value: Any) -> bool:
|
|
478
|
+
"""Update a security policy."""
|
|
479
|
+
|
|
480
|
+
if policy_name in self.security_policies:
|
|
481
|
+
old_value = self.security_policies[policy_name]
|
|
482
|
+
self.security_policies[policy_name] = value
|
|
483
|
+
|
|
484
|
+
if self.audit:
|
|
485
|
+
self.audit.log_event(
|
|
486
|
+
AuditEventType.SYSTEM_EVENT,
|
|
487
|
+
AuditLevel.INFO,
|
|
488
|
+
f"Security policy updated: {policy_name}",
|
|
489
|
+
session_id=self.session_id,
|
|
490
|
+
metadata={
|
|
491
|
+
"policy_name": policy_name,
|
|
492
|
+
"old_value": old_value,
|
|
493
|
+
"new_value": value,
|
|
494
|
+
},
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
return True
|
|
498
|
+
|
|
499
|
+
return False
|
|
500
|
+
|
|
501
|
+
async def shutdown(self) -> None:
|
|
502
|
+
"""Shutdown the security manager gracefully."""
|
|
503
|
+
|
|
504
|
+
if self.audit:
|
|
505
|
+
self.audit.log_event(
|
|
506
|
+
AuditEventType.SYSTEM_EVENT,
|
|
507
|
+
AuditLevel.INFO,
|
|
508
|
+
"SecurityManager shutting down",
|
|
509
|
+
session_id=self.session_id,
|
|
510
|
+
metadata={"operation_count": self.operation_counter},
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# Cleanup components
|
|
514
|
+
if self.sandbox:
|
|
515
|
+
self.sandbox.cleanup_all_sandboxes()
|
|
516
|
+
|
|
517
|
+
if self.approval:
|
|
518
|
+
# Cancel pending approvals
|
|
519
|
+
pending = self.approval.get_pending_requests()
|
|
520
|
+
for request in pending:
|
|
521
|
+
self.approval.cancel_request(request.id)
|
|
522
|
+
|
|
523
|
+
if self.audit:
|
|
524
|
+
self.audit.shutdown()
|
|
525
|
+
|
|
526
|
+
def __del__(self):
|
|
527
|
+
"""Destructor to ensure proper cleanup."""
|
|
528
|
+
# Note: Cannot reliably run async cleanup in destructor
|
|
529
|
+
# Cleanup should be called explicitly via shutdown()
|
|
530
|
+
pass
|