Root cause
Validation & fail-open logic introduced
Implement comprehensive secure plugin system with zero-trust architecture
How AI contributed: Direct introductionPublished Feb 10, 2026 in
jahlives/openssl_encryptAll 20 findings in this repository
PluginImportGuard is a sys.meta_path hook plus a sys.modules hider that is supposed to block dangerous imports at plugin runtime. Its denylist is CWE-184-incomplete: ALWAYS_BLOCKED is only {ctypes, __builtin__, __builtins__} and CAPABILITY_GATED adds subprocess (always), os (always), and socket (NETWORK_ACCESS). _should_block_module() returns False for every other top-level name, so find_module()/find_spec() do not raise and hide_dangerous_modules() never removes those names from sys.modules. After 50bec5d the AST analyzer's DANGEROUS_MODULES independently treats sys, shutil, multiprocessing, importlib, pickle, shelve, pty, fcntl, pwd, grp, signal, resource, pipes, popen2, and platform as never-importable; an attacker who bypasses that static pass (string obfuscation, encoding, or __import__ indirection) can therefore import those modules at runtime and reach arbitrary code execution (pickle.__reduce__, importlib, sys.modules, shutil, multiprocessing). Direct import pickle/import sys is the sink; AST is only the bypassable outer check.
Root cause
Implement comprehensive secure plugin system with zero-trust architecture
Fix
security: Sync import guard and AST analyzer blocked module lists (GHSA-9pgj-v69p-q586)
Case facts
@@ -0,0 +1,496 @@+#!/usr/bin/env python3+"""+Plugin Manager for OpenSSL Encrypt++This module manages the lifecycle of plugins including discovery, loading,+validation, and execution. It enforces security boundaries and ensures+plugins never access sensitive data.++Security Features:+- Capability-based security model+- Plugin validation and sandboxing+- Resource usage monitoring and limits+- Audit logging for all plugin operations+"""++import importlib+import importlib.util+import logging+import os+import sys+import threading+import time+from pathlib import Path+from typing import Any, Dict, List, Optional, Set, Type++from .plugin_base import (+ BasePlugin,+ PluginCapability,+ PluginResult,+ PluginSecurityContext,+ PluginType,+)+from .plugin_config import PluginConfigManager+from .plugin_sandbox import PluginSandbox++logger = logging.getLogger(__name__)+++class PluginRegistration:+ """+ Registration record for a plugin including metadata and security info.+ """++ def __init__(self, plugin: BasePlugin, file_path: str, enabled: bool = True):+ self.plugin = plugin+ self.file_path = file_path+ self.enabled = enabled+ self.load_time = time.time()+ self.last_used = None+ self.usage_count = 0+ self.error_count = 0+ self.capabilities = plugin.get_required_capabilities()++ def record_usage(self, success: bool = True):+ """Record plugin usage statistics."""+ self.usage_count += 1+ self.last_used = time.time()+ if not success:+ self.error_count += 1+++class PluginManager:+ """+ Manages plugin discovery, loading, validation, and execution.++ Security Architecture:+ - All plugins run in isolated sandboxes+ - Capabilities are validated before plugin execution+ - Resource usage is monitored and limited+ - Audit trail is maintained for all operations+ """++ def __init__(self, config_manager: Optional["PluginConfigManager"] = None):+ self.plugins: Dict[str, PluginRegistration] = {}+ self.plugin_directories: Set[str] = set()+ self.sandbox = PluginSandbox()+ self.config_manager = config_manager or PluginConfigManager()+ self.lock = threading.RLock()++ # Security settings+ self.max_execution_time = 30.0 # seconds+ self.max_memory_mb = 100+ self.allowed_capabilities = set(PluginCapability)+ self.audit_log = []++ def add_plugin_directory(self, directory: str) -> None:+ """Add directory to scan for plugins."""+ if os.path.isdir(directory):+ self.plugin_directories.add(os.path.abspath(directory))@@ -0,0 +1,441 @@+#!/usr/bin/env python3+"""+Plugin Sandbox for OpenSSL Encrypt++This module provides security sandboxing for plugin execution, ensuring+plugins cannot access sensitive data, perform unauthorized operations,+or consume excessive resources.++Security Features:+- Resource limits (memory, execution time)+- Capability enforcement+- File system access restrictions+- Network access controls+- Process isolation where possible+"""++import gc+import logging+import multiprocessing+import os+import resource+import signal+import sys+import tempfile+import threading+import time+from contextlib import contextmanager+from typing import Any, Dict, Optional++from .plugin_base import BasePlugin, PluginCapability, PluginResult, PluginSecurityContext++logger = logging.getLogger(__name__)+++class ResourceMonitor:+ """Monitor plugin resource usage during execution."""++ def __init__(self):+ self.start_memory = 0+ self.peak_memory = 0+ self.start_time = 0+ self.execution_time = 0+ self.monitoring = False++ def start(self):+ """Start resource monitoring."""+ self.start_memory = self._get_memory_usage()+ self.peak_memory = self.start_memory+ self.start_time = time.time()+ self.monitoring = True++ def stop(self):+ """Stop resource monitoring."""+ if self.monitoring:+ self.execution_time = time.time() - self.start_time+ self.monitoring = False++ def update_peak_memory(self):+ """Update peak memory usage."""+ if self.monitoring:+ current_memory = self._get_memory_usage()+ if current_memory > self.peak_memory:+ self.peak_memory = current_memory++ def get_stats(self) -> Dict[str, float]:+ """Get resource usage statistics."""+ return {+ "memory_start_mb": self.start_memory / (1024 * 1024),+ "memory_peak_mb": self.peak_memory / (1024 * 1024),+ "memory_used_mb": (self.peak_memory - self.start_memory) / (1024 * 1024),+ "execution_time_s": self.execution_time,+ }++ @staticmethod+ def _get_memory_usage() -> int:+ """Get current memory usage in bytes."""+ try:+ # Try to get RSS (Resident Set Size) on Unix systems+ if hasattr(resource, "RUSAGE_SELF"):+ usage = resource.getrusage(resource.RUSAGE_SELF)+ # On Linux, ru_maxrss is in KB, on BSD systems it's in bytes+ if sys.platform == "linux":+ return usage.ru_maxrss * 1024+ else:+ return usage.ru_maxrss+ else:+ # Fallback for systems without resource module+ import psutil+@@ -31,6 +31,9 @@ logger = logging.getLogger(__name__) +# Import the shared blocked modules constant (single source of truth)+from .plugin_security_constants import BLOCKED_MODULES+ def _validate_plugin_source(plugin: BasePlugin) -> None: """Run AST security analysis on a plugin's source code before execution.@@ -78,17 +81,11 @@ class PluginImportGuard: access to already-imported modules. """ - # Modules that should always be blocked- ALWAYS_BLOCKED = {- "ctypes", # Load arbitrary C code- "__builtin__", # Access to builtins- "__builtins__", # Access to builtins- }+ # All modules from the shared BLOCKED_MODULES set are always blocked+ ALWAYS_BLOCKED = BLOCKED_MODULES # Modules that are blocked unless specific capability is granted CAPABILITY_GATED = {- "subprocess": None, # Always blocked (no capability for this)- "os": None, # Always blocked (contains os.system, etc.) "socket": PluginCapability.NETWORK_ACCESS, # Allowed with NETWORK_ACCESS } @@ -0,0 +1,36 @@+#!/usr/bin/env python3+"""+Shared security constants for the plugin sandbox system.++Both the AST analyzer (static analysis) and the import guard (runtime)+reference this single source of truth to ensure blocked module lists+stay in sync.+"""++# Modules that must be blocked in both the AST analyzer and the runtime+# import guard to prevent sandbox escape.+BLOCKED_MODULES = frozenset({+ "subprocess",+ "ctypes",+ "multiprocessing",+ "importlib",+ "__builtin__",+ "__builtins__",+ "sys",+ "shutil",+ "pickle",+ "shelve",+ "commands",+ "pty",+ "fcntl",+ "pwd",+ "grp",+ "signal",+ "resource",+ "pipes",+ "popen2",+ "platform",+ "os", # os.system, os.popen, etc. — always blocked+ "pathlib", # Path.read_text() etc. bypass sandbox file restrictions+ "io", # io.open() bypasses sandbox restricted_open+})@@ -73,32 +73,10 @@ class DangerousPatternVisitor(ast.NodeVisitor): "__reduce_ex__", } - # Modules that should never be imported- # Note: 'os' and 'socket' are NOT blocked here:- # - 'os' is needed for path operations; file access is restricted by sandbox allowed_paths- # - 'socket' is needed for network plugins; dangerous OS functions are checked separately- DANGEROUS_MODULES = {- "subprocess",- "ctypes",- "multiprocessing",- "importlib",- "__builtin__",- "__builtins__",- "sys",- "shutil",- "pickle",- "shelve",- "commands",- "pty",- "fcntl",- "pwd",- "grp",- "signal",- "resource",- "pipes",- "popen2",- "platform",- }+ # Use the shared blocked modules set to keep AST and runtime in sync+ from .plugin_security_constants import BLOCKED_MODULES++ DANGEROUS_MODULES = BLOCKED_MODULES # os module functions that are dangerous DANGEROUS_OS_FUNCTIONS = {AI-assisted change 0037cef788302ec9b35a25a79fe08690e653e4e6ec2be3ca21c04eb7440b8b74 · Fix 28eb2900faa363f780f87072a8d48abe19ff3fb2aca4c6ebce6fb3bdcca9e610