Root cause
Injection & unsafe execution 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
openssl_encrypt versions before 1.4.0 contain a sandbox bypass vulnerability where the plugin sandbox fails to restrict alternative file access methods like pathlib.Path and io.open. Attackers can import pathlib or io modules to read and write arbitrary files, completely bypassing the restricted_open file access controls.
A plugin executing without PluginCapability.READ_FILES was supposed to be confined to the sandbox temporary directory and explicitly safe paths. The sandbox enforced that policy only by replacing builtins.open with restricted_open. Plugin code could instead import pathlib or io and call pathlib.Path.read_text/read_bytes/write_text/write_bytes/open or io.open; those independent entry points never reached restricted_open, so the plugin could read or modify arbitrary host files, for example Path('/etc/passwd').read_text(), despite lacking the file capability. The repository advisory specifically describes the bypass in threading mode and notes that process isolation had a separate broader restriction failure.
Root cause
Implement comprehensive secure plugin system with zero-trust architecture
Fix
security: Block pathlib/io sandbox bypass for file operations (GHSA-mcjj-qw7m-j3cp)
AI-assisted fix: Claude Code · Tobi
Case facts
@@ -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+@@ -454,7 +454,7 @@ def _setup_restricted_environment(self, context: PluginSecurityContext): # Restrict file access based on capabilities if PluginCapability.READ_FILES not in context.capabilities: # Override file operations to restrict access- saved_state["file_ops"] = self._restrict_file_operations()+ self._restrict_file_operations(saved_state) # Restrict network access if PluginCapability.NETWORK_ACCESS not in context.capabilities:@@ -476,35 +476,69 @@ def _setup_restricted_environment(self, context: PluginSecurityContext): return saved_state - def _restrict_file_operations(self):+ def _restrict_file_operations(self, saved_state): """Restrict file system operations. - Returns:- Original open function for restoration+ Monkey-patches builtins.open, pathlib.Path file methods, and io.open+ to enforce sandbox path restrictions.++ Args:+ saved_state: Dict to store original functions for restoration """ import builtins+ import io+ import pathlib - # Store original function+ # Store original functions for restoration original_open = builtins.open+ saved_state["builtins_open"] = original_open+ saved_state["io_open"] = io.open+ saved_state["pathlib_open"] = pathlib.Path.open+ saved_state["pathlib_read_text"] = pathlib.Path.read_text+ saved_state["pathlib_write_text"] = pathlib.Path.write_text+ saved_state["pathlib_read_bytes"] = pathlib.Path.read_bytes+ saved_state["pathlib_write_bytes"] = pathlib.Path.write_bytes+ saved_state["pathlib_unlink"] = pathlib.Path.unlink+ saved_state["pathlib_mkdir"] = pathlib.Path.mkdir+ saved_state["pathlib_rmdir"] = pathlib.Path.rmdir # Capture context for use in restricted_open context = self.current_context+ sandbox = self def restricted_open(file, mode="r", **kwargs):- # Only allow access to temp directory and explicitly safe paths- abs_path = os.path.abspath(file)-- # Check if this is a write operation+ abs_path = os.path.abspath(str(file)) is_write = any(c in mode for c in ["w", "a", "+", "x"])-- if not self._is_safe_path(abs_path, context, is_write):+ if not sandbox._is_safe_path(abs_path, context, is_write): raise SandboxViolationError(f"File access denied: {file} (mode: {mode})")- return original_open(file, mode, **kwargs) - # Replace open function+ def _make_pathlib_blocker(method_name, is_write=False):+ original = getattr(pathlib.Path, method_name)++ def blocked(self_path, *args, **kwargs):+ abs_path = os.path.abspath(str(self_path))+ if not sandbox._is_safe_path(abs_path, context, is_write):+ raise SandboxViolationError(+ f"File access denied via pathlib: {self_path}"+ )+ return original(self_path, *args, **kwargs)++ return blocked++ # Replace open functions builtins.open = restricted_open- return original_open+ io.open = restricted_open++ # Monkey-patch pathlib.Path methods+ pathlib.Path.open = _make_pathlib_blocker("open")+ pathlib.Path.read_text = _make_pathlib_blocker("read_text")+ pathlib.Path.read_bytes = _make_pathlib_blocker("read_bytes")+ pathlib.Path.write_text = _make_pathlib_blocker("write_text", is_write=True)+ pathlib.Path.write_bytes = _make_pathlib_blocker("write_bytes", is_write=True)+ pathlib.Path.unlink = _make_pathlib_blocker("unlink", is_write=True)+ pathlib.Path.mkdir = _make_pathlib_blocker("mkdir", is_write=True)+ pathlib.Path.rmdir = _make_pathlib_blocker("rmdir", is_write=True) AI-assisted change 967ccbb8899e187f4e507af6a320fd428761763e7e8d11184a7110ff5f0f0551 · Fix 385eade2c5a2b90f66a59d2e293c2eff411cc50f361c2fd004dbb802650a64de