A lightweight sandboxing tool for enforcing filesystem and network restrictions on arbitrary processes at the OS level
Project description
Sandbox Runtime (Python)
This repo mirrors Anthropic's TypeScript implementation at https://github.com/anthropic-experimental/sandbox-runtime.
A Python implementation of the Sandbox Runtime - a lightweight sandboxing tool for enforcing filesystem and network restrictions on arbitrary processes at the OS level, without requiring a container.
srt-py uses native OS sandboxing primitives (sandbox-exec on macOS, bubblewrap on Linux) and proxy-based network filtering. It can be used to sandbox the behaviour of agents, local MCP servers, bash commands and arbitrary processes.
Beta Research Preview
The Sandbox Runtime is a research preview developed for Claude Code to enable safer AI agents. It's being made available as an early open source preview to help the broader ecosystem build more secure agentic systems.
Installation
Install from PyPI:
# Using pip
pip install sandbox-runtime
# Using uv
uv add sandbox-runtime
Or install directly from GitHub:
# Using pip
pip install "sandbox-runtime @ git+https://github.com/thoo/sandbox-runtime-py.git"
# Using uv
uv add "sandbox-runtime @ git+https://github.com/thoo/sandbox-runtime-py.git"
For development:
git clone https://github.com/thoo/sandbox-runtime-py.git
cd sandbox-runtime-py
uv sync --all-extras
Basic Usage
CLI
Command Modes
- Args mode:
srt-py <cmd> [args...](unknown flags are passed through) - String mode:
srt-py -c "<shell command>" - Invalid config files now fail fast instead of silently falling back to defaults.
- HTTPS proxying uses CONNECT tunneling; direct
https://requests without CONNECT are rejected.
macOS Verification (sandbox-exec)
# Confirm sandbox-exec is available
which sandbox-exec
# Create a simple settings file
cat > /tmp/srt-settings.json <<'JSON'
{
"network": {
"allowedDomains": ["example.com"],
"deniedDomains": []
},
"filesystem": {
"denyRead": ["~/.ssh"],
"allowWrite": ["."],
"denyWrite": []
}
}
JSON
# Network allowlist (HTTP)
srt-py --debug --settings /tmp/srt-settings.json curl -I -m 5 http://example.com
# Network allowlist (HTTPS via CONNECT)
srt-py --debug --settings /tmp/srt-settings.json curl -I -m 5 https://example.com
# Filesystem deny
srt-py --settings /tmp/srt-settings.json cat ~/.ssh/id_rsa
# Network restrictions
$ srt-py "curl anthropic.com"
Running: curl anthropic.com
<html>...</html> # Request succeeds
$ srt-py "curl example.com"
Running: curl example.com
Connection blocked by network allowlist # Request blocked
# Filesystem restrictions
$ srt-py "cat README.md"
Running: cat README.md
# Anthropic Sandb... # Current directory access allowed
$ srt-py "cat ~/.ssh/id_rsa"
Running: cat ~/.ssh/id_rsa
cat: /Users/.../.ssh/id_rsa: Operation not permitted # Specific file blocked
# With debug logging
$ srt-py --debug curl https://example.com
# With custom settings file
$ srt-py --settings /path/to/srt-settings.json npm install
# Flags are passed through to the command (no `--` needed)
$ srt-py curl -I https://example.com
As a Library
The sandbox runtime can be used as a Python library for programmatic control over sandboxing:
import asyncio
import subprocess
from sandbox_runtime import SandboxManager, SandboxRuntimeConfig
async def main():
# Define your sandbox configuration
config = SandboxRuntimeConfig(
network={
"allowed_domains": ["example.com", "api.github.com"],
"denied_domains": [],
},
filesystem={
"deny_read": ["~/.ssh"],
"allow_write": [".", "/tmp"],
"deny_write": [".env"],
},
)
# Initialize the sandbox (starts proxy servers, etc.)
await SandboxManager.initialize(config)
# Wrap a command with sandbox restrictions
sandboxed_command = await SandboxManager.wrap_with_sandbox(
"curl https://example.com"
)
# Execute the sandboxed command
process = subprocess.Popen(
sandboxed_command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = process.communicate()
print(f"Exit code: {process.returncode}")
print(f"Output: {stdout.decode()}")
# Cleanup when done (optional, happens automatically on process exit)
await SandboxManager.reset()
asyncio.run(main())
Advanced Library Usage
Custom Permission Callback
You can provide a callback to handle permission requests for domains not in the allowlist:
from sandbox_runtime import SandboxManager, SandboxRuntimeConfig
from sandbox_runtime.schemas import NetworkHostPattern
async def permission_callback(request: NetworkHostPattern) -> bool:
"""Called when a request is made to a domain not in the allowlist."""
print(f"Permission requested for {request.host}:{request.port}")
# Implement your own logic (prompt user, check database, etc.)
return request.host.endswith(".trusted.com")
async def main():
config = SandboxRuntimeConfig(
network={
"allowed_domains": ["api.example.com"],
"denied_domains": ["malicious.com"],
},
filesystem={
"deny_read": [],
"allow_write": ["."],
"deny_write": [],
},
)
# Pass the callback to initialize
await SandboxManager.initialize(
config,
sandbox_ask_callback=permission_callback,
)
# ... rest of your code
Violation Tracking
Monitor sandbox violations in real-time:
from sandbox_runtime import SandboxManager, SandboxViolationStore
# Get the violation store
store = SandboxManager.get_sandbox_violation_store()
# Subscribe to violation updates
def on_violation(violations):
for v in violations:
print(f"Violation: {v.line}")
unsubscribe = store.subscribe(on_violation)
# Get violations for a specific command
violations = store.get_violations_for_command("cat /etc/passwd")
# Get all recent violations
all_violations = store.get_violations(limit=10)
# Unsubscribe when done
unsubscribe()
Checking Dependencies
from sandbox_runtime import SandboxManager
# Check if all sandbox dependencies are available
if SandboxManager.check_dependencies():
print("Sandbox is ready")
else:
print("Missing dependencies")
# Check platform support
from sandbox_runtime.utils.platform import get_platform
platform = get_platform()
if SandboxManager.is_supported_platform(platform):
print(f"Platform {platform} is supported")
Getting Configuration Details
from sandbox_runtime import SandboxManager
# Get filesystem configurations
read_config = SandboxManager.get_fs_read_config()
write_config = SandboxManager.get_fs_write_config()
print(f"Denied read paths: {read_config.deny_only}")
print(f"Allowed write paths: {write_config.allow_only}")
print(f"Denied write paths: {write_config.deny_within_allow}")
# Get network configuration
network_config = SandboxManager.get_network_restriction_config()
print(f"Allowed hosts: {network_config.allowed_hosts}")
print(f"Denied hosts: {network_config.denied_hosts}")
# Get proxy ports (after initialization)
http_port = SandboxManager.get_proxy_port()
socks_port = SandboxManager.get_socks_proxy_port()
Available Exports
from sandbox_runtime import (
# Main manager
SandboxManager,
# Configuration models (Pydantic)
SandboxRuntimeConfig,
NetworkConfig,
FilesystemConfig,
RipgrepConfig,
IgnoreViolationsConfig,
# Schema types
FsReadRestrictionConfig,
FsWriteRestrictionConfig,
NetworkRestrictionConfig,
NetworkHostPattern,
SandboxAskCallback,
# Violation tracking
SandboxViolationStore,
SandboxViolationEvent,
# Utilities
get_default_write_paths,
)
Overview
This package provides a standalone sandbox implementation that can be used as both a CLI tool and a library. It's designed with a secure-by-default philosophy tailored for common developer use cases: processes start with minimal access, and you explicitly poke only the holes you need.
Key capabilities:
- Network restrictions: Control which hosts/domains can be accessed via HTTP/HTTPS and other protocols
- Filesystem restrictions: Control which files/directories can be read/written
- Unix socket restrictions: Control access to local IPC sockets
- Violation monitoring: On macOS, tap into the system's sandbox violation log store for real-time alerts
Example Use Case: Sandboxing MCP Servers
A key use case is sandboxing Model Context Protocol (MCP) servers to restrict their capabilities:
Without sandboxing (.mcp.json):
{
"mcpServers": {
"filesystem": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_server_filesystem"]
}
}
}
With sandboxing (.mcp.json):
{
"mcpServers": {
"filesystem": {
"command": "srt-py",
"args": ["uv", "run", "python", "-m", "mcp_server_filesystem"]
}
}
}
If you have an installed entrypoint, you can use uvx instead:
{
"mcpServers": {
"filesystem": {
"command": "uvx",
"args": ["mcp_server_filesystem"]
}
}
}
With sandboxing (.mcp.json):
{
"mcpServers": {
"filesystem": {
"command": "srt-py",
"args": ["uvx", "mcp_server_filesystem"]
}
}
}
How It Works
The sandbox uses OS-level primitives to enforce restrictions that apply to the entire process tree:
- macOS: Uses
sandbox-execwith dynamically generated Seatbelt profiles - Linux: Uses bubblewrap for containerization with network namespace isolation
Dual Isolation Model
Both filesystem and network isolation are required for effective sandboxing.
Filesystem Isolation enforces read and write restrictions:
- Read (deny-only pattern): By default, read access is allowed everywhere. You can deny specific paths (e.g.,
~/.ssh). An empty deny list means full read access. - Write (allow-only pattern): By default, write access is denied everywhere. You must explicitly allow paths (e.g.,
.,/tmp). An empty allow list means no write access.
Network Isolation (allow-only pattern): By default, all network access is denied. You must explicitly allow domains. An empty allowedDomains list means no network access.
Configuration
Settings File Location
By default, the sandbox runtime looks for configuration at ~/.srt-settings.json. You can specify a custom path using the --settings flag:
srt-py --settings /path/to/srt-settings.json <command>
Complete Configuration Example
{
"network": {
"allowedDomains": [
"github.com",
"*.github.com",
"api.github.com",
"pypi.org",
"*.pypi.org"
],
"deniedDomains": ["malicious.com"],
"allowUnixSockets": ["/var/run/docker.sock"],
"allowLocalBinding": false
},
"filesystem": {
"denyRead": ["~/.ssh"],
"allowWrite": [".", "src/", "tests/", "/tmp"],
"denyWrite": [".env", "config/production.json"],
"allowGitConfig": false
},
"ignoreViolations": {
"*": ["/usr/bin", "/System"],
"git push": ["/usr/bin/nc"]
},
"enableWeakerNestedSandbox": false,
"mandatoryDenySearchDepth": 3
}
Configuration Options
Network Configuration
Uses an allow-only pattern - all network access is denied by default.
| Option | Type | Description |
|---|---|---|
allowedDomains |
list[str] |
Allowed domains (supports wildcards like *.example.com). Empty = no network access. |
deniedDomains |
list[str] |
Denied domains (checked first, takes precedence) |
allowUnixSockets |
list[str] |
Unix socket paths that can be accessed (macOS only) |
allowLocalBinding |
bool |
Allow binding to local ports (default: false) |
httpProxyPort |
int |
Use external HTTP proxy instead of built-in |
socksProxyPort |
int |
Use external SOCKS proxy instead of built-in |
Filesystem Configuration
| Option | Type | Description |
|---|---|---|
denyRead |
list[str] |
Paths to deny read access (deny-only pattern) |
allowWrite |
list[str] |
Paths to allow write access (allow-only pattern) |
denyWrite |
list[str] |
Paths to deny write within allowed paths |
allowGitConfig |
bool |
Allow writes to .git/config (default: false) |
Path Syntax
macOS supports git-style glob patterns:
*- Matches any characters except/**- Matches any characters including/?- Matches any single character except/[abc]- Matches any character in the set
Linux currently does not support glob matching. Use literal paths only.
All platforms:
- Paths can be absolute or relative to the current working directory
~expands to the user's home directory
Other Configuration
| Option | Type | Description |
|---|---|---|
ignoreViolations |
dict[str, list[str]] |
Command patterns → paths where violations are ignored |
enableWeakerNestedSandbox |
bool |
Enable weaker sandbox for Docker environments |
mandatoryDenySearchDepth |
int |
Search depth for dangerous files (1-10, default: 3) |
allowPty |
bool |
Allow pseudo-terminal operations |
Platform Support
| Platform | Status | Mechanism |
|---|---|---|
| macOS | Supported | sandbox-exec with Seatbelt profiles |
| Linux | Supported | bubblewrap (bwrap) |
| Windows | Not supported | - |
Platform-Specific Dependencies
Linux requires:
# Ubuntu/Debian
apt-get install bubblewrap socat ripgrep
# Fedora
dnf install bubblewrap socat ripgrep
# Arch
pacman -S bubblewrap socat ripgrep
macOS requires:
# Install via Homebrew
brew install ripgrep
API Reference
SandboxManager
The main class for managing sandbox restrictions. All methods are static.
class SandboxManager:
# Initialization
@staticmethod
async def initialize(
runtime_config: SandboxRuntimeConfig,
sandbox_ask_callback: SandboxAskCallbackType | None = None,
enable_log_monitor: bool = False,
) -> None: ...
# State checking
@staticmethod
def is_sandboxing_enabled() -> bool: ...
@staticmethod
def is_supported_platform(platform: Platform) -> bool: ...
@staticmethod
def check_dependencies(ripgrep_config: RipgrepConfig | None = None) -> bool: ...
# Command wrapping
@staticmethod
async def wrap_with_sandbox(
command: str,
bin_shell: str | None = None,
custom_config: SandboxRuntimeConfig | None = None,
) -> str: ...
# Configuration access
@staticmethod
def get_config() -> SandboxRuntimeConfig | None: ...
@staticmethod
def update_config(new_config: SandboxRuntimeConfig) -> None: ...
@staticmethod
def get_fs_read_config() -> FsReadRestrictionConfig: ...
@staticmethod
def get_fs_write_config() -> FsWriteRestrictionConfig: ...
@staticmethod
def get_network_restriction_config() -> NetworkRestrictionConfig: ...
# Proxy information
@staticmethod
def get_proxy_port() -> int | None: ...
@staticmethod
def get_socks_proxy_port() -> int | None: ...
# Violation tracking
@staticmethod
def get_sandbox_violation_store() -> SandboxViolationStore: ...
@staticmethod
def annotate_stderr_with_sandbox_failures(command: str, stderr: str) -> str: ...
# Cleanup
@staticmethod
async def reset() -> None: ...
SandboxRuntimeConfig
Pydantic model for configuration:
from sandbox_runtime import SandboxRuntimeConfig, NetworkConfig, FilesystemConfig
config = SandboxRuntimeConfig(
network=NetworkConfig(
allowed_domains=["example.com"],
denied_domains=[],
),
# Or use dict (auto-converted)
filesystem={
"deny_read": ["~/.ssh"],
"allow_write": ["."],
"deny_write": [".env"],
},
)
# Serialize to dict
config_dict = config.model_dump()
# Generate JSON schema
schema = SandboxRuntimeConfig.model_json_schema()
SandboxViolationStore
class SandboxViolationStore:
def add_violation(self, violation: SandboxViolationEvent) -> None: ...
def get_violations(self, limit: int | None = None) -> list[SandboxViolationEvent]: ...
def get_violations_for_command(self, command: str) -> list[SandboxViolationEvent]: ...
def get_count(self) -> int: ...
def get_total_count(self) -> int: ...
def clear(self) -> None: ...
def subscribe(self, listener: ViolationListener) -> Callable[[], None]: ...
Architecture
sandbox_runtime/
├── __init__.py # Public API exports
├── cli.py # CLI entrypoint (srt-py command)
├── config.py # Pydantic configuration models
├── schemas.py # Type definitions
├── manager.py # Main sandbox manager
├── http_proxy.py # HTTP/HTTPS proxy (aiohttp)
├── socks_proxy.py # SOCKS5 proxy (asyncio)
├── macos_sandbox.py # macOS sandbox-exec utilities
├── linux_sandbox.py # Linux bubblewrap utilities
├── seccomp.py # Seccomp filter handling
├── sandbox_utils.py # Shared utilities
├── violation_store.py # Violation tracking
└── utils/
├── debug.py # Debug logging
├── platform.py # Platform detection
└── ripgrep.py # Ripgrep wrapper
Development
# Install with dev dependencies
uv sync --all-extras
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=sandbox_runtime
# Type checking
uv run pyright
# Linting
uv run ruff check
# Formatting
uv run ruff format
# Run all pre-commit hooks
uvx pre-commit run --all-files
Building Seccomp Binaries
The pre-generated BPF filters are included in the repository, but you can rebuild them if needed. This requires Docker:
# From the parent sandbox-runtime directory
cd ..
./scripts/build-seccomp-binaries.sh
This script uses Docker to cross-compile seccomp binaries for multiple architectures:
- x64 (x86-64)
- arm64 (aarch64)
The script builds static generator binaries, generates the BPF filters (~104 bytes each), and stores them in vendor/seccomp/x64/ and vendor/seccomp/arm64/. The generator binaries are removed to keep the package size small.
What gets built:
unix-block.bpf- Pre-compiled BPF filter that blocks Unix domain socket creationapply-seccomp- Static binary that applies the seccomp filter and execs the user command
Source files (in vendor/seccomp-src/):
seccomp-unix-block.c- Generates the BPF filter using libseccompapply-seccomp.c- Applies the filter viaprctl(PR_SET_SECCOMP)
Architecture support: x64 and arm64 are fully supported with pre-built binaries. Other architectures are not currently supported.
For more details, see the original TypeScript implementation README.
Security Limitations
-
Network Sandboxing: The network filtering operates by restricting domains. It does not inspect traffic content. Users should be aware of potential data exfiltration through allowed domains.
-
Privilege Escalation via Unix Sockets: The
allowUnixSocketsconfiguration can grant access to powerful system services (e.g., Docker socket). -
Filesystem Permission Escalation: Overly broad write permissions can enable privilege escalation.
-
Linux Sandbox Strength: The
enableWeakerNestedSandboxmode considerably weakens security and should only be used in Docker environments.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sandbox_runtime-0.2.0.tar.gz.
File metadata
- Download URL: sandbox_runtime-0.2.0.tar.gz
- Upload date:
- Size: 671.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc205f8d93790b304e14f0a0e5ee229f6504186d1cdf2a90be01e8bb1bb07ef3
|
|
| MD5 |
452da6207b34e8013ca6d34629b03d65
|
|
| BLAKE2b-256 |
f6363aacccf31602eebb731059f256c5369ec9e10bd2fceabedf6f85672416f1
|
File details
Details for the file sandbox_runtime-0.2.0-py3-none-any.whl.
File metadata
- Download URL: sandbox_runtime-0.2.0-py3-none-any.whl
- Upload date:
- Size: 66.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fae29ed936caeb4294007bf5dee6ec0fca4aaaf35b2a4af34fa6e496d247372
|
|
| MD5 |
1fa287a6b536c9c41e354c47ccbb6b99
|
|
| BLAKE2b-256 |
e6d68fcfa949c7d53013bae642757b824e7b75dbbc552e4a9fa5981001c68d29
|