Running Untrusted Code Safely: Building a gVisor Sandbox for AI-Generated Functions
What I learned building a harness that grades untrusted TypeScript inside gVisor containers: which layers are real security boundaries, and how the same problem looks on AWS Lambda.
I built a small harness that turns a TypeScript function into a coding challenge and then grades other people's rewrites of it. The interesting part isn't the grading. It's that the harness has to run code it doesn't trust, and getting that right changed how I think about sandboxing AI-generated code, which is the same problem any agent hits when it executes what a model wrote.
What the harness does
Point it at a trusted function. A static analyzer uses the TypeScript type checker to describe the parameters and flag sources of nondeterminism like
Date.now() and module state. An input generator creates edge-case values from the types (optionally asking an LLM for semantically interesting ones). The original function runs in the sandbox, its outputs are frozen as a JSON challenge, and later any rewrite is graded against those frozen results without needing the original source.Which layers are actually security boundaries
Honest answer: one. Inside the sandbox I use a static import allowlist, a
vm context with string code generation disabled, and a seccomp profile. All of those are defense in depth. The only real boundary is gVisor (runsc): no network, a read-only root filesystem, a non-root user, a syscall allowlist, and memory, CPU and PID cgroup limits. Everything else only reduces noise.Writing that down separately turned out to be useful. It stops you from treating a
vm context as a jail.A design change I'm glad I made
My first version had a privileged harness thread holding an HMAC key, to prove results came from the harness and not from the submission. That only makes sense if the harness and the untrusted code share a process. Once each pass runs in its own container, there is no secret inside to steal, so I deleted the key. Instead the host reconciles the set of test IDs: the sandbox must report exactly the expected ones, no fewer, no more, no duplicates.
Other decisions
- Each submission runs twice, once in order and once shuffled. If the results diverge, the verdict is "nondeterministic" instead of picking a winner.
- Time and randomness are frozen per test (a logical clock and a seed derived from the test ID) rather than disabled, so functions that use them stay gradable.
- A test that hangs or eats memory gets its container killed, and a fresh container starts with only the remaining tests.
- Mutation testing checks the test suite itself. A suite can score 100% on grading and much lower on mutations, which shows that edge cases like default parameter values aren't really exercised.
- There are per-test and per-pass wall-clock limits, a 64 MB V8 heap cap, and a host-side RSS watchdog for off-heap memory.
How this maps to AWS
I haven't deployed this on AWS, so treat this section as design reasoning, not results. Lambda already isolates each execution environment with Firecracker microVMs, but you don't control the runtime, so you can't swap in gVisor there. My harness needs a Linux host where I can register
runsc, so on AWS it would be EC2, or ECS on EC2, with gVisor-enabled containers and a controller that schedules one short-lived container per pass.If an agent on AWS executes model-written code, the same questions apply: no network by default, hard resource limits, a fresh environment per run, and never trusting results the sandbox reports about itself without checking them on the host.
Takeaways
- Name your real security boundary and label everything else as defense in depth.
- Remove secrets from places the untrusted code can reach instead of trying to protect them.
- Verify what the sandbox tells you from outside the sandbox.
- Test your tests: mutation testing exposed gaps that a 100% score hid.
The code is at https://github.com/mhm655/recreate. I'd like to know how others sandbox agent-generated code: do you reach for Lambda, containers with gVisor, or something else?
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article