Security Model
agex provides a secure Python execution environment for AI agents through a comprehensive multi-layer security strategy.
Philosophy: A Walled Garden, Not a Fortress
The security model of agex is best understood as a "walled garden," not an impenetrable fortress. Its primary goal is to provide a robust safety net that prevents common and foreseeable errors—both accidental and malicious—within its intended use case. It is designed to stop an agent from inadvertently deleting files, accessing sensitive data, or otherwise impacting the host system in unintended ways.
This approach is a deliberate trade-off. While a fully isolated Virtual Machine (VM) offers a higher degree of theoretical security, it comes at a cost to ease of integration.
agex is significantly safer than frameworks that rely on direct exec() calls, but is not intended to be a substitute for a VM when running fully untrusted code from unknown sources.
Core Security Strategy
The agex sandbox uses a whitelist-based security model with these key components:
- AST-level validation: All Python code is parsed and validated by the execution engine to block dangerous language features.
- Attribute access control: Only explicitly whitelisted attributes and methods are accessible.
- Data Isolation: When using
Versionedstate, all data is serialized at the boundary of an agent task. Agents never get a direct reference to host objects, preventing accidental or malicious mutation. - Type system isolation: Safe type placeholders prevent access to dangerous methods on type objects.
- Import restrictions: Module imports are controlled through explicit registration.
Python Language Restrictions
The framework modifies standard Python behavior in specific areas to maintain security:
String Formatting (.format() method)
Python's .format() method is intercepted to prevent attribute access attacks. Malicious code can use format strings to introspect and access arbitrary attributes of objects, a common technique for escaping sandboxes.
# ✅ Allowed: Simple key-based formatting
"Hello {name}".format(name="World")
# ❌ Blocked: Attribute access via format string
"{obj.attr}".format(obj=obj) # SecurityError: Format string attribute access not allowed
# ✅ Secure alternative: f-strings use proper AST validation
f"{obj.attr}"
Type System (type() builtin)
The type() builtin is overridden to return safe placeholder objects. This blocks access to dangerous, low-level methods on type objects while preserving legitimate functionality like isinstance() checks.
The primary goal is to prevent access to object.__subclasses__(), which can be used to traverse the entire class hierarchy of a Python application, find sensitive classes (like os._wrap_close), and achieve arbitrary code execution.
# ✅ Safe operations:
type(42)(123) # Constructor calls
isinstance(42, type(42)) # Type checking
# ❌ Blocked operations that allow sandbox escapes:
type(42).__subclasses__ # Class hierarchy introspection
type(42).__mro__ # Method resolution order
Introspection Functions
The built-in dir() and help() functions are overridden to show only the attributes and methods that have been explicitly whitelisted for the agent. This allows for useful introspection without leaking access to sensitive internal methods or private attributes (those prefixed with _).
For a complete overview of all sandbox limitations, see our Nearly Python guide.
Resource Limits
Beyond code validation, agex provides defense-in-depth resource limiting to protect against catastrophic resource exhaustion (e.g., [0] * 10**9 or infinite file creation):
Memory Limits
Memory limits use a delta-based approach: the limit is set to current process memory + configured headroom. This gives each task a budget for new allocations without counting existing process memory.
When exceeded, Python raises MemoryError, which the agent sees as an EvalError with the underlying MemoryError in the cause chain.
File Descriptor Limits
Prevents agents from exhausting system file descriptors through excessive file operations.
VFS Size Limits
Limits the total size of all files in the Virtual FileSystem, preventing unbounded storage consumption.
Platform Support
Resource limits use Unix RLIMIT_AS and RLIMIT_NOFILE, supported on Linux and macOS. On Windows, limits are not enforced (a warning is issued).
Process-Level Limits
Memory and file descriptor limits are process-wide. For concurrent tasks, size limits according to expected concurrency. For per-task isolation, use the Modal integration which provides containerized execution.
See Agent Resource Limits and VFS Size Limits for configuration details.
Network Access Control
By default, agent code cannot make network connections. This prevents agents from exfiltrating data, making unauthorized API calls, or accessing internal services.
How It Works
Network access is controlled at the socket level. During agent code evaluation, all network socket operations (connect, send, recv, bind, listen, DNS lookups) are blocked by default. This affects all networking code, including HTTP libraries like requests, httpx, and aiohttp.
# Agent code trying to make HTTP requests will fail:
import requests
response = requests.get("https://example.com") # SandboxError: Network access denied
Granting Network Access
To allow specific functions, classes, or modules to use the network, register them with network_access=True:
import requests
# Allow this specific function to make network calls
@agent.fn(network_access=True)
def fetch_weather(city: str) -> dict:
"""Fetch weather data for a city."""
response = requests.get(f"https://api.weather.com/{city}")
return response.json()
# Or register an HTTP client class
agent.cls(requests.Session, network_access=True)
# Or an entire module
agent.module(requests, network_access=True)
Practical Example: pandas
Many libraries have functions that work with both local files and URLs. For example, pandas' read_csv can load data from disk or from the network:
import pandas as pd
# Register pandas WITHOUT network_access
agent.module(pd, visibility="low", recursive=True)
With this registration, agent code can read local files but not URLs:
# ✅ Works - reads from VFS or local disk
df = pd.read_csv("data/sales.csv")
# ❌ Blocked - attempts network connection
df = pd.read_csv("https://example.com/data.csv") # SandboxError
This gives agents full pandas functionality for data analysis while preventing them from loading arbitrary data from the internet or exfiltrating data to external servers.
If you want to allow network access for pandas (e.g., for fetching public datasets), register it with network_access=True:
What Gets Blocked
The sandbox intercepts these socket operations when network access is denied:
| Operation | Purpose |
|---|---|
connect / connect_ex |
Outbound connections |
bind / listen / accept |
Server sockets |
send / sendall / sendto / sendfile |
Sending data |
recv / recvfrom / recv_into / recvfrom_into |
Receiving data |
getaddrinfo |
DNS resolution |
What's Allowed
Local inter-process communication (Unix domain sockets, AF_UNIX) is always allowed. This ensures Python's internal mechanisms (like asyncio's event loop) continue to work correctly. Only actual network sockets (AF_INET, AF_INET6) are gated.
Error Handling
When network access is denied, a SandboxError is raised with a helpful message:
SandboxError: Network access denied: connect() blocked by sandbox.
Register function with network_access=True to allow network operations.
See Registration Methods for details on the network_access parameter.