- Created tests/pod_executor/ with adapted tests from old locations - tests/pod_executor/simple/test_executor.py: 17/17 tests passing - tests/pod_executor/security/test_resource_limits.py: 22/23 tests passing - Removed old test locations (will be deleted with mcp_forge cleanup) - Fixed all corrupted files from sed/quote issues using Python scripts - Removed mcp_forge dependencies from pod_executor: - Removed ForgeConfig from backend.py (explicit parameters) - Removed SessionConfig from sessions.py (explicit parameters) - Fixed all audit logger calls to use string-based events - Updated mcp_forge/security/__init__.py: - Removed resource_limits imports (now in pod_executor) - Added comment directing to pod_executor.security.resource_limits - Deleted from mcp_forge: - src/mcp_forge/execution/ (simple and jupyter backends) - src/mcp_forge/podman/ (container management) - src/mcp_forge/security/resource_limits.py Total: 39/40 tests passing in pod_executor package |
||
|---|---|---|
| .. | ||
| containers | ||
| jupyter | ||
| security | ||
| simple | ||
| __init__.py | ||
| README.md | ||
pod_executor
Standalone Python code execution in Podman containers with security isolation.
Overview
pod_executor provides both stateless and stateful (Jupyter) code execution backends that run Python code in isolated Podman containers. It's designed to be usable standalone or as part of larger systems like MCP-Forge.
Features
- ✅ Stateless execution: Each code snippet runs in a fresh container
- ✅ Stateful execution: Jupyter kernels maintain namespace across executions
- ✅ Security isolation: Containers with configurable resource limits
- ✅ Protocol-based design: Pluggable audit logging and validation
- ✅ No external configuration: All parameters explicit
- ✅ Rootless Podman support: Works with user-level Podman
Installation
# Requires Python 3.11+ and Podman
pip install podman jupyter-client pyzmq
Quick Start
Simple Stateless Execution
from pathlib import Path
from pod_executor import (
CodeExecutor,
ResourceLimits,
SecureContainerManager,
PodmanClient,
NoOpValidator,
NullAuditLogger
)
# Setup Podman client (user socket)
client = PodmanClient(
socket_path=Path("/run/user/1000/podman/podman.sock"),
validator=NoOpValidator(),
audit_logger=NullAuditLogger()
)
# Setup container manager
container_manager = SecureContainerManager(
podman_client=client,
validator=NoOpValidator(),
audit_logger=NullAuditLogger()
)
# Setup executor with resource limits
limits = ResourceLimits(
memory="512m",
cpu_quota=100000, # 100% of 1 CPU
storage="1g",
timeout=30
)
executor = CodeExecutor(
container_manager=container_manager,
image="python:3.12",
resource_limits=limits
)
# Execute code
result = executor.execute("print('Hello from container!')")
print(result.stdout) # "Hello from container!\n"
print(result.exit_code) # 0
print(result.execution_time) # e.g., 0.523
Stateful Jupyter Execution
from pod_executor import JupyterBackend
# Setup backend
backend = JupyterBackend(
container_manager=container_manager,
image="mcp-forge/jupyter:latest",
default_timeout=300,
default_memory="512m",
max_sessions=10,
idle_timeout=3600
)
# Execute in session - variables persist
result1 = backend.execute("x = 42", session_id="my-session")
result2 = backend.execute("print(x * 2)", session_id="my-session")
print(result2.stdout) # "84\n"
# List active sessions
sessions = backend.list_sessions()
# Cleanup
backend.destroy_session("my-session")
Architecture
pod_executor/
├── security/ # Security components
│ ├── resource_limits.py # Memory, CPU, storage limits
│ ├── audit.py # Audit logging protocols
│ └── validation.py # Security validation protocols
├── containers/ # Container management
│ ├── client.py # Podman client wrapper
│ └── manager.py # Container lifecycle
├── simple/ # Stateless execution
│ └── executor.py # CodeExecutor
└── jupyter/ # Stateful execution
├── backend.py # JupyterBackend
├── kernel.py # Kernel management
└── sessions.py # Session management
Security
Validators
Three validator implementations:
- NoOpValidator: No validation (testing only!)
- BasicValidator: Minimal checks (image allowlist, forbidden params)
- Custom: Implement
OperationValidatorProtocol
from pod_executor.security import BasicValidator
validator = BasicValidator(allowed_images=["python:3.12*", "jupyter/*"])
Audit Logging
Three audit logger implementations:
- NullAuditLogger: No logging
- SimpleFileAuditLogger: JSON Lines file logging
- Custom: Implement
AuditLoggerProtocol
from pod_executor.security import SimpleFileAuditLogger
logger = SimpleFileAuditLogger(Path("/var/log/executor/audit.log"))
Resource Limits
Control container resources:
from pod_executor import ResourceLimits
limits = ResourceLimits(
memory="2g", # Memory limit
cpu_quota=200000, # CPU quota (200% = 2 CPUs)
storage="5g", # Storage limit (tracked, not enforced)
timeout=600 # Max execution time in seconds
)
Container Images
Requires Python-capable container images:
- Simple executor: Any Python image (
python:3.12,python:3.11-slim, etc.) - Jupyter backend: Image with
ipykernelinstalled
Build Jupyter image:
FROM python:3.12-slim
RUN pip install ipykernel==6.29.0
Error Handling
from pod_executor import SecurityError, KernelError, SessionError
try:
result = executor.execute("import os; os.system('bad')")
except SecurityError as e:
print(f"Security violation: {e}")
except KernelError as e:
print(f"Kernel error: {e}")
Dependencies
- podman (Python library) - Podman API client
- jupyter-client - Jupyter kernel protocol (for stateful execution)
- pyzmq - ZMQ messaging (for Jupyter communication)
Limitations
- Requires Podman (not Docker)
- Resource limits may not work in all rootless configurations
- Jupyter backend needs host networking for ZMQ communication
- No automatic image pulling (images must exist)
Development
# Run syntax checks
python3 -m py_compile src/pod_executor/**/*.py
# Test simple execution
python3 -c "from pod_executor import CodeExecutor; print('Import OK')"
License
Part of MCP-Forge project.