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).
2 How to Think About It
Think about running code safely, before any code:
3 The Build — explained part by part
Here is a complete sandbox using subprocess with a timeout. Each part is explained below.
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
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.
eval in your own process — it can do anything to your program.4 Test & Prove Each Part
We test that normal code runs and returns output, and that long-running code is killed by the timeout.
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
Success
{"success": true, "output": "4\n", "error": ""}Timeout
{"success": false, "output": "", "error": "Timed out"}6 Run It & Automate It
python3 sandbox.pySee safe code return its output, and an infinite loop get killed after the timeout.
Jenkins runs the tests automatically — each line explained below.
{'success': True, 'output': '4\n', 'error': ''}
{'success': False, 'output': '', 'error': 'Timed out'}timeout= to subprocess.run and catch TimeoutExpired.capture_output=True and text=True to capture stdout as text.// 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.' }
}
}
- Memory limits. Cap how much memory the code can use. (Teaches: resource limits.)
- No network. Block network access in the sandbox. (Teaches: deeper isolation.)
- Multiple languages. Sandbox more than Python. (Teaches: a general runner.)