thecodex.expert · The Codex Family of Knowledge
Tier 5 · Expert · Python Project

Process Sandbox

Run untrusted code in an isolated subprocess with limits and a timeout. Understand the isolation principles behind Docker and online code runners.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a sandbox: run a piece of code in a separate process, capture its output, and kill it if it runs too long — so untrusted or buggy code cannot hang or harm the host. It teaches process isolation and resource limits, the principles behind Docker, online judges, and serverless platforms (full containers add more, but this is the core idea).

Where this shows up: Docker and containers, online code runners (like coding-challenge sites), CI systems, serverless functions, any platform running untrusted code. Isolating code so it cannot harm or hang the host is essential and surprisingly approachable.

2 How to Think About It

Think about running code safely, before any code:

The plan — in plain English
1. Run the code in a separate process, not our own — so a crash or hang stays contained. → 2. Capture its output instead of letting it print to our screen. → 3. Enforce a timeout: if it runs longer than allowed, kill it. → 4. Report the output or that it timed out. Isolation plus limits keep the host safe.

Yes

No

Untrusted code

Run in a separate process

Capture its output

Finished in time?

Return the output

Kill it: timed out

3 The Build — explained part by part

Here is a complete sandbox using subprocess with a timeout. Each part is explained below.

Pythonsandbox.py
import subprocess

def run_sandboxed(code, timeout=5):
    # Run the code in a separate Python process, isolated from ours.
    try:
        result = subprocess.run(
            ["python3", "-c", code],
            capture_output=True,      # capture output instead of printing
            text=True,
            timeout=timeout,          # kill it if it runs too long
        )
        return {
            "success": result.returncode == 0,
            "output": result.stdout,
            "error": result.stderr,
        }
    except subprocess.TimeoutExpired:
        return {"success": False, "output": "", "error": "Timed out"}

if __name__ == "__main__":
    print(run_sandboxed("print(2 + 2)"))
    print(run_sandboxed("while True: pass", timeout=1))   # killed
What each part does — in plain words
subprocess.run(["python3", "-c", code], ...) — the key to isolation: run the code in a brand-new process, separate from ours. If it crashes or misbehaves, our program is untouched.

capture_output=True — grab the code’s output (and errors) instead of letting it write to our terminal. We decide what to do with it.

timeout=timeout — the crucial limit: if the code runs longer than allowed, subprocess.run kills the process and raises TimeoutExpired. This stops infinite loops dead.

returncode == 0 — a process exit code of 0 means success; anything else means it errored. We report which.

except subprocess.TimeoutExpired — catch the timeout and report it cleanly, rather than letting it propagate.

note on real sandboxes — this isolates execution and time. Production sandboxes (Docker, gVisor) also restrict the filesystem, network, memory, and system calls. This shows the foundational idea: separate process plus enforced limits.
Common mistakes — and how to avoid them
✗ Running untrusted code with eval in your own process — it can do anything to your program.
✓ Run it in a separate process so it is isolated.
✗ No timeout — an infinite loop hangs your whole program.
✓ Always set a timeout so runaway code is killed.
✗ Thinking a process + timeout is fully secure — it is not, on its own.
✓ For real isolation, also limit filesystem, network, and memory (containers do this).

4 Test & Prove Each Part

We test that normal code runs and returns output, and that long-running code is killed by the timeout.

Simple code runs and returns its output
Code that errors is reported as unsuccessful
An infinite loop is killed by the timeout
Pythontest_sandbox.py
import subprocess


def run_sandboxed(code, timeout=5):
    try:
        result = subprocess.run(
            ["python3", "-c", code],
            capture_output=True, text=True, timeout=timeout,
        )
        return {"success": result.returncode == 0,
                "output": result.stdout, "error": result.stderr}
    except subprocess.TimeoutExpired:
        return {"success": False, "output": "", "error": "Timed out"}


def test_simple_code():
    """Simple code runs and returns output."""
    result = run_sandboxed("print(2 + 2)")
    assert result["success"] is True
    assert result["output"].strip() == "4"


def test_error_code():
    """Code that raises is reported as failed."""
    result = run_sandboxed("raise ValueError('boom')")
    assert result["success"] is False
    assert "ValueError" in result["error"]


def test_timeout():
    """An infinite loop is killed."""
    result = run_sandboxed("while True: pass", timeout=1)
    assert result["success"] is False
    assert result["error"] == "Timed out"

Run with pytest -v. The timeout test is the important one: it proves a deliberately infinite loop is stopped after one second instead of hanging forever. Being able to safely run code that might never finish is the whole point of a sandbox.

5 The Interface

runrun_sandboxed(code, timeout)execute safely
Success
{"success": true, "output": "4\n", "error": ""}
Timeout
{"success": false, "output": "", "error": "Timed out"}

6 Run It & Automate It

Run it locally
python3 sandbox.py
See safe code return its output, and an infinite loop get killed after the timeout.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
{'success': True, 'output': '4\n', 'error': ''}
{'success': False, 'output': '', 'error': 'Timed out'}
If it breaks — how to fix it
🚨 The timeout does not kill the code.
Make sure you pass timeout= to subprocess.run and catch TimeoutExpired.
🚨 Output is empty for working code.
Set capture_output=True and text=True to capture stdout as text.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Memory limits. Cap how much memory the code can use. (Teaches: resource limits.)
  2. No network. Block network access in the sandbox. (Teaches: deeper isolation.)
  3. Multiple languages. Sandbox more than Python. (Teaches: a general runner.)
What you learned
You built a process sandbox: running code in an isolated subprocess with output capture and a timeout — the foundational principles behind Docker, online code runners, and serverless. Safely running untrusted code that might never finish is a powerful capability. Related: subprocess, Error Handling.