truthound-dashboard 1.3.0__py3-none-any.whl → 1.4.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.
- truthound_dashboard/api/alerts.py +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -18
- truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BCA8H1hO.js +0 -574
- truthound_dashboard/static/assets/index-BNsSQ2fN.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CsJWCRx9.js +0 -1
- truthound_dashboard-1.3.0.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""Plugin Sandbox for secure code execution.
|
|
2
|
+
|
|
3
|
+
This module provides a sandboxed execution environment for
|
|
4
|
+
plugin code with resource limits and security restrictions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import io
|
|
11
|
+
import logging
|
|
12
|
+
import sys
|
|
13
|
+
import traceback
|
|
14
|
+
from contextlib import contextmanager
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class SandboxConfig:
|
|
23
|
+
"""Configuration for the plugin sandbox.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
enabled: Whether sandbox is enabled.
|
|
27
|
+
memory_limit_mb: Memory limit in MB.
|
|
28
|
+
cpu_time_limit_seconds: CPU time limit in seconds.
|
|
29
|
+
network_enabled: Whether network access is allowed.
|
|
30
|
+
allowed_modules: List of allowed Python modules.
|
|
31
|
+
blocked_modules: List of blocked Python modules.
|
|
32
|
+
max_output_size: Maximum output size in bytes.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
enabled: bool = True
|
|
36
|
+
memory_limit_mb: int = 256
|
|
37
|
+
cpu_time_limit_seconds: int = 30
|
|
38
|
+
network_enabled: bool = False
|
|
39
|
+
allowed_modules: list[str] = field(default_factory=list)
|
|
40
|
+
blocked_modules: list[str] = field(
|
|
41
|
+
default_factory=lambda: [
|
|
42
|
+
"os",
|
|
43
|
+
"subprocess",
|
|
44
|
+
"sys",
|
|
45
|
+
"shutil",
|
|
46
|
+
"socket",
|
|
47
|
+
"http",
|
|
48
|
+
"urllib",
|
|
49
|
+
"requests",
|
|
50
|
+
"httpx",
|
|
51
|
+
"multiprocessing",
|
|
52
|
+
"threading",
|
|
53
|
+
"ctypes",
|
|
54
|
+
"pickle",
|
|
55
|
+
"shelve",
|
|
56
|
+
"sqlite3",
|
|
57
|
+
"importlib",
|
|
58
|
+
"__builtin__",
|
|
59
|
+
"builtins",
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
max_output_size: int = 1024 * 1024 # 1MB
|
|
63
|
+
allowed_builtins: list[str] = field(
|
|
64
|
+
default_factory=lambda: [
|
|
65
|
+
"abs",
|
|
66
|
+
"all",
|
|
67
|
+
"any",
|
|
68
|
+
"ascii",
|
|
69
|
+
"bin",
|
|
70
|
+
"bool",
|
|
71
|
+
"bytearray",
|
|
72
|
+
"bytes",
|
|
73
|
+
"callable",
|
|
74
|
+
"chr",
|
|
75
|
+
"classmethod",
|
|
76
|
+
"complex",
|
|
77
|
+
"dict",
|
|
78
|
+
"dir",
|
|
79
|
+
"divmod",
|
|
80
|
+
"enumerate",
|
|
81
|
+
"filter",
|
|
82
|
+
"float",
|
|
83
|
+
"format",
|
|
84
|
+
"frozenset",
|
|
85
|
+
"getattr",
|
|
86
|
+
"hasattr",
|
|
87
|
+
"hash",
|
|
88
|
+
"hex",
|
|
89
|
+
"id",
|
|
90
|
+
"int",
|
|
91
|
+
"isinstance",
|
|
92
|
+
"issubclass",
|
|
93
|
+
"iter",
|
|
94
|
+
"len",
|
|
95
|
+
"list",
|
|
96
|
+
"map",
|
|
97
|
+
"max",
|
|
98
|
+
"min",
|
|
99
|
+
"next",
|
|
100
|
+
"object",
|
|
101
|
+
"oct",
|
|
102
|
+
"ord",
|
|
103
|
+
"pow",
|
|
104
|
+
"print",
|
|
105
|
+
"property",
|
|
106
|
+
"range",
|
|
107
|
+
"repr",
|
|
108
|
+
"reversed",
|
|
109
|
+
"round",
|
|
110
|
+
"set",
|
|
111
|
+
"setattr",
|
|
112
|
+
"slice",
|
|
113
|
+
"sorted",
|
|
114
|
+
"staticmethod",
|
|
115
|
+
"str",
|
|
116
|
+
"sum",
|
|
117
|
+
"super",
|
|
118
|
+
"tuple",
|
|
119
|
+
"type",
|
|
120
|
+
"vars",
|
|
121
|
+
"zip",
|
|
122
|
+
]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SandboxSecurityError(Exception):
|
|
127
|
+
"""Raised when sandbox security is violated."""
|
|
128
|
+
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class SandboxTimeoutError(Exception):
|
|
133
|
+
"""Raised when sandbox execution times out."""
|
|
134
|
+
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SandboxMemoryError(Exception):
|
|
139
|
+
"""Raised when sandbox memory limit is exceeded."""
|
|
140
|
+
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class RestrictedImporter:
|
|
145
|
+
"""Custom importer that restricts module imports.
|
|
146
|
+
|
|
147
|
+
This importer blocks dangerous modules and only allows
|
|
148
|
+
a whitelist of safe modules.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self, blocked_modules: list[str], allowed_modules: list[str]) -> None:
|
|
152
|
+
"""Initialize the restricted importer.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
blocked_modules: List of blocked module names.
|
|
156
|
+
allowed_modules: List of allowed module names.
|
|
157
|
+
"""
|
|
158
|
+
self.blocked_modules = set(blocked_modules)
|
|
159
|
+
self.allowed_modules = set(allowed_modules)
|
|
160
|
+
# Always allow these safe modules for data processing
|
|
161
|
+
self.safe_modules = {
|
|
162
|
+
"math",
|
|
163
|
+
"statistics",
|
|
164
|
+
"decimal",
|
|
165
|
+
"fractions",
|
|
166
|
+
"random",
|
|
167
|
+
"re",
|
|
168
|
+
"json",
|
|
169
|
+
"datetime",
|
|
170
|
+
"collections",
|
|
171
|
+
"itertools",
|
|
172
|
+
"functools",
|
|
173
|
+
"operator",
|
|
174
|
+
"string",
|
|
175
|
+
"textwrap",
|
|
176
|
+
"unicodedata",
|
|
177
|
+
"typing",
|
|
178
|
+
"dataclasses",
|
|
179
|
+
"enum",
|
|
180
|
+
"copy",
|
|
181
|
+
"numbers",
|
|
182
|
+
"hashlib",
|
|
183
|
+
"hmac",
|
|
184
|
+
"base64",
|
|
185
|
+
"binascii",
|
|
186
|
+
"io",
|
|
187
|
+
"csv",
|
|
188
|
+
}
|
|
189
|
+
self.allowed_modules.update(self.safe_modules)
|
|
190
|
+
|
|
191
|
+
def find_module(self, name: str, path: list[str] | None = None) -> "RestrictedImporter | None":
|
|
192
|
+
"""Check if module import should be blocked.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
name: Module name.
|
|
196
|
+
path: Module path.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Self if import should be checked, None otherwise.
|
|
200
|
+
"""
|
|
201
|
+
base_module = name.split(".")[0]
|
|
202
|
+
|
|
203
|
+
# Check if blocked
|
|
204
|
+
if base_module in self.blocked_modules:
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
# Check if allowed
|
|
208
|
+
if self.allowed_modules and base_module not in self.allowed_modules:
|
|
209
|
+
return self
|
|
210
|
+
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def load_module(self, name: str) -> Any:
|
|
214
|
+
"""Block the module load.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
name: Module name.
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
SandboxSecurityError: Always, since this is only called for blocked imports.
|
|
221
|
+
"""
|
|
222
|
+
raise SandboxSecurityError(f"Import of module '{name}' is not allowed in sandbox")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class CodeAnalyzer(ast.NodeVisitor):
|
|
226
|
+
"""AST visitor to analyze code for security issues.
|
|
227
|
+
|
|
228
|
+
This analyzer checks for dangerous constructs like:
|
|
229
|
+
- Blocked function calls (eval, exec, compile, etc.)
|
|
230
|
+
- Blocked attribute access
|
|
231
|
+
- Potentially dangerous patterns
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
BLOCKED_FUNCTIONS = {
|
|
235
|
+
"eval",
|
|
236
|
+
"exec",
|
|
237
|
+
"compile",
|
|
238
|
+
"open",
|
|
239
|
+
"input",
|
|
240
|
+
"__import__",
|
|
241
|
+
"globals",
|
|
242
|
+
"locals",
|
|
243
|
+
"vars",
|
|
244
|
+
"dir",
|
|
245
|
+
"getattr",
|
|
246
|
+
"setattr",
|
|
247
|
+
"delattr",
|
|
248
|
+
"breakpoint",
|
|
249
|
+
"exit",
|
|
250
|
+
"quit",
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
BLOCKED_ATTRIBUTES = {
|
|
254
|
+
"__class__",
|
|
255
|
+
"__bases__",
|
|
256
|
+
"__subclasses__",
|
|
257
|
+
"__mro__",
|
|
258
|
+
"__code__",
|
|
259
|
+
"__globals__",
|
|
260
|
+
"__builtins__",
|
|
261
|
+
"__dict__",
|
|
262
|
+
"__closure__",
|
|
263
|
+
"__func__",
|
|
264
|
+
"__self__",
|
|
265
|
+
"__module__",
|
|
266
|
+
"__qualname__",
|
|
267
|
+
"__annotations__",
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
def __init__(self) -> None:
|
|
271
|
+
"""Initialize the analyzer."""
|
|
272
|
+
self.issues: list[str] = []
|
|
273
|
+
self.warnings: list[str] = []
|
|
274
|
+
|
|
275
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
276
|
+
"""Check function calls.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
node: Call AST node.
|
|
280
|
+
"""
|
|
281
|
+
# Check for blocked function names
|
|
282
|
+
if isinstance(node.func, ast.Name):
|
|
283
|
+
if node.func.id in self.BLOCKED_FUNCTIONS:
|
|
284
|
+
self.issues.append(f"Blocked function call: {node.func.id}")
|
|
285
|
+
elif isinstance(node.func, ast.Attribute):
|
|
286
|
+
if node.func.attr in self.BLOCKED_FUNCTIONS:
|
|
287
|
+
self.issues.append(f"Blocked function call: {node.func.attr}")
|
|
288
|
+
|
|
289
|
+
self.generic_visit(node)
|
|
290
|
+
|
|
291
|
+
def visit_Attribute(self, node: ast.Attribute) -> None:
|
|
292
|
+
"""Check attribute access.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
node: Attribute AST node.
|
|
296
|
+
"""
|
|
297
|
+
if node.attr in self.BLOCKED_ATTRIBUTES:
|
|
298
|
+
self.issues.append(f"Blocked attribute access: {node.attr}")
|
|
299
|
+
|
|
300
|
+
self.generic_visit(node)
|
|
301
|
+
|
|
302
|
+
def visit_Import(self, node: ast.Import) -> None:
|
|
303
|
+
"""Check imports.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
node: Import AST node.
|
|
307
|
+
"""
|
|
308
|
+
for alias in node.names:
|
|
309
|
+
self.warnings.append(f"Import statement: {alias.name}")
|
|
310
|
+
self.generic_visit(node)
|
|
311
|
+
|
|
312
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
|
313
|
+
"""Check from imports.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
node: ImportFrom AST node.
|
|
317
|
+
"""
|
|
318
|
+
self.warnings.append(f"Import from: {node.module}")
|
|
319
|
+
self.generic_visit(node)
|
|
320
|
+
|
|
321
|
+
def analyze(self, code: str) -> tuple[list[str], list[str]]:
|
|
322
|
+
"""Analyze code for security issues.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
code: Python source code.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Tuple of (issues, warnings).
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
tree = ast.parse(code)
|
|
332
|
+
self.visit(tree)
|
|
333
|
+
except SyntaxError as e:
|
|
334
|
+
self.issues.append(f"Syntax error: {e}")
|
|
335
|
+
|
|
336
|
+
return self.issues, self.warnings
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@dataclass
|
|
340
|
+
class SandboxResult:
|
|
341
|
+
"""Result of sandbox execution.
|
|
342
|
+
|
|
343
|
+
Attributes:
|
|
344
|
+
success: Whether execution succeeded.
|
|
345
|
+
result: Return value if any.
|
|
346
|
+
error: Error message if failed.
|
|
347
|
+
stdout: Captured stdout.
|
|
348
|
+
stderr: Captured stderr.
|
|
349
|
+
execution_time_ms: Execution time in milliseconds.
|
|
350
|
+
memory_used_mb: Approximate memory used in MB.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
success: bool
|
|
354
|
+
result: Any = None
|
|
355
|
+
error: str | None = None
|
|
356
|
+
stdout: str = ""
|
|
357
|
+
stderr: str = ""
|
|
358
|
+
execution_time_ms: float = 0
|
|
359
|
+
memory_used_mb: float | None = None
|
|
360
|
+
warnings: list[str] = field(default_factory=list)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class PluginSandbox:
|
|
364
|
+
"""Sandboxed execution environment for plugin code.
|
|
365
|
+
|
|
366
|
+
This class provides a secure environment for executing
|
|
367
|
+
untrusted plugin code with various safety restrictions.
|
|
368
|
+
|
|
369
|
+
Attributes:
|
|
370
|
+
config: Sandbox configuration.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
def __init__(self, config: SandboxConfig | None = None) -> None:
|
|
374
|
+
"""Initialize the sandbox.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
config: Sandbox configuration.
|
|
378
|
+
"""
|
|
379
|
+
self.config = config or SandboxConfig()
|
|
380
|
+
|
|
381
|
+
def analyze_code(self, code: str) -> tuple[list[str], list[str]]:
|
|
382
|
+
"""Analyze code for security issues without executing.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
code: Python source code.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Tuple of (issues, warnings).
|
|
389
|
+
"""
|
|
390
|
+
analyzer = CodeAnalyzer()
|
|
391
|
+
return analyzer.analyze(code)
|
|
392
|
+
|
|
393
|
+
@contextmanager
|
|
394
|
+
def _capture_output(self):
|
|
395
|
+
"""Context manager to capture stdout and stderr."""
|
|
396
|
+
old_stdout = sys.stdout
|
|
397
|
+
old_stderr = sys.stderr
|
|
398
|
+
sys.stdout = io.StringIO()
|
|
399
|
+
sys.stderr = io.StringIO()
|
|
400
|
+
try:
|
|
401
|
+
yield sys.stdout, sys.stderr
|
|
402
|
+
finally:
|
|
403
|
+
sys.stdout = old_stdout
|
|
404
|
+
sys.stderr = old_stderr
|
|
405
|
+
|
|
406
|
+
@contextmanager
|
|
407
|
+
def _restricted_imports(self):
|
|
408
|
+
"""Context manager to restrict imports."""
|
|
409
|
+
importer = RestrictedImporter(
|
|
410
|
+
blocked_modules=self.config.blocked_modules,
|
|
411
|
+
allowed_modules=self.config.allowed_modules,
|
|
412
|
+
)
|
|
413
|
+
sys.meta_path.insert(0, importer)
|
|
414
|
+
try:
|
|
415
|
+
yield
|
|
416
|
+
finally:
|
|
417
|
+
sys.meta_path.remove(importer)
|
|
418
|
+
|
|
419
|
+
def _create_safe_builtins(self) -> dict[str, Any]:
|
|
420
|
+
"""Create a restricted builtins dictionary.
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Dictionary of allowed builtins.
|
|
424
|
+
"""
|
|
425
|
+
import builtins
|
|
426
|
+
|
|
427
|
+
safe_builtins = {}
|
|
428
|
+
for name in self.config.allowed_builtins:
|
|
429
|
+
if hasattr(builtins, name):
|
|
430
|
+
safe_builtins[name] = getattr(builtins, name)
|
|
431
|
+
|
|
432
|
+
# Add None, True, False
|
|
433
|
+
safe_builtins["None"] = None
|
|
434
|
+
safe_builtins["True"] = True
|
|
435
|
+
safe_builtins["False"] = False
|
|
436
|
+
|
|
437
|
+
# Add safe exceptions
|
|
438
|
+
safe_builtins["Exception"] = Exception
|
|
439
|
+
safe_builtins["ValueError"] = ValueError
|
|
440
|
+
safe_builtins["TypeError"] = TypeError
|
|
441
|
+
safe_builtins["KeyError"] = KeyError
|
|
442
|
+
safe_builtins["IndexError"] = IndexError
|
|
443
|
+
safe_builtins["AttributeError"] = AttributeError
|
|
444
|
+
safe_builtins["RuntimeError"] = RuntimeError
|
|
445
|
+
safe_builtins["StopIteration"] = StopIteration
|
|
446
|
+
|
|
447
|
+
return safe_builtins
|
|
448
|
+
|
|
449
|
+
def execute(
|
|
450
|
+
self,
|
|
451
|
+
code: str,
|
|
452
|
+
globals_dict: dict[str, Any] | None = None,
|
|
453
|
+
locals_dict: dict[str, Any] | None = None,
|
|
454
|
+
entry_point: str | None = None,
|
|
455
|
+
entry_args: dict[str, Any] | None = None,
|
|
456
|
+
) -> SandboxResult:
|
|
457
|
+
"""Execute code in the sandbox.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
code: Python source code to execute.
|
|
461
|
+
globals_dict: Global variables to provide.
|
|
462
|
+
locals_dict: Local variables to provide.
|
|
463
|
+
entry_point: Function name to call after execution.
|
|
464
|
+
entry_args: Arguments to pass to entry point.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
SandboxResult with execution results.
|
|
468
|
+
"""
|
|
469
|
+
import time
|
|
470
|
+
|
|
471
|
+
if not self.config.enabled:
|
|
472
|
+
# Execute without sandbox
|
|
473
|
+
return self._execute_unsafe(code, globals_dict, locals_dict, entry_point, entry_args)
|
|
474
|
+
|
|
475
|
+
# Analyze code first
|
|
476
|
+
issues, warnings = self.analyze_code(code)
|
|
477
|
+
if issues:
|
|
478
|
+
return SandboxResult(
|
|
479
|
+
success=False,
|
|
480
|
+
error=f"Code analysis failed: {'; '.join(issues)}",
|
|
481
|
+
warnings=warnings,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
start_time = time.perf_counter()
|
|
485
|
+
|
|
486
|
+
# Create execution environment
|
|
487
|
+
safe_globals = globals_dict.copy() if globals_dict else {}
|
|
488
|
+
safe_globals["__builtins__"] = self._create_safe_builtins()
|
|
489
|
+
|
|
490
|
+
safe_locals = locals_dict.copy() if locals_dict else {}
|
|
491
|
+
|
|
492
|
+
result = None
|
|
493
|
+
error = None
|
|
494
|
+
stdout_str = ""
|
|
495
|
+
stderr_str = ""
|
|
496
|
+
|
|
497
|
+
try:
|
|
498
|
+
with self._capture_output() as (stdout, stderr):
|
|
499
|
+
with self._restricted_imports():
|
|
500
|
+
# Compile and execute
|
|
501
|
+
compiled = compile(code, "<sandbox>", "exec")
|
|
502
|
+
exec(compiled, safe_globals, safe_locals)
|
|
503
|
+
|
|
504
|
+
# Call entry point if specified
|
|
505
|
+
if entry_point and entry_point in safe_locals:
|
|
506
|
+
func = safe_locals[entry_point]
|
|
507
|
+
if callable(func):
|
|
508
|
+
result = func(**(entry_args or {}))
|
|
509
|
+
else:
|
|
510
|
+
error = f"Entry point '{entry_point}' is not callable"
|
|
511
|
+
elif entry_point:
|
|
512
|
+
error = f"Entry point '{entry_point}' not found"
|
|
513
|
+
|
|
514
|
+
stdout_str = stdout.getvalue()
|
|
515
|
+
stderr_str = stderr.getvalue()
|
|
516
|
+
|
|
517
|
+
# Truncate output if too large
|
|
518
|
+
max_size = self.config.max_output_size
|
|
519
|
+
if len(stdout_str) > max_size:
|
|
520
|
+
stdout_str = stdout_str[:max_size] + "\n... (truncated)"
|
|
521
|
+
if len(stderr_str) > max_size:
|
|
522
|
+
stderr_str = stderr_str[:max_size] + "\n... (truncated)"
|
|
523
|
+
|
|
524
|
+
except SandboxSecurityError as e:
|
|
525
|
+
error = f"Security violation: {e}"
|
|
526
|
+
except SandboxTimeoutError as e:
|
|
527
|
+
error = f"Timeout: {e}"
|
|
528
|
+
except SandboxMemoryError as e:
|
|
529
|
+
error = f"Memory limit exceeded: {e}"
|
|
530
|
+
except Exception as e:
|
|
531
|
+
error = f"Execution error: {type(e).__name__}: {e}\n{traceback.format_exc()}"
|
|
532
|
+
|
|
533
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
534
|
+
|
|
535
|
+
return SandboxResult(
|
|
536
|
+
success=error is None,
|
|
537
|
+
result=result,
|
|
538
|
+
error=error,
|
|
539
|
+
stdout=stdout_str,
|
|
540
|
+
stderr=stderr_str,
|
|
541
|
+
execution_time_ms=execution_time,
|
|
542
|
+
warnings=warnings,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def _execute_unsafe(
|
|
546
|
+
self,
|
|
547
|
+
code: str,
|
|
548
|
+
globals_dict: dict[str, Any] | None = None,
|
|
549
|
+
locals_dict: dict[str, Any] | None = None,
|
|
550
|
+
entry_point: str | None = None,
|
|
551
|
+
entry_args: dict[str, Any] | None = None,
|
|
552
|
+
) -> SandboxResult:
|
|
553
|
+
"""Execute code without sandbox restrictions.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
code: Python source code.
|
|
557
|
+
globals_dict: Global variables.
|
|
558
|
+
locals_dict: Local variables.
|
|
559
|
+
entry_point: Function to call.
|
|
560
|
+
entry_args: Arguments for entry point.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
SandboxResult.
|
|
564
|
+
"""
|
|
565
|
+
import time
|
|
566
|
+
|
|
567
|
+
start_time = time.perf_counter()
|
|
568
|
+
|
|
569
|
+
exec_globals = globals_dict.copy() if globals_dict else {}
|
|
570
|
+
exec_locals = locals_dict.copy() if locals_dict else {}
|
|
571
|
+
|
|
572
|
+
result = None
|
|
573
|
+
error = None
|
|
574
|
+
stdout_str = ""
|
|
575
|
+
stderr_str = ""
|
|
576
|
+
|
|
577
|
+
try:
|
|
578
|
+
with self._capture_output() as (stdout, stderr):
|
|
579
|
+
exec(code, exec_globals, exec_locals)
|
|
580
|
+
|
|
581
|
+
if entry_point and entry_point in exec_locals:
|
|
582
|
+
func = exec_locals[entry_point]
|
|
583
|
+
if callable(func):
|
|
584
|
+
result = func(**(entry_args or {}))
|
|
585
|
+
else:
|
|
586
|
+
error = f"Entry point '{entry_point}' is not callable"
|
|
587
|
+
elif entry_point:
|
|
588
|
+
error = f"Entry point '{entry_point}' not found"
|
|
589
|
+
|
|
590
|
+
stdout_str = stdout.getvalue()
|
|
591
|
+
stderr_str = stderr.getvalue()
|
|
592
|
+
|
|
593
|
+
except Exception as e:
|
|
594
|
+
error = f"Execution error: {type(e).__name__}: {e}\n{traceback.format_exc()}"
|
|
595
|
+
|
|
596
|
+
execution_time = (time.perf_counter() - start_time) * 1000
|
|
597
|
+
|
|
598
|
+
return SandboxResult(
|
|
599
|
+
success=error is None,
|
|
600
|
+
result=result,
|
|
601
|
+
error=error,
|
|
602
|
+
stdout=stdout_str,
|
|
603
|
+
stderr=stderr_str,
|
|
604
|
+
execution_time_ms=execution_time,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
def validate_code(self, code: str) -> tuple[bool, list[str]]:
|
|
608
|
+
"""Validate code without executing.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
code: Python source code.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
Tuple of (is_valid, list of issues).
|
|
615
|
+
"""
|
|
616
|
+
issues, _ = self.analyze_code(code)
|
|
617
|
+
return len(issues) == 0, issues
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Enterprise Plugin Security Module.
|
|
2
|
+
|
|
3
|
+
This module provides enterprise-grade security features:
|
|
4
|
+
- Multiple signature algorithms (HMAC, RSA, Ed25519)
|
|
5
|
+
- Trust store with certificate management
|
|
6
|
+
- Verification chain (Chain of Responsibility pattern)
|
|
7
|
+
- Security policy presets
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .protocols import (
|
|
13
|
+
IsolationLevel,
|
|
14
|
+
TrustLevel,
|
|
15
|
+
SecurityPolicy,
|
|
16
|
+
ResourceLimits,
|
|
17
|
+
SignatureAlgorithm,
|
|
18
|
+
SignatureInfo,
|
|
19
|
+
VerificationResult,
|
|
20
|
+
)
|
|
21
|
+
from .policies import (
|
|
22
|
+
SecurityPolicyPresets,
|
|
23
|
+
create_policy,
|
|
24
|
+
get_preset,
|
|
25
|
+
list_presets,
|
|
26
|
+
)
|
|
27
|
+
from .signing import (
|
|
28
|
+
SigningService,
|
|
29
|
+
SigningServiceImpl,
|
|
30
|
+
TrustStore,
|
|
31
|
+
TrustStoreImpl,
|
|
32
|
+
VerificationChain,
|
|
33
|
+
VerificationChainBuilder,
|
|
34
|
+
create_verification_chain,
|
|
35
|
+
)
|
|
36
|
+
from .analyzer import (
|
|
37
|
+
SecurityAnalyzer,
|
|
38
|
+
SecurityReport,
|
|
39
|
+
CodeAnalysisResult,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Protocols
|
|
44
|
+
"IsolationLevel",
|
|
45
|
+
"TrustLevel",
|
|
46
|
+
"SecurityPolicy",
|
|
47
|
+
"ResourceLimits",
|
|
48
|
+
"SignatureAlgorithm",
|
|
49
|
+
"SignatureInfo",
|
|
50
|
+
"VerificationResult",
|
|
51
|
+
# Policies
|
|
52
|
+
"SecurityPolicyPresets",
|
|
53
|
+
"create_policy",
|
|
54
|
+
"get_preset",
|
|
55
|
+
"list_presets",
|
|
56
|
+
# Signing
|
|
57
|
+
"SigningService",
|
|
58
|
+
"SigningServiceImpl",
|
|
59
|
+
"TrustStore",
|
|
60
|
+
"TrustStoreImpl",
|
|
61
|
+
"VerificationChain",
|
|
62
|
+
"VerificationChainBuilder",
|
|
63
|
+
"create_verification_chain",
|
|
64
|
+
# Analysis
|
|
65
|
+
"SecurityAnalyzer",
|
|
66
|
+
"SecurityReport",
|
|
67
|
+
"CodeAnalysisResult",
|
|
68
|
+
]
|