You can't sandbox Python with eval/exec tricks — stripping __builtins__ still lets attacker code reach every class in the process and run os.system in one line (proven below, tested). Real options, weakest to strongest: RestrictedPython (blocks dangerous syntax, but not a true sandbox); OS-level isolation — Docker, gVisor, or Firecracker micro-VMs plus seccomp (strong, but server infrastructure); and WebAssembly — Python compiled to WASM runs with no filesystem, no sockets, and no OS access by default. For a zero-setup WASM sandbox you can use right now, our online Python compiler runs code entirely in your browser.
Running untrusted Python is a real need — a coding playground that runs visitor submissions, a backend executing AI-generated code, or an analyst opening a suspicious script. The approach almost everyone reaches for first is a "restricted" eval/exec, and it is dangerously broken. Let me show you exactly how broken, then the approaches that actually work.
Everything below was run on Python 3.14 — including a real, one-line escape out of a naive sandbox.
The trap: eval/exec "sandboxes" escape in one line
The classic idea: run the user's code with the builtins removed, so they can't call open, __import__, or eval. It looks airtight — and it's defeated by a single expression, because Python's introspection lets you climb from *any* object back to every class loaded in the process:
# a naive "sandbox": strip every builtin, then run the user's codeSANDBOX = {"__builtins__": {}}# the attacker submits this — it uses NO builtins, only introspection:escape = ("[c for c in ().__class__.__base__.__subclasses__() ""if c.__name__=='BuiltinImporter'][0]().load_module('os')"".system('echo ESCAPED-THE-SANDBOX')")eval(escape, SANDBOX)
ESCAPED-THE-SANDBOX # os.system executed — sandbox defeated# it's not subtle: from an "empty" sandbox you still reach every# class loaded in the interpreter —>>> len(eval("().__class__.__base__.__subclasses__()", {"__builtins__": {}}))177
().__class__ is tuple, .__base__ is object, and object.__subclasses__() lists every class in the process — including one that can import os. From there it's os.system, and the attacker owns your machine. No builtins required.
Never run untrusted code with a restricted `eval`/`exec`. There is no blocklist that closes every introspection path — Python is too dynamic. If you can run arbitrary Python in your process, you have no sandbox at all.
RestrictedPython — better, but not a real sandbox
The most mature language-level option is RestrictedPython (from the Zope project). Instead of a blocklist, it compiles a *restricted subset* of Python and refuses dangerous constructs — including the dunder-attribute access the escape above depends on:
from RestrictedPython import compile_restricted, safe_globalssrc = "().__class__.__base__.__subclasses__()" # the escape from abovebytecode = compile_restricted(src, "<user>", "eval")eval(bytecode, dict(safe_globals))
SyntaxError: Line 1: "__subclasses__" is an invalid attribute namebecause it starts with "_".# RestrictedPython refuses dunder access at COMPILE time — escape blocked.
RestrictedPython's own maintainers are explicit: it "is not a sandbox system or a secured environment." It's excellent for a *trusted* subset — spreadsheet-style formulas, template logic — but Python's dynamism means language-level restriction alone shouldn't be your only barrier against a hostile attacker.
OS-level isolation: containers and micro-VMs
If a server has to execute genuinely untrusted code, the real boundary is the operating system, not the language. In rough order of strength:
- Containers (Docker/Podman) — Linux namespaces (PID, network, mount) isolate the process. Good, but a container shares the host kernel, so a kernel exploit escapes it. Drop capabilities, use a read-only rootfs, and disable networking.
- `seccomp` — a kernel feature that restricts which system calls a process may make (in the strictest mode, essentially only
read,write,exit,sigreturn). Layer it on top of a container to shrink the attack surface. - gVisor — a user-space kernel that intercepts syscalls, adding a strong isolation layer between the code and the real kernel.
- Micro-VMs (Firecracker) — a real VM boundary with near-native speed and millisecond boot. This is what serverless platforms use to run arbitrary customer code safely.
This tier is the right answer for a backend that runs user code at scale — but it's infrastructure you build, operate, and pay for. For most people who just need to *run some Python safely*, it's far more than they want to manage.
WebAssembly: the zero-setup sandbox
The modern sweet spot is WebAssembly. Python compiled to WASM (via Pyodide) runs inside a sandbox that was *designed* for untrusted code from day one and hardened in browsers for a decade. By default a WASM module has no filesystem, no network sockets, no environment variables, and no OS access — it can only touch what the host explicitly hands it.
That flips the escape from the first section on its head: even if attacker code reaches os inside a WASM Python, os.system has nothing to call — there's no shell, no real filesystem, and socket can't open a connection, because the WASM runtime simply doesn't expose them. The isolation is enforced *below* Python, so Python's dynamism can't defeat it.
The trade-off is compatibility: pure-Python packages install fine, but libraries with C extensions (NumPy, pandas) need a Pyodide-compiled build — the runtime ships many, but not every package on PyPI works.
Try it: a Python sandbox in your browser
Our free online Python compiler is a WASM sandbox — it runs your code through Pyodide entirely in your browser tab. That gives you the isolation properties above with zero setup:
- Nothing runs on a server — the code executes in *your* browser, so there's no backend to attack and nothing is uploaded.
- No sockets, no real filesystem, no OS — a reverse shell or credential-stealer simply can't function; the APIs it needs aren't there.
- It even installs packages —
importauto-installs pure-Python wheels from PyPI via micropip, still inside the sandbox.
One honest caveat that applies to *every* online code tool: the sandbox protects your machine, but don't paste real secrets (API keys, passwords) into any web editor — a hostile snippet could exfiltrate data you put in the sandbox via a network fetch. It can't reach anything on your computer, but it can read what you gave it.
Which sandbox should you use?
| Approach | Isolation | Use it for |
|---|---|---|
Restricted eval/exec | None (escapable) | Never — it is not a sandbox |
| RestrictedPython | Language-level | Trusted subsets: formulas, template logic |
| Container + seccomp / gVisor | Strong (OS) | A backend running user code at scale |
| Micro-VM (Firecracker) | Strongest (VM) | Multi-tenant untrusted code, serverless |
| WebAssembly (Pyodide) | Strong, zero-privilege | Browser/edge, zero-setup, portable |
Short version: never trust a restricted eval. For a trusted subset, RestrictedPython. For a server running hostile code, containers or micro-VMs. And for running untrusted Python right now with nothing to install, a WASM sandbox like our in-browser compiler is the fastest safe path.
Run Python in a sandbox — free
Our online compiler is a WASM sandbox: it runs your code in your browser, with no server, no sockets, and no filesystem. Nothing uploaded.
Open the Python SandboxFree tools mentioned here
Related guides
Frequently asked questions
How do I run untrusted Python code safely?
Don't use a restricted eval/exec — it's escapable in one line. Use real isolation instead: RestrictedPython for a trusted language subset, a container (Docker) with seccomp or gVisor for a server, a micro-VM (Firecracker) for multi-tenant workloads, or WebAssembly to run Python in a sandbox with no filesystem, network, or OS access. For zero setup, a WASM-based online compiler runs untrusted code in your browser.
Is eval() with restricted builtins a safe Python sandbox?
No. Stripping __builtins__ does not stop introspection: expressions like ().__class__.__base__.__subclasses__() still reach every class in the process, including ones that import os and run shell commands. A restricted eval is not a security boundary — it's defeated by a single line, as demonstrated on Python 3.14.
What is RestrictedPython and is it a sandbox?
RestrictedPython (from the Zope project) compiles a restricted subset of Python and blocks dangerous constructs like dunder-attribute access at compile time. Its maintainers state it is not itself a sandbox or secured environment — it's meant for running trusted, limited code (like template or formula logic), and shouldn't be your only defense against a hostile attacker.
Can WebAssembly sandbox Python?
Yes — this is one of the best modern options. Python compiled to WASM (via Pyodide) runs in a sandbox with no filesystem, network sockets, or OS access unless the host explicitly grants them, and the isolation is enforced below Python so its dynamic features can't escape it. The main limitation is that C-extension packages need a Pyodide-compiled build.
Is an online Python compiler safe to run untrusted code?
It depends on where the code runs. A server-side runner executes code on someone's machine and needs heavy isolation. A WASM-based one (like ours) runs entirely in your own browser: nothing is uploaded, no server executes the code, and the sandbox has no sockets or real filesystem, so classic malware can't function. Still, never paste real secrets into any web tool.