Secure sandbox execution for AI-generated Python code.
SafeRun AI is a local-first developer tool that executes untrusted Python code inside a hardened Docker sandbox. It combines static AST scanning, policy enforcement, resource limits, and audit logging to protect the host system from malicious or buggy AI-generated code.
The tool is designed for developers, AI engineers, and local LLM users who need to test AI-generated code without risking their machine.
Large language models can generate code that:
- Deletes or modifies files (
os.remove,shutil.rmtree) - Spawns shells (
subprocess.run) - Exfiltrates data via network requests
- Consumes all CPU/memory (fork bombs, infinite loops)
- Uses
eval/execto bypass static checks
Running such code directly on your host is dangerous. SafeRun AI provides a layered defense.
We assume the attacker (or the AI model) can generate arbitrary Python code. The sandbox aims to prevent:
- Persistence – modifying system files or installing backdoors
- Exfiltration – sending data to external networks
- Resource exhaustion – CPU/memory/disk DoS
- Privilege escalation – breaking out of the container to the host
Current limitations:
- Kernel-level escapes
- Privileged container exploits
- Network-based attacks
Security layers:
- AST-based static scanner (detects dangerous imports/calls/paths)
- YAML policy engine (allow/block lists, resource limits)
- Docker container with:
- Non-root user (
sandbox, UID 1000) - Read-only root filesystem
- No network access (by default)
- Memory/CPU limits
- Process limits (pids-limit=64)
- No privileged mode, no host devices
- Non-root user (
- Optional Sarvam AI explanations (falls back to local rules)
- Execute untrusted Python in isolated Docker sandbox
- Static risk scanner with risk levels: LOW, MEDIUM, HIGH, BLOCKED
- Configurable policy (allowed imports, blocked calls, resources)
- Runtime monitoring (stdout, stderr, exit code, execution time)
- SQLite audit history with last 20 executions shown
- Optional Sarvam AI integration (graceful fallback)
- Works fully offline (no API key required)
- Developer-friendly Streamlit UI
- Export execution reports as JSON
# Install CLI
pip install saferun-ai
# Use immediately (connects to hosted backend)
saferun script.py
saferun run script.py
# Or use local backend
export SAFERUN_API_URL=http://localhost:8000
saferun script.py| Component | Technology |
|---|---|
| Backend | Python 3.11+, FastAPI, Pydantic |
| Sandbox | Docker SDK for Python |
| Scanner | Python ast module |
| Policy | PyYAML |
| Database | SQLAlchemy + SQLite |
| Frontend | Streamlit |
| AI (optional) | Sarvam AI REST API |
| Testing | pytest |
- Python 3.11 or higher
- Docker Desktop (or Docker Engine) running
- (Optional) Sarvam AI API key
# Clone the repository
git clone https://github.com/predictivemanish/saferun-ai.git
cd saferun-ai
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt
# Build the sandbox Docker image
cd sandbox_image
docker build -t saferun-sandbox:latest .
cd ..
# Set environment variables (optional)
cp .env.example .env
# Edit .env if you have a Sarvam API key (otherwise leave empty)
# Initialize database (auto-creates saferun.db)
python -c "from backend.database import init_db; init_db()"--
- Terminal 1 (Backend)
uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
- Terminal 2 (Frontend)
streamlit run frontend/app.py
-
Paste Python code into the editor.
-
Click Scan Only to see static analysis results and risk level.
-
Click Execute in Sandbox to run the code inside Docker.
-
If the code is blocked, check the Override safety blocks checkbox to force execution (use with caution).
-
View execution results, stdout/stderr, and execution time.
-
Scroll down to see the execution history (last 20 runs).
saferun-ai/
├── README.md
├── requirements.txt
├── .env.example
├── docker-compose.yml
├── backend/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── database.py
│ ├── models.py
│ ├── schemas.py
│ ├── scanner.py
│ ├── sandbox.py
│ ├── policy_engine.py
│ ├── audit.py
│ ├── explanations.py
│ ├── utils.py
│ └── policies/
│ └── default_policy.yaml
├── frontend/
│ ├── __init__.py
│ └── app.py
├── sandbox_image/
│ └── Dockerfile
├── tests/
│ ├── test_scanner.py
│ ├── test_policy.py
│ └── test_api.py
├── examples/
│ ├── safe_example.py
│ ├── dangerous_example.py
│ ├── timeout_example.py
│ └── network_example.py
├── tests/
│ ├── test_scanner.py
Disclaimer
No sandbox is completely secure. Running untrusted code always carries residual risk. Always review AI-generated code before execution, even when using this tool. The authors are not liable for any damages arising from its use.
SafeRun ships as a Model Context Protocol (MCP) server, so any MCP-capable
agent client — Claude Desktop, Claude Code, Cursor, VS Code Copilot, or your own
agent built on any MCP-compatible framework — can use SafeRun as its secure
Python execution tool. The agent never runs code on your host directly: every
snippet goes through AST scan -> policy check -> Docker sandbox -> audit log.
pip install "saferun-ai[mcp]" # or: pip install mcp docker pyyaml sqlalchemypython saferun_mcp.py # stdio transport (what MCP clients expect){
"mcpServers": {
"saferun": {
"command": "python",
"args": ["/absolute/path/to/saferun_mcp.py"]
}
}
}| Tool | What it does |
|---|---|
scan_code |
Static security scan — risk level, warnings, policy violations. No execution. |
execute_code |
Runs a snippet in the hardened sandbox after scan + policy. Refuses blocked code. |
get_history |
Recent executions from the audit log. |
- The
execute_codetool has no override flag — an agent cannot bypass a blocked scan the way a human can via the REST API'soverride=true. - Read-mode
open()is allowed (the sandbox rootfs is read-only anyway); write-mode is governed byfilesystem_write_enabledin the policy. - Works with both MCP SDK v1 (
FastMCP) and v2 (MCPServer).
- Policy engine missed
from X import Yviolations — the pattern parser splitimport_from_Xon the first underscore, yieldingfrom_X, which never matchedblocked_imports. The policy now consumes exact-match fields (detected_imports,detected_calls) from the scanner. - Aliasing bypass in the scanner —
f = eval; f("...")previously slipped pastDANGEROUS_CALLSbecause onlyast.Callnodes were inspected. The scanner now tracks dangerous aliases in a first pass, and also catchesgetattr(__builtins__, "ev" + "al")(constant-folded string bypasses).import builtinsis flagged as a dangerous import. open()in read mode is no longer auto-blocked — only write mode sets thefile_writepattern;filesystem_write_enabledin the policy governs it.- Timeouts are now reported as
status="timeout"(previously surfaced as a generic error), with partial stdout captured before the container is killed. - CORS fixed —
allow_credentials=Truewithallow_origins=["*"]is invalid per the CORS spec; credentials are now disabled.