Cover image for How to safely run AI-generated code — a practical sandboxing checklist

Weston Carnes

Cross-post. Original: stellarbytecapital.com/blog/how-to-run-ai-generated-code-safely

If you're building an AI agent, sooner or later it will write code and you'll have to run that code. The moment you do, you're executing something no human reviewed against your infrastructure. This is a practical checklist for doing that safely — the controls we use in production, in the order they matter.

The short version: treat every piece of AI-generated code as hostile, and design so that even a full compromise of the runtime buys the attacker nothing.

First, the threat model

Before controls, be honest about what can go wrong when you run untrusted code:

  • It reads or exfiltrates data belonging to other users on the same host.
  • It reaches out to the network to leak data or pull a payload.
  • It leaves state behind — temp files, mutated env, background threads — that corrupts the next run.
  • It exhausts CPU, memory, or disk and takes down the shared service.
  • It escapes the sandbox entirely via a kernel or runtime bug.

"The model probably won't do that" is not a control. Design for the case where it does.

The core pattern: one disposable sandbox per execution

The single highest-leverage decision: run every execution in its own fresh sandbox, and destroy it after the run. Never reuse.

Reuse is where most bugs and attacks live — leaked file descriptors, leftover temp files, mutated globals, a background thread from the last run. If nothing is ever reused, that entire class of problems disappears. To keep it fast, keep a warm pool of ready sandboxes and backfill each one as it's consumed.

The checklist

  1. Isolation boundary — use a real boundary, not a language-level "safe eval." A container is the baseline; a microVM (gVisor, Firecracker) is stronger against kernel escapes.
  2. Network egress: default-deny — no outbound network by default. An escaped agent that can't reach the internet has nowhere to send data.
  3. Filesystem: read-only + ephemeral — mount inputs read-only; give a scratch space that dies with the sandbox.
  4. Resource limits — cap CPU, memory, PIDs, and wall-clock time per execution.
  5. Per-user quotas — cap executions per user per window; stops abuse and runaway loops.
  6. Least privilege — non-root, dropped capabilities, no Docker socket, no host devices.
  7. Secrets stay out — never mount API keys into a sandbox running generated code; proxy authenticated calls through a trusted layer.
  8. Audit everything — emit an event for every lifecycle step and every network call.

Common mistakes

  • Reusing a long-lived interpreter to save startup time — you trade milliseconds for a permanent state-leak surface.
  • Allowing full network egress "because the task might need it." Default-deny, then allowlist.
  • Trusting the model to stay in scope. Sandbox behavior, don't prompt for it.
  • Running as root inside the container because it was easier during development.

Container or microVM?

For most internal tools, a hardened one-shot container with default-deny egress is a reasonable baseline. If you're multi-tenant or running adversarial code, step up to gVisor or Firecracker — stronger isolation, still cheap with a warm pool.

The point is the combination: disposable sandboxes, no egress, read-only filesystem, capped resources, quotas, least privilege, no secrets, full auditing.


We're Xingyao Byte — building secure AI-execution layers, quant trading systems, and payment platforms. Remote, async-first → stellarbytecapital.com