Skip to main content

Sandbox Isolation

Monty is designed to safely execute untrusted code by providing complete isolation from the host environment. The interpreter implements a strict sandbox that prevents code from accessing or modifying the host system.

What’s Blocked

Monty blocks all direct access to:
  • Filesystem: No direct file I/O operations
  • Environment Variables: Cannot read or modify environment variables
  • Network Access: No sockets or HTTP requests
  • Subprocess Execution: Cannot spawn processes or run shell commands
  • Import System: Cannot import modules with side effects or use __import__
  • Third-party Libraries: No external Python packages
  • Most Standard Library: Only a minimal subset is available

What’s Allowed

Monty provides controlled access through:
  • External Functions: Only functions explicitly provided by the host
  • OS Operations: Filesystem and network operations handled via host callbacks
  • Limited Standard Library: sys, os, typing, asyncio, re modules
  • Safe Operations: Pure computation, data structures, control flow
import pydantic_monty

# This code is safely sandboxed
code = """
result = x * 2
if result > 10:
    result = fetch_data()  # Only runs if you provide fetch_data
result
"""

m = pydantic_monty.Monty(code, inputs=['x'])

# You control what external functions are available
result = m.run(
    inputs={'x': 7},
    external_functions={'fetch_data': lambda: 'safe data'}
)
print(result)  # 'safe data'

Security Guarantees

Complete Host Isolation

Monty never uses CPython’s exec() or any FFI to Python. It’s a completely independent interpreter written in Rust that provides:
  • No filesystem access without explicit host callbacks
  • No environment variable access
  • No network access without explicit host callbacks
  • No subprocess execution
  • No access to system resources

Memory Safety

Monty is written in Rust without unsafe code (except in carefully reviewed FFI boundaries for Python/JavaScript bindings). This provides:
  • No buffer overflows
  • No use-after-free errors
  • No null pointer dereferences
  • Safe memory management with reference counting

Resource Limits

Monty enforces configurable limits to prevent denial-of-service attacks:
  • Memory limits: Cap maximum heap usage
  • Execution time: Timeout after specified duration
  • Allocation count: Limit number of heap allocations
  • Recursion depth: Prevent stack overflow (default: 1000)
See the Resource Limits page for detailed configuration options.

Controlled External Access

External Functions

All host interaction happens through external functions that you explicitly provide:
import pydantic_monty

code = "fetch(url)"
m = pydantic_monty.Monty(code, inputs=['url'])

# You control EXACTLY what the code can access
def safe_fetch(url: str) -> str:
    # Validate URL, check whitelist, add authentication, etc.
    if not url.startswith('https://api.example.com/'):
        raise ValueError('URL not allowed')
    # Your actual fetch logic here
    return "safe data"

result = m.run(
    inputs={'url': 'https://api.example.com/data'},
    external_functions={'fetch': safe_fetch}
)

OS Operations

Filesystem and network operations are never performed directly. Instead, they trigger callbacks that you handle:
# When code calls os.path.exists(path), Monty pauses
# and returns control to you via start()/resume()

progress = m.start(inputs={'path': '/some/path'})

if isinstance(progress, pydantic_monty.OsSnapshot):
    # You decide how to handle the OS operation
    if progress.function == 'path_exists':
        path = progress.args[0]
        # Apply your security checks
        result = your_safe_path_check(path)
        progress = progress.resume(return_value=result)

Deserialization Safety

Only deserialize (load()) snapshot data from trusted sources. Loading untrusted snapshot data could:
  • Restore malicious execution state
  • Bypass resource limits
  • Execute arbitrary code when resumed
Always validate the source of serialized data before calling:
  • Monty.load(bytes)
  • FunctionSnapshot.load(bytes)
  • RunProgress.load(bytes)

Threat Model

Monty is designed to protect against:

Protected Against

  • Code Injection: Untrusted Python code execution
  • Resource Exhaustion: Memory bombs, infinite loops
  • Information Disclosure: No access to host environment
  • Path Traversal: No filesystem access without your control
  • Regex DoS: Controlled through execution limits
  • Import Abuse: Only safe minimal standard library available

Out of Scope

  • Timing Attacks: Execution timing is observable
  • Deserialization Attacks: Don’t load snapshots from untrusted sources
  • Host Function Vulnerabilities: Security of external functions is your responsibility

Why Monty?

Traditional sandboxing approaches have significant limitations:
SolutionStart LatencySecurityComplexity
Monty0.06msStrictEasy
Docker195msGoodIntermediate
Pyodide/WASM2800msPoorIntermediate
Subprocess30msNoneEasy
Direct exec()0.1msNoneEasy
Monty provides strict security with microsecond startup time and no external dependencies.

Best Practices

1

Validate All Inputs

Validate and sanitize all input values before passing them to Monty.
2

Whitelist External Functions

Only provide the minimum set of external functions needed. Apply strict validation within each function.
3

Set Resource Limits

Always configure appropriate resource limits for your use case to prevent DoS.
4

Handle OS Callbacks Safely

When using start()/resume(), apply security checks in your OS operation handlers.
5

Trust Snapshot Sources

Only deserialize snapshots from trusted sources that you control.

Build docs developers (and LLMs) love