This project provides a secure API service that allows users to execute arbitrary Python scripts by submitting them via an HTTP POST request. The script must define a main() function that returns a JSON-compatible Python object.
- ✅ Execute arbitrary Python scripts in a controlled sandbox
- ✅ Flask-based HTTP API with
/executeendpoint - ✅ Output includes both return value and captured
stdout - ✅ Lightweight local runtime (437MB)
- ✅ Hardened Cloud Run deployment (735MB)
- ✅ Uses NsJail for isolation with custom seccomp filters and chroot
- ✅ Supports
os,pandas, andnumpy - ✅ Includes separate configurations for local and cloud environments
Build and run the Docker container with local sandboxing:
docker build -t python-api --build-arg BUILD=local .
docker run -p 8080:8080 python-apiThis project includes a Cloud Run-compatible version of NsJail using a custom patch to bypass gVisor restrictions (e.g., PR_SET_SECUREBITS).
docker build -t python-api --build-arg BUILD=cloud .Google Cloud Run uses gVisor as its container runtime sandbox, which has specific restrictions on system calls. Contrary to common assumptions, gVisor does not block chroot operations. Instead, it restricts more powerful namespace operations:
- ❌ clone(CLONE_NEWUSER) - Blocked (user namespace creation)
- ❌ clone(CLONE_NEWNET) - Blocked (network namespace creation)
- ❌ clone(CLONE_NEWPID) - Blocked (PID namespace creation)
- ✅ chroot() - Allowed (filesystem root change)
Since gVisor blocks the more robust namespace isolation mechanisms that tools like NsJail typically rely on, chroot became the viable sandboxing approach:
Traditional Approach (Blocked on Cloud Run)
nsjail --user_ns --net_ns --pid_ns --chroot /sandboxCloud Run Compatible Approach
chroot /sandbox /usr/bin/python3 script.pyNsJail Modifications
- 🛠️ Patched NsJail to disable securebits checks that prevent chroot in restricted environments
- 📦 Simplified sandboxing strategy to work within gVisor's constraints
Bundled Runtime Environment
- 🐍 Full Python 3.11 runtime included in /sandbox
- 📚 Minimal shared libraries for isolated execution
- 🔒 Read-only filesystem for additional security
Image Size Impact
- 🏠 Local image: ~437MB (relies on host system libraries)
- ☁️ Cloud image: ~735MB (includes complete isolated runtime)
Security Benefits Even with the simpler chroot approach, the sandboxing provides:
- Filesystem isolation - Code cannot access files outside /sandbox
- Library isolation - Uses only bundled, controlled dependencies
- Read-only environment - Prevents filesystem modifications
- gVisor additional protection - Hardware-level isolation from Cloud Run
The choice of chroot over user namespaces wasn't a limitation—it was an adaptation to gVisor's security model. By understanding what gVisor actually blocks (namespace creation, not chroot), I created a Cloud Run-compatible sandboxing solution that maintains security while working within the platform's constraints.
For the cloud variant, the following system calls are explicitly blocked using a restrictive seccomp policy:
KILL {
chmod, fchmod, fchmodat,
chown, fchown, lchown,
setuid, setgid, setreuid, setregid,
setresuid, setresgid, setfsuid, setfsgid,
setgroups,
mount, umount
} DEFAULT ALLOW
This ensures:
- 🚫 No permission changes (
chmod,chown, etc.) - 🔐 No UID/GID escalation (
setuid,setgid, etc.) - 🧱 No mounting or unmounting of filesystems
- Basic input validation ensures the script is a string and defines a
main()function that returns JSON. - All script executions are sandboxed using NsJail, making the system robust against malicious or unsafe code.
To demonstrate frontend integration and highlight my React skills, I’ve also built a simple React-based IDE-style client that lets users:
- Edit and run Python code in a browser
- Toggle between light/dark themes
- Configure the Cloud Run endpoint dynamically
- View structured JSON output from the API
This provides a convenient way to test the /execute endpoint interactively.
Built using React, CodeMirror, axios, and Tailwind-style UI with Lucide icons.
cd client
npm install
npm run devThen open http://localhost:5173 in your browser.
You can quickly test the API using the following commands:
curl -X POST https://python-api-378367989398.us-central1.run.app/execute \
-H "Content-Type: application/json" \
-d '{
"script": "def main():\n print(\"Hello from IDE\")\n return {\"status\": \"ok\"}"
}'curl -X POST https://python-api-378367989398.us-central1.run.app/execute \
-H "Content-Type: application/json" \
-d '{
"script": "def main():\n import numpy as np\n arr = np.array([1, 2, 3, 4, 5])\n return {\"mean\": float(np.mean(arr))}"
}'curl -X POST https://python-api-378367989398.us-central1.run.app/execute \
-H "Content-Type: application/json" \
-d '{
"script": "def main():\n import os\n os.mkdir(\"/should_fail\")\n return {\"status\": \"created\"}"
}'