AI agents
Code Execution and Sandboxed Compute
Design code tools, sandboxes, filesystems, dependencies, and execution evidence for coding and analysis agents.
By the end you can
- Define sandboxed agent compute as an operational contract rather than a capability label
- Contrast Ephemeral sandbox with Persistent workspace in “A data-analysis agent installed a package that executed an unexpected setup script”
- Trace “Passing tests does not prove that generated code is safe” through a concrete execution path
- Produce “Threat-model a coding workspace” with evidence for “Generated code cannot access secrets or networks outside the task policy”
Key idea
Passing tests does not prove that generated code is safe
Tests may omit destructive behavior, data exfiltration, dependency risk, or hidden resource use. Unacceptable code can still produce a correct output.
The gap has a measured size. In 2022 a security study called Asleep at the Keyboard? built 89 scenarios and had GitHub Copilot complete each one. Its abstract gives the shape of it: “In total, we produce 89 different scenarios for Copilot to complete, producing 1,689 programs. Of these, we found approximately 40% to be vulnerable.”
One experiment inside it is worth reading twice. The MITRE Top-25 set ran 54 scenarios across 18 CWEs and produced 1,084 valid programs. Of those, 477 — 44.00% — contained a CWE. In 24 of the 54 scenarios, 44.44%, the vulnerable suggestion was the top-scoring one. The top-ranked completion is the one a developer accepts without scrolling.
Nothing in that experiment is about broken code. The programs compile and do the job asked of them. Roughly two in five also carry a weakness. A test suite written from the same task description as the prompt will pass on both kinds. It asks the same question the generator was answering.
So combine execution tests with sandboxing, diff review, dependency controls, and explicit artifact promotion. The suite tells you the code did what you asked. The other four controls are what tell you about the remaining 40%.
Of 1,689 programs Copilot produced across 89 scenarios, approximately 40% were vulnerable — and a green test run is not what separates them from the rest.
Example
ua-parser-js, xz, and the 205,474 package names that do not exist yet
On 22 October 2021 three versions of the npm package ua-parser-js — 0.7.29, 0.8.0 and 1.0.0 — were published with malicious code inside them. GitHub's advisory went out the same day, titled Embedded malware in ua-parser-js. It did not tell users to uninstall the package and move on. It told them: “Any computer that has this package installed or running should be considered fully compromised. All secrets and keys stored on that computer should be rotated immediately from a different computer.” NVD issued CVE-2021-4229 for the same event on 24 May 2022. Base score 8.8 (AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H), for a backdoor alongside a crypto-mining component. The fixed versions are 0.7.30, 0.8.1 and 1.0.1.
That advisory is the blast radius of a single install command, written out by the people who had to answer for it. Not remove the dependency. Rotate everything the machine could see, and do the rotating from a machine that never touched it. An agent that may install what it likes into an environment holding credentials is one publish away from being that machine.
The dangerous code need not be in the library's source at all. On 29 March 2024 a PostgreSQL developer, Andres Freund, wrote to the oss-security mailing list under the subject "backdoor in upstream xz/liblzma leading to ssh server compromise". His finding: “The upstream xz repository and the xz tarballs have been backdoored.” NVD gave CVE-2024-3094 the maximum base score, 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H). The liblzma build extracted a prebuilt object file from a disguised test file and used it to modify functions in the shipped library. Part of the backdoor existed only in the distributed tarballs, not in the repository's tracked sources. A reader auditing the repo would have found nothing. The build step was the payload.
The third piece is supplied by the agent itself. Models invent package names, and a 2025 study measured how often. Its abstract describes the experiment: “Using 16 popular LLMs for code generation and two unique prompt datasets, we generate 576,000 code samples in two programming languages that we analyze for package hallucinations.” At least 5.2% of the packages the commercial models recommended did not exist. For the open-source models it was 21.7%. The names amount to 205,474 unique hallucinated packages. Each one is an unclaimed slot on a public registry, free for anyone to register, under a name some model is already recommending.
Put the three together and the notebook task that "required a small parser" is a complete supply chain. A name the model invented. A registry that will serve whoever claims it. An install step that runs before any test does.
- Decision at stake: whether one install command inside an agent workspace is a decision nobody reviews. GitHub's advisory for ua-parser-js did not say remove the dependency. It said treat the machine as fully compromised and rotate every secret on it from a different computer.
- Hidden assumption: that a package name the model produced refers to something that exists. At least 5.2% of names recommended by commercial models and 21.7% of those from open-source models did not — 205,474 unique names, every one of them available for someone else to register.
- Primary control question: passing tests does not prove that generated code is safe. CVE-2024-3094 scored 10.0 for a backdoor injected by the build, and part of it lived only in the distributed tarballs, never in the tracked sources a reviewer would read.
- Evidence to collect: that generated code could not access secrets or networks outside the task policy. Show it per run from the sandbox's own logs and capability configuration, not from a suite that came back green.
Case
The runc escape, five years apart: CVE-2019-5736 and CVE-2024-21626
Container isolation is a real boundary with a documented history of failing. The record is specific enough to date twice.
CVE-2019-5736 let a process inside a container overwrite the host runc binary and gain root. NIST scored it 8.6. The disclosure went out in February 2019, and every runtime built on runc was affected. Google's gVisor answers this class by intercepting system calls in a userspace kernel.
Five years later the same class came back. CVE-2024-21626 was disclosed on 31 January 2024, as a container breakout through process.cwd trickery and leaked fds. NVD describes it this way: “In runc 1.1.11 and earlier, due to an internal file descriptor leak, an attacker could cause a newly-spawned container process (from runc exec) to have a working directory in the host filesystem namespace, allowing for a container escape by giving access to the host filesystem ("attack 2").” It affected runc 1.0.0-rc93 through 1.1.11 and was fixed in 1.1.12. Its base score is 8.6 HIGH — the same number as the 2019 bug, carried by the identical vector AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H.
Two escapes, two dates, one score, one vector. The boundary is worth having. It is not worth being the only thing between generated code and the host.
Generated code stays untrusted until the environment contains it
Code execution lets an agent compute precisely, transform data, run tests, and automate work. It also creates a powerful path to files, credentials, networks, subprocesses, and persistence.
A sandbox should define filesystem boundaries, network policy, process limits, dependency sources, execution time, and artifact export. Those six are the contract. Anything left unstated is granted by default.
The model's generated code remains untrusted until the environment contains its effects. Untrusted is not a judgement about the model. It is the only assumption compatible with the measured rate at which generated programs carry weaknesses, and with install steps that run arbitrary code before a single test does.
Nothing about the code itself makes it safe to run; the filesystem, network and process limits around it are all that separate a useful script from a path to credentials.
Visual
Code escapes through the layer nobody specified
Generated code escapes through whichever layer nobody specified — the workspace, the runtime image, the capability policy, or the resource limits. Resource limits and evidence should not share an owner or a test.
Five layers, and each one has a default that applies when you decline to choose. The workspace is an isolated filesystem with explicit inputs and export paths. The runtime image is a pinned language, system library, and dependency environment. The capability policy sets rules for network, subprocesses, devices, secrets, and persistence. Resource limits cover CPU, memory, storage, time, and process quotas. Evidence is logs, tests, diffs, artifacts, and environment metadata.
The capability layer is the one people believe they already have. Container runtimes ship the opposite default, and the federal guidance has said so since 2017. NIST Special Publication 800-190, the Application Container Security Guide, lists unbounded network access from containers among its container risks. The default, in its words: “By default in most container runtimes, individual containers are able to access each other and the host OS over the network.” The same guide warns that containers do not offer as clear and concrete a security boundary as a VM, because they share a kernel.
That is a federal standard describing the shipped default, not a hypothetical oversight. A team that runs a coding agent "in a container" and never wrote a network policy has not restricted its outbound reach. It has accepted the runtime's. The runtime's is open — to the other containers and to the host.
- 1
Workspace
An isolated filesystem with explicit inputs and export paths.
- 2
Runtime image
A pinned language, system library, and dependency environment.
- 3
Capability policy
Rules for network, subprocesses, devices, secrets, and persistence.
- 4
Resource limits
CPU, memory, storage, time, and process quotas.
- 5
Evidence
Logs, tests, diffs, artifacts, and environment metadata.
Comparison
The main choices inside sandboxed agent compute
An ephemeral sandbox, a persistent workspace, and host execution trade convenience against blast radius, in that order. The question to ask of each is the same. After a run, what could the code have touched that the task never mentioned?
An ephemeral sandbox answers easily, because whatever it touched is discarded with the container: strong isolation, simple cleanup, harder long-running state. A persistent workspace answers only if someone keeps maintaining the boundary. Files survive across steps or sessions, which supports projects but needs provenance and quotas, and lets attacks accumulate. Host execution runs generated code near production resources, and largely cannot answer at all: low setup cost, large blast radius, a poor default.
The usual argument for the last two is that real isolation is too expensive to put around every run. That price is published. Amazon Web Services reported Firecracker's numbers in 2020: memory overhead of less than 5MB per container, boot to application code in less than 125ms, and creation of up to 150 MicroVMs per second per host. Its jailer wraps the VMM in a chroot with pid and network namespaces, drops privileges, and applies a restrictive syscall filter: “The seccomp-bpf profile whitelists 24 syscalls, each with additional argument filtering, and 30 ioctls (of which 22 are required by KVM ioctl-based API).”
Under 5MB, under 125ms, 150 a second, and 24 permitted syscalls. Whatever argues for running generated code on the host, it is no longer the cost of the alternative. The choice is right when generated code cannot access secrets or networks outside the task policy, whatever the convenience it costs to get there.
Ephemeral sandbox
A fresh environment is destroyed after the task.
- Strong isolation
- Simple cleanup
- Harder long-running state
Persistent workspace
Files survive across steps or sessions.
- Supports projects
- Needs provenance and quotas
- Can accumulate attacks
Host execution
Generated code runs near production resources.
- Low setup cost
- Large blast radius
- Poor default
Steps
Threat-model a coding workspace
Threat-model the workspace a coding agent already uses. Its filesystem, its network, its package sources, its export path. For each of those four, write down what the agent can reach today and what the task actually requires, then close the gap between the two. If the exercise ends with no gap anywhere, suspect the threat model rather than the workspace, and try it again with a hostile reader. Record what you found. The next person to look will otherwise start from the same blank page.
Five steps make the result auditable. List assets: identify source code, secrets, customer data, networks, and build credentials. Define isolation: choose filesystem, process, network, and identity boundaries. Pin dependencies: use approved registries, lockfiles, hashes, and immutable images. Collect evidence: capture commands, outputs, diffs, tests, and exported artifacts. Gate promotion: require review or deterministic checks before code leaves the sandbox.
The third step is the one that gets waved through, and it is the one with a number attached. A 2019 study called Small World with High Risks went through the npm ecosystem: 5,386,239 package versions, 199,327 maintainers, 609 publicly reported security issues. Its finding: “Installing an average npm package introduces an implicit trust on 79 third-party packages and 39 maintainers, creating a surprisingly large attack surface.” By 2018, 391 maintainers each affected more than 10,000 packages, and 20 colluding maintainers could reach more than half the ecosystem.
So one `install` in an agent workspace is not a decision about one library. It is a trust decision about 79 packages and 39 people, taken by a process that will not be asked to justify it. Lockfiles, hashes, an approved registry and an immutable image are how that decision gets made once, by someone, in advance — rather than freshly at every run, by the model.
- 1
List assets
Identify source code, secrets, customer data, networks, and build credentials.
- 2
Define isolation
Choose filesystem, process, network, and identity boundaries.
- 3
Pin dependencies
Use approved registries, lockfiles, hashes, and immutable images.
- 4
Collect evidence
Capture commands, outputs, diffs, tests, and exported artifacts.
- 5
Gate promotion
Require review or deterministic checks before code leaves the sandbox.
A reviewable patch beats direct write access
A coding agent is safer when it produces a reviewable patch and test evidence than when it receives direct write access to production.
A reviewer's job starts where the suite stops. Passing tests does not prove that generated code is safe, only that it does what the tests thought to ask of it. Roughly 40% of the 1,689 programs in the Copilot study were vulnerable while doing the job asked of them. The install step that shipped the ua-parser-js backdoor ran before any test in the project could have looked at it. So read the patch for what it reaches — files, credentials, outbound calls, new dependencies — and not only for whether the run came back green.
The sandbox is what makes that review affordable, because anything the code did while it ran was already confined to what the task allowed. Without it, the review has to be exhaustive, since a miss is unbounded. With it, a miss costs a discarded container.
Give an agent the ability to commit and you have removed the one step where a human could still say no.
Key takeaways
- Code execution gives agents precise computation, transformation, testing, and automation capabilities — and of the 1,689 programs Copilot produced across 89 scenarios, approximately 40% were vulnerable.
- A sandbox should define filesystem boundaries, network policy, process limits, dependency sources, execution time, and artifact export. NIST SP 800-190 records that by default in most container runtimes, containers can reach each other and the host OS over the network.
- An isolated filesystem with explicit inputs and export paths is worth its cost. Firecracker reports under 5MB of memory overhead per container, boot to application code in under 125ms, and up to 150 MicroVMs per second per host.
- A pinned language, system library, and dependency environment is a decision about 79 third-party packages and 39 maintainers per average npm install, not a decision about one library.
- Combine execution tests with sandboxing, diff review, dependency controls, and explicit artifact promotion — CVE-2024-3094 scored 10.0 for a backdoor that lived in the build step, and part of it existed only in the distributed tarballs.
- A coding agent is safer producing a reviewable patch and test evidence than holding direct write access to production; the remedy for ua-parser-js was rotating every secret on the machine, from a different computer.