truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
  162. truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.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
+ ]