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,393 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Security-focused configuration validator for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module ensures that critical security settings cannot be accidentally disabled
|
|
5
|
+
and validates configuration against security best practices.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Tuple
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SecurityValidationError(Exception):
|
|
16
|
+
"""Raised when a security validation fails."""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConfigSecurityValidator:
|
|
22
|
+
"""Validates Omnimancer configurations for security compliance."""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.critical_security_settings = self._get_critical_security_settings()
|
|
26
|
+
self.recommended_security_settings = self._get_recommended_security_settings()
|
|
27
|
+
self.dangerous_combinations = self._get_dangerous_combinations()
|
|
28
|
+
|
|
29
|
+
def _get_critical_security_settings(self) -> Dict[str, Any]:
|
|
30
|
+
"""Settings that must never be disabled for security reasons."""
|
|
31
|
+
return {
|
|
32
|
+
"api_key_encryption_enabled": True,
|
|
33
|
+
"secure_storage_enabled": True,
|
|
34
|
+
"mcp.sandbox_mode": True,
|
|
35
|
+
"mcp.allow_dangerous_tools": False,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def _get_recommended_security_settings(self) -> Dict[str, Any]:
|
|
39
|
+
"""Recommended security settings that should be enabled by default."""
|
|
40
|
+
return {
|
|
41
|
+
"provider_health_check_enabled": True,
|
|
42
|
+
"request_cache_enabled": True, # Helps with rate limiting
|
|
43
|
+
"mcp.log_tool_calls": True, # Security auditing
|
|
44
|
+
"debug_mode": False, # Don't leak info in production
|
|
45
|
+
"telemetry_enabled": False, # Privacy
|
|
46
|
+
"mcp.max_tool_execution_time": 300, # Prevent runaway tools
|
|
47
|
+
"mcp.max_concurrent_servers": 10, # Resource limiting
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def _get_dangerous_combinations(self) -> List[Dict[str, Any]]:
|
|
51
|
+
"""Combinations of settings that create security risks."""
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
"description": "Debug mode with telemetry enabled can leak sensitive information",
|
|
55
|
+
"conditions": {"debug_mode": True, "telemetry_enabled": True},
|
|
56
|
+
"severity": "high",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "MCP sandbox disabled with dangerous tools allowed is extremely risky",
|
|
60
|
+
"conditions": {
|
|
61
|
+
"mcp.sandbox_mode": False,
|
|
62
|
+
"mcp.allow_dangerous_tools": True,
|
|
63
|
+
},
|
|
64
|
+
"severity": "critical",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"description": "No health checks with fallback disabled can cause reliability issues",
|
|
68
|
+
"conditions": {
|
|
69
|
+
"provider_health_check_enabled": False,
|
|
70
|
+
"provider_fallback_enabled": False,
|
|
71
|
+
},
|
|
72
|
+
"severity": "medium",
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
def validate_security(
|
|
77
|
+
self, config: Dict[str, Any], strict_mode: bool = True
|
|
78
|
+
) -> Tuple[bool, List[str], List[str]]:
|
|
79
|
+
"""
|
|
80
|
+
Validate configuration for security compliance.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
config: Configuration to validate
|
|
84
|
+
strict_mode: If True, treat security warnings as errors
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Tuple of (is_valid, errors, warnings)
|
|
88
|
+
"""
|
|
89
|
+
errors = []
|
|
90
|
+
warnings = []
|
|
91
|
+
|
|
92
|
+
# Check critical security settings
|
|
93
|
+
critical_errors = self._validate_critical_settings(config)
|
|
94
|
+
errors.extend(critical_errors)
|
|
95
|
+
|
|
96
|
+
# Check recommended security settings
|
|
97
|
+
recommended_warnings = self._validate_recommended_settings(config)
|
|
98
|
+
if strict_mode:
|
|
99
|
+
errors.extend(recommended_warnings)
|
|
100
|
+
else:
|
|
101
|
+
warnings.extend(recommended_warnings)
|
|
102
|
+
|
|
103
|
+
# Check dangerous combinations
|
|
104
|
+
combination_issues = self._validate_dangerous_combinations(config)
|
|
105
|
+
for issue in combination_issues:
|
|
106
|
+
if issue["severity"] in ["critical", "high"]:
|
|
107
|
+
errors.append(issue["message"])
|
|
108
|
+
else:
|
|
109
|
+
warnings.append(issue["message"])
|
|
110
|
+
|
|
111
|
+
# Validate API key security
|
|
112
|
+
api_key_issues = self._validate_api_key_security(config)
|
|
113
|
+
errors.extend(api_key_issues)
|
|
114
|
+
|
|
115
|
+
# Check file permissions
|
|
116
|
+
permission_warnings = self._validate_file_permissions(config)
|
|
117
|
+
warnings.extend(permission_warnings)
|
|
118
|
+
|
|
119
|
+
is_valid = len(errors) == 0
|
|
120
|
+
return is_valid, errors, warnings
|
|
121
|
+
|
|
122
|
+
def _validate_critical_settings(self, config: Dict[str, Any]) -> List[str]:
|
|
123
|
+
"""Validate critical security settings that must never be disabled."""
|
|
124
|
+
errors = []
|
|
125
|
+
|
|
126
|
+
for (
|
|
127
|
+
setting_path,
|
|
128
|
+
required_value,
|
|
129
|
+
) in self.critical_security_settings.items():
|
|
130
|
+
current_value = self._get_nested_value(config, setting_path)
|
|
131
|
+
|
|
132
|
+
if current_value != required_value:
|
|
133
|
+
errors.append(
|
|
134
|
+
f"CRITICAL: Security setting '{setting_path}' must be set to {required_value}, "
|
|
135
|
+
f"but is currently {current_value}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return errors
|
|
139
|
+
|
|
140
|
+
def _validate_recommended_settings(self, config: Dict[str, Any]) -> List[str]:
|
|
141
|
+
"""Validate recommended security settings."""
|
|
142
|
+
warnings = []
|
|
143
|
+
|
|
144
|
+
for (
|
|
145
|
+
setting_path,
|
|
146
|
+
recommended_value,
|
|
147
|
+
) in self.recommended_security_settings.items():
|
|
148
|
+
current_value = self._get_nested_value(config, setting_path)
|
|
149
|
+
|
|
150
|
+
if current_value != recommended_value:
|
|
151
|
+
warnings.append(
|
|
152
|
+
f"RECOMMENDED: Security setting '{setting_path}' should be set to {recommended_value}, "
|
|
153
|
+
f"but is currently {current_value}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return warnings
|
|
157
|
+
|
|
158
|
+
def _validate_dangerous_combinations(
|
|
159
|
+
self, config: Dict[str, Any]
|
|
160
|
+
) -> List[Dict[str, Any]]:
|
|
161
|
+
"""Check for dangerous combinations of settings."""
|
|
162
|
+
issues = []
|
|
163
|
+
|
|
164
|
+
for combination in self.dangerous_combinations:
|
|
165
|
+
all_conditions_met = True
|
|
166
|
+
|
|
167
|
+
for setting_path, required_value in combination["conditions"].items():
|
|
168
|
+
current_value = self._get_nested_value(config, setting_path)
|
|
169
|
+
if current_value != required_value:
|
|
170
|
+
all_conditions_met = False
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
if all_conditions_met:
|
|
174
|
+
issues.append(
|
|
175
|
+
{
|
|
176
|
+
"message": f"{combination['severity'].upper()}: {combination['description']}",
|
|
177
|
+
"severity": combination["severity"],
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return issues
|
|
182
|
+
|
|
183
|
+
def _validate_api_key_security(self, config: Dict[str, Any]) -> List[str]:
|
|
184
|
+
"""Validate API key security practices."""
|
|
185
|
+
errors = []
|
|
186
|
+
|
|
187
|
+
# Check if API keys are stored in plain text (should be encrypted)
|
|
188
|
+
if "providers" in config:
|
|
189
|
+
for provider_name, provider_config in config["providers"].items():
|
|
190
|
+
api_key = provider_config.get("api_key")
|
|
191
|
+
|
|
192
|
+
if api_key and isinstance(api_key, str):
|
|
193
|
+
# Check for obviously insecure patterns
|
|
194
|
+
if api_key in [
|
|
195
|
+
"test",
|
|
196
|
+
"demo",
|
|
197
|
+
"placeholder",
|
|
198
|
+
"your-api-key-here",
|
|
199
|
+
]:
|
|
200
|
+
errors.append(
|
|
201
|
+
f"Provider '{provider_name}' has placeholder API key"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Check if it looks like a real key but isn't encrypted
|
|
205
|
+
if api_key.startswith(
|
|
206
|
+
("sk-", "api-", "key-")
|
|
207
|
+
) and not api_key.startswith("encrypted:"):
|
|
208
|
+
# This might be a plain text key in an optimized config
|
|
209
|
+
# In production, these should be encrypted
|
|
210
|
+
pass # Don't warn for optimized configs as encryption happens at runtime
|
|
211
|
+
|
|
212
|
+
return errors
|
|
213
|
+
|
|
214
|
+
def _validate_file_permissions(self, config: Dict[str, Any]) -> List[str]:
|
|
215
|
+
"""Check file permissions for configuration and storage paths."""
|
|
216
|
+
warnings = []
|
|
217
|
+
|
|
218
|
+
storage_path = config.get("storage_path")
|
|
219
|
+
if storage_path:
|
|
220
|
+
storage_dir = Path(storage_path)
|
|
221
|
+
|
|
222
|
+
if storage_dir.exists():
|
|
223
|
+
# Check if directory is readable by others (Unix-like systems)
|
|
224
|
+
try:
|
|
225
|
+
stat_info = storage_dir.stat()
|
|
226
|
+
mode = stat_info.st_mode
|
|
227
|
+
|
|
228
|
+
# Check if group/others have read access (simplified check)
|
|
229
|
+
if mode & 0o044: # Group or others can read
|
|
230
|
+
warnings.append(
|
|
231
|
+
f"Storage directory '{storage_path}' may be readable by other users. "
|
|
232
|
+
f"Consider restricting permissions with: chmod 700 '{storage_path}'"
|
|
233
|
+
)
|
|
234
|
+
except Exception:
|
|
235
|
+
# Permission check failed, probably Windows or insufficient permissions
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
return warnings
|
|
239
|
+
|
|
240
|
+
def _get_nested_value(self, config: Dict[str, Any], path: str) -> Any:
|
|
241
|
+
"""Get a nested configuration value using dot notation."""
|
|
242
|
+
keys = path.split(".")
|
|
243
|
+
current = config
|
|
244
|
+
|
|
245
|
+
for key in keys:
|
|
246
|
+
if isinstance(current, dict) and key in current:
|
|
247
|
+
current = current[key]
|
|
248
|
+
else:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
return current
|
|
252
|
+
|
|
253
|
+
def fix_security_issues(
|
|
254
|
+
self, config: Dict[str, Any]
|
|
255
|
+
) -> Tuple[Dict[str, Any], List[str]]:
|
|
256
|
+
"""
|
|
257
|
+
Automatically fix security issues in configuration.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
config: Configuration to fix
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Tuple of (fixed_config, list of changes made)
|
|
264
|
+
"""
|
|
265
|
+
fixed_config = config.copy()
|
|
266
|
+
changes = []
|
|
267
|
+
|
|
268
|
+
# Fix critical security settings
|
|
269
|
+
for (
|
|
270
|
+
setting_path,
|
|
271
|
+
required_value,
|
|
272
|
+
) in self.critical_security_settings.items():
|
|
273
|
+
current_value = self._get_nested_value(fixed_config, setting_path)
|
|
274
|
+
|
|
275
|
+
if current_value != required_value:
|
|
276
|
+
self._set_nested_value(fixed_config, setting_path, required_value)
|
|
277
|
+
changes.append(
|
|
278
|
+
f"Fixed critical setting: {setting_path} = {required_value}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Apply recommended security settings
|
|
282
|
+
for (
|
|
283
|
+
setting_path,
|
|
284
|
+
recommended_value,
|
|
285
|
+
) in self.recommended_security_settings.items():
|
|
286
|
+
current_value = self._get_nested_value(fixed_config, setting_path)
|
|
287
|
+
|
|
288
|
+
if current_value is None: # Only set if not explicitly configured
|
|
289
|
+
self._set_nested_value(fixed_config, setting_path, recommended_value)
|
|
290
|
+
changes.append(
|
|
291
|
+
f"Applied recommended setting: {setting_path} = {recommended_value}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return fixed_config, changes
|
|
295
|
+
|
|
296
|
+
def _set_nested_value(self, config: Dict[str, Any], path: str, value: Any) -> None:
|
|
297
|
+
"""Set a nested configuration value using dot notation."""
|
|
298
|
+
keys = path.split(".")
|
|
299
|
+
current = config
|
|
300
|
+
|
|
301
|
+
# Navigate to parent
|
|
302
|
+
for key in keys[:-1]:
|
|
303
|
+
if key not in current or not isinstance(current[key], dict):
|
|
304
|
+
current[key] = {}
|
|
305
|
+
current = current[key]
|
|
306
|
+
|
|
307
|
+
# Set final value
|
|
308
|
+
current[keys[-1]] = value
|
|
309
|
+
|
|
310
|
+
def generate_security_report(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
|
311
|
+
"""
|
|
312
|
+
Generate a comprehensive security report for a configuration.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
config: Configuration to analyze
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Security report dictionary
|
|
319
|
+
"""
|
|
320
|
+
is_valid, errors, warnings = self.validate_security(config, strict_mode=False)
|
|
321
|
+
|
|
322
|
+
report = {
|
|
323
|
+
"timestamp": str(Path().resolve()),
|
|
324
|
+
"overall_status": (
|
|
325
|
+
"SECURE"
|
|
326
|
+
if is_valid and len(warnings) == 0
|
|
327
|
+
else "INSECURE" if not is_valid else "NEEDS_ATTENTION"
|
|
328
|
+
),
|
|
329
|
+
"critical_errors": len([e for e in errors if "CRITICAL:" in e]),
|
|
330
|
+
"security_errors": len(errors),
|
|
331
|
+
"security_warnings": len(warnings),
|
|
332
|
+
"errors": errors,
|
|
333
|
+
"warnings": warnings,
|
|
334
|
+
"recommendations": [],
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Add specific recommendations
|
|
338
|
+
if report["critical_errors"] > 0:
|
|
339
|
+
report["recommendations"].append(
|
|
340
|
+
"URGENT: Fix critical security issues immediately. "
|
|
341
|
+
"These settings compromise system security."
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if report["security_warnings"] > 0:
|
|
345
|
+
report["recommendations"].append(
|
|
346
|
+
"Review security warnings and consider applying recommended settings."
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if report["overall_status"] == "SECURE":
|
|
350
|
+
report["recommendations"].append(
|
|
351
|
+
"Configuration follows security best practices. "
|
|
352
|
+
"Continue monitoring for changes."
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Security score (0-100)
|
|
356
|
+
len(self.critical_security_settings) + len(self.recommended_security_settings)
|
|
357
|
+
actual_issues = len(errors) + len(warnings)
|
|
358
|
+
report["security_score"] = max(0, 100 - (actual_issues * 10))
|
|
359
|
+
|
|
360
|
+
return report
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def validate_config_security(
|
|
364
|
+
config: Dict[str, Any], strict_mode: bool = True
|
|
365
|
+
) -> Tuple[bool, List[str], List[str]]:
|
|
366
|
+
"""
|
|
367
|
+
Convenience function to validate configuration security.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
config: Configuration to validate
|
|
371
|
+
strict_mode: If True, treat warnings as errors
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Tuple of (is_valid, errors, warnings)
|
|
375
|
+
"""
|
|
376
|
+
validator = ConfigSecurityValidator()
|
|
377
|
+
return validator.validate_security(config, strict_mode)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def fix_config_security_issues(
|
|
381
|
+
config: Dict[str, Any],
|
|
382
|
+
) -> Tuple[Dict[str, Any], List[str]]:
|
|
383
|
+
"""
|
|
384
|
+
Convenience function to fix security issues in configuration.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
config: Configuration to fix
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Tuple of (fixed_config, list of changes)
|
|
391
|
+
"""
|
|
392
|
+
validator = ConfigSecurityValidator()
|
|
393
|
+
return validator.fix_security_issues(config)
|