Running Untrusted Python in a Real Sandbox: MicroPython + WASM + Wasmtime

Running Untrusted Python in a Real Sandbox: MicroPython + WASM + Wasmtime

Cloud Platform Engineering

The problem Simon Willison was solving is familiar to anyone who ships extensible software: plugin code runs with full application privileges. A buggy or malicious plugin can break everything or leak private data. The standard answer—virtual machines, containers, subprocess isolation—adds infrastructure overhead and operational complexity for what is often a simple script execution requirement. Willison’s answer is a 362KB WASM binary.

The Architecture

The sandbox stacks three components:

  1. MicroPython — a Python 3 implementation compiled to WebAssembly, optimized for constrained environments
  2. WebAssembly (WASM) — sandboxing guarantees tested at browser scale for a decade; the isolation layer that provides the security contract
  3. Wasmtime — a Python library that executes WASM binaries with built-in resource controls

The design preserves interpreter state across calls, which is the practically important detail. A background thread runs the MicroPython WASM interpreter. Code blocks arrive via a request queue; results return through a reply queue. Variables persist:

session.run("x = 10")
session.run("x += 5")  # x is 15; state persists between calls

This enables untrusted enrichment scripts and data transformation code to maintain state across a session without requiring the full interpreter to reinitialize per call.

The Resource Controls

Wasmtime’s “fuel” concept provides per-execution CPU limits—default 20 million units per call. Memory limits are enforced at the WASM boundary. File system access is explicitly scoped or disabled. Network operations route through controlled layers only.

The C module handling host function integration is 78 lines, compiled directly into the 362KB binary. No external dependencies.

What This Enables and Where It Falls Short

The Datasette Agent plugin (datasette-agent-micropython) is the first production consumer: it lets LLM-generated code run against a database without touching the host filesystem or network. The CLI tool (uvx micropython-wasm -c 'print("Hello world")') is available for testing.

Willison is transparent about the alpha state and the limits of fuel calibration—the units don’t map cleanly to wall-clock time and require empirical tuning per use case. The implementation has been tested for WASM-level breakouts but lacks professional security auditing. This is proof-of-concept that works in production for Willison’s specific trust model, not a drop-in hardened sandbox for arbitrary adversarial code.

The So What

For cloud platform teams building extensible SaaS tools, agentic code execution environments, or any service where user-supplied scripts run alongside trusted application logic, this pattern is worth understanding. The WASM sandbox approach trades infrastructure complexity (containers, subprocess management, network policies) for runtime complexity (fuel calibration, MicroPython stdlib limitations). For use cases where the code is Python, the scripts are short-lived, and the sandbox boundary is well-defined, the tradeoff is favorable. The 362KB artifact and zero-infrastructure-dependency story is particularly relevant for serverless or edge environments where spinning up a container per plugin execution is prohibitive.

Source: Simon Willison's Weblog

Content created with AI assistance and reviewed for accuracy.

💬

Join the conversation

Stack Insiders is our free community for readers who want to go deeper — share resources, ask questions, and connect with others across every vertical we cover.

Join Stack Insiders →

Newsletter coming soon.

Curated digests across AI, biohacking, photography, travel, and more. Be the first to know when we launch.