AI Security

Sandboxing AI Workloads and Securing Agents Across the Engineering Pipeline

Containers aren't enough. The isolation ladder from WASM to microVMs, how to stand it up on Google Cloud, AWS and your own machine, and how to keep coding agents and CI/CD pipelines contained when they hold the keys to everything.

Every AI agent that can run code is, functionally, a remote code execution endpoint that you deployed on purpose. The model writes a script, your infrastructure runs it, and whatever that script does happens with whatever privileges you gave it. That would be alarming enough if models only produced the code you asked for. They don't. Models also read: repository contents, issue threads, pull request descriptions, web pages, tool outputs. Any of those can carry instructions the model will treat as its own.

This guide is a practical map of the problem. It covers the isolation options for running AI-generated code, how to stand them up on Google Cloud, AWS and your own machine, and then the part most teams skip: how to keep agents contained when they live inside your engineering workflow and your CI/CD pipeline, where they hold the keys to everything.

Three things you are actually defending against

Before picking a sandbox, be clear about the threats, because each one wants a different control.

  • Untrusted code. The agent writes a program and you execute it. Bugs, infinite loops, rm -rf and deliberate exploits all live here. This is what compute isolation (containers, gVisor, microVMs) addresses.
  • Untrusted input, also known as prompt injection. The agent reads something an attacker controlled and follows the instructions embedded in it. No amount of kernel isolation helps, because the model is doing exactly what it was designed to do. The controls here are network egress restriction, tool permissioning, and human review at the points where actions become irreversible.
  • Over-privileged identity. The agent runs with an IAM role, a GITHUB_TOKEN or an API key that can do far more than the task requires. When either of the first two threats materialises, this is what turns a contained incident into a breach. The control is boring and essential: least privilege, short-lived credentials, and keeping secrets out of the agent's reach entirely.

Most published incidents involve the second and third, not the first. The sandbox held; the network and the credentials did not. Keep that in mind as you read the rest.

The isolation ladder

These are the compute isolation options, ordered from weakest to strongest. The mental model: containers isolate namespaces, gVisor isolates the kernel API, microVMs isolate the kernel itself. Choose the lowest rung you can operate, then stack the higher ones on top as defence in depth.

Language-level and WASM sandboxes

RestrictedPython, Pyodide, Wasmtime, Deno's permission flags, V8 isolates.

  • Pros: effectively zero startup cost, trivially embeddable, and WASM has a real capability model.
  • Cons: restriction at the Python language level is escapable and should not be treated as a security boundary. WASM confines you to what compiles to it: no arbitrary pip packages, no native libraries, no GPU.
  • Use for: formula evaluation, data formatting, browser-side analysis. Not for "run whatever the agent produced".

Hardened containers

Docker or Podman with seccomp, dropped capabilities, no-new-privileges, a read-only root filesystem, a non-root user, cgroup limits and --network none.

  • Pros: works everywhere, runs any image, fast, and GPU passthrough is straightforward.
  • Cons: shares the host kernel. A kernel exploit is a full escape.
  • Use for: semi-trusted code, or as a layer inside a stronger boundary.

User-space kernels (gVisor)

gVisor's runsc intercepts syscalls in user space so the host kernel sees only a tiny surface. It is a drop-in OCI runtime and is what Modal, Beam, Cloud Run and GKE Sandbox use under the hood.

  • Pros: sub-second starts, minimal operational change, and a strong reduction in kernel attack surface.
  • Cons: syscall compatibility gaps break some tools, syscall-heavy workloads slow down noticeably, and GPU support is narrower than native.
  • Use for: the default for most code-interpreter and coding-agent workloads.

MicroVMs (Firecracker, Cloud Hypervisor, Kata Containers)

A hardware-virtualised boundary with boot times in the low hundreds of milliseconds and snapshot and restore for warm pools. Firecracker underpins Lambda, Fargate, Bedrock AgentCore, E2B and Vercel Sandbox.

  • Pros: kernel-level separation, fast enough for per-session VMs, and Kata lets you keep using OCI images.
  • Cons: needs KVM (bare metal or nested virtualisation), more operational weight, GPU passthrough is doable but fiddly, and the hypervisor is still attack surface.
  • Use for: adversarial or multi-tenant workloads where you cannot accept a shared kernel.

Full VMs and dedicated hosts

One VM per tenant or per session, with an autoscaling pool.

  • Pros: the strongest practical boundary, native GPUs, no compatibility surprises.
  • Cons: tens of seconds to start, expensive, and needs pooling to be usable.
  • Use for: GPU-heavy or regulated workloads.

Managed sandbox services

E2B, Modal Sandboxes, Daytona, Vercel Sandbox, Northflank, Beam, plus the cloud-native offerings covered below.

  • Pros: an SDK that does create, exec and destroy for you, warm pools, and nothing to run.
  • Cons: your data leaves your VPC unless the vendor supports bring-your-own-cloud, per-second billing adds up, some impose session limits, and you inherit the vendor's defaults for network and identity.
OptionBoundaryStart timeBest for
WASM and language sandboxesRuntimeInstantFormulae, formatting, browser-side analysis
Hardened containersNamespaces, shared kernelSub-secondSemi-trusted code, inner layer
gVisorUser-space kernelSub-secondDefault for code interpreters and coding agents
MicroVMsHardware virtualisationHundreds of millisecondsAdversarial and multi-tenant workloads
Full VMsHardware virtualisationTens of secondsGPU-heavy or regulated workloads

Doing it on Google Cloud

Cloud Run Sandboxes are the simplest option if you are already on Cloud Run. A sandbox binary ships inside the service; sandbox do -- python3 script.py creates a fresh sandbox, runs the command and tears it down. The CLI can also keep a sandbox alive across a multi-step agent loop.

GKE Agent Sandbox adds Kubernetes custom resources (Sandbox, SandboxTemplate, SandboxWarmPool) on top of gVisor. Templates require the gVisor runtime class, a memory limit and dropping all capabilities, and warm pools hand out environments in under a second. There is no extra charge beyond the GKE resources. Pick this when sandboxes need to live next to your other Kubernetes workloads or need GPUs.

GKE Sandbox without the custom resources is the same runtime with runtimeClassName: gvisor on a node pool. Simpler if you do not need the lifecycle API.

Self-managed microVMs are possible because Compute Engine supports nested virtualisation, but you will be building your own Firecracker or Kata fleet.

Bring-your-own-cloud vendors such as Northflank and Beam will deploy their control plane into your Google Cloud project, giving you the SDK experience with data staying in your VPC.

Doing it on AWS

Bedrock AgentCore Code Interpreter is the managed option: Python, JavaScript and TypeScript in Firecracker microVMs with three network modes (Sandbox, Public and VPC). Read the fine print. Security researchers have demonstrated that Sandbox mode still allows DNS queries, which is enough for command-and-control and exfiltration, and that the microVM metadata service exposes the interpreter's IAM execution role. AWS classified both as intended behaviour and updated its documentation to recommend VPC mode for full traffic control. Run it in VPC mode behind an egress firewall with DNS filtering, and give the execution role as close to zero permissions as you can.

Lambda is already a Firecracker microVM per invocation. Fifteen-minute cap, no persistent state, no GPU. Good for short, stateless execution.

Fargate gives the same microVM isolation with longer-lived tasks and custom images. Put it in a private subnet with no NAT gateway for fully offline execution.

EC2 with Firecracker or Kata on .metal instances gives you KVM for a self-managed microVM pool, typically via firecracker-containerd or Kata on EKS. Most control, most work.

EKS with gVisor means installing runsc on a node group and using a RuntimeClass. Same trade-offs as GKE Sandbox, except you maintain it.

E2B, Modal and Vercel Sandbox all run on AWS. E2B's bring-your-own-cloud option is AWS-only and enterprise-tier.

Doing it locally

  • Docker or Podman, hardened. A non-root user, tmpfs for /tmp, and the flags below. Rootless Podman adds a second layer.
docker run --network none --read-only --cap-drop ALL \
  --security-opt no-new-privileges --pids-limit 256 \
  --memory 2g --cpus 2 --tmpfs /tmp --user 1000:1000 \
  your-image python3 script.py
  • gVisor. Install runsc, register it as a Docker runtime, then docker run --runtime=runsc. Linux only, but the best isolation per unit of effort you will get on a laptop.
  • Firecracker or Kata. Linux with /dev/kvm; use firecracker-containerd, firectl or Kata with containerd. Ideal for prototyping a warm-pool design that mirrors production.
  • macOS. No KVM, but Docker Desktop, OrbStack and Colima already run containers inside a Linux VM, so the VM is your boundary. For per-session VMs, use Apple's Virtualization framework via Tart or Lima.
  • Windows. Windows Sandbox for desktop-agent and GUI use cases; WSL2 plus Docker with gVisor for code execution.
  • Pure WASM. Pyodide in a browser tab or Wasmtime on the CLI when you want zero infrastructure for Python data work.

Non-negotiables regardless of layer

The AgentCore findings are the pattern to internalise: compute isolation held, network and identity did not.

  1. Deny egress by default. If the agent needs the internet, route it through an allowlisting proxy and filter DNS explicitly, not with string matching.
  2. Block metadata endpoints (169.254.169.254 and friends) at the network layer.
  3. Give the sandbox no cloud identity if you can. If it must have one, scope it to the single bucket or table it needs and use short-lived tokens.
  4. Mount inputs read-only, write outputs to a scratch volume you copy out, and destroy the sandbox after every session.
  5. Set hard limits on CPU, memory, PIDs, disk and wall-clock time.
  6. Log every tool call with its arguments, and treat the sandbox as a hostile process in your SIEM.

Securing agents inside AI engineering

Coding agents (Claude Code, Cursor, Copilot's agent mode, Devin-style tools) and the MCP servers that extend them sit in a fundamentally different position from a code interpreter. They run on developer machines or in cloud development environments, they read your entire repository, and they typically hold the developer's own credentials. The sandbox question here is less "can the code escape" and more "what can the agent be talked into doing".

Treat repository content as untrusted input

A README, a code comment, a test fixture, an issue title, a dependency's documentation: anything the agent reads can carry instructions. Attacks that embed hidden text in a pull request description to make a reviewing agent approve it, exfiltrate secrets or run a command are well documented. Assume every file and every fetched page is adversarial, and design so that a successful injection can only do bounded damage.

Run the agent in its own environment, not your shell

Use a devcontainer or a throwaway VM for agent sessions. The agent gets a copy of the repository, a scoped Git credential, and nothing else. Your SSH keys, cloud CLI sessions, browser cookies and ~/.aws never enter that environment. Cloud development environments (Codespaces, Gitpod, Coder, GKE Agent Sandbox's computer-use runtime) make this the default. On a laptop, a gVisor-backed devcontainer with network access restricted to your package registries and Git host gets you most of the way.

Permission the tools, not just the model

Agents act through tools: shell, file edit, HTTP fetch, MCP servers. Each tool should have an explicit allowlist and a policy for what needs human confirmation. Read-only operations can auto-run. Anything that writes outside the workspace, sends network traffic, installs packages or touches git push should require approval, at least until you have telemetry showing the agent behaves. Most agent frameworks support this; the mistake is leaving it in "allow all" mode after the first week.

Audit MCP servers like any other dependency

An MCP server is arbitrary code running with whatever credentials you gave it, receiving arbitrary prompts from a model that reads arbitrary content. Before wiring one in: read its source or pin a reviewed version, run it with its own least-privilege credential (not your personal token), keep it network-isolated where possible, and log everything it is asked to do. A community MCP server that "just needs" your full GitHub token is a supply-chain risk, not a productivity tool.

Give agents their own identity

Do not let an agent operate as you. Create non-human identities (a GitHub App, a dedicated service account, a scoped API key) with permissions limited to the repositories and resources the agent needs, short expiry, and clear attribution in audit logs. When something goes wrong, you want to be able to say "the agent did this" rather than "someone with Alice's credentials did this".

Scan agent-written code harder, not softer

Agent-generated code is high volume and plausible-looking, which is the worst combination for review fatigue. Run SAST, secret scanning and dependency checks on every agent commit. Watch specifically for hallucinated package names: models regularly invent plausible dependencies, and attackers register those names on PyPI and npm. Enforce lockfiles and private registry mirrors so a made-up package fails to resolve rather than resolving to malware.

Securing agents in the CI/CD pipeline

CI is where agents are most dangerous, because CI is where secrets, deploy keys and production access are concentrated, and because CI runs on content that outsiders can influence. An agent in a GitHub Actions job that summarises pull requests is one crafted PR title away from being an attacker's shell.

Never let untrusted content meet privileged tokens

The classic "pwn request" pattern predates AI agents: a workflow triggered by pull_request_target or issue_comment runs with a write-capable GITHUB_TOKEN while processing attacker-controlled text. Adding an LLM that reads that text and can call tools makes it dramatically easier to exploit. The rules:

  • Agent jobs that read PR, issue or commit content run with a read-only token and no repository secrets.
  • Any job that holds write access, deploy credentials or cloud OIDC roles never invokes a model on untrusted input.
  • Split the workflow: an unprivileged agent job produces an artifact (a review, a patch, a plan); a separate privileged job, gated on human approval or strict validation, acts on it.

Short-lived, scoped credentials only

Use OIDC federation to your cloud rather than long-lived keys in secrets. Scope the assumed role to the specific job. Set permissions: explicitly at the job level in GitHub Actions, defaulting to contents: read and adding only what is needed. Rotate anything an agent job could conceivably have read.

Isolate the runners

Run agent jobs on ephemeral runners, one job per VM, destroyed afterwards. Self-hosted persistent runners accumulate state and credentials and are a favourite lateral-movement target. If you need self-hosted runners for GPU or network reasons, back them with Firecracker or a fresh VM per job.

An agent must not be able to merge its own work

Branch protection should require review from a human who is not the agent's identity. Required status checks should include SAST, secret scanning and dependency review. Agent-authored PRs get a label and a mandatory reviewer, and CODEOWNERS should route them to someone who understands the affected area. The agent can open, update and comment; it cannot approve or merge.

Pin everything

Pin third-party actions by commit SHA, not by tag. Pin the model version and record it in the workflow so an unannounced model update cannot change behaviour silently. Keep system prompts and tool definitions in the repository under review, not in a dashboard someone edits on a Friday afternoon.

Restrict egress from CI

Agent jobs should reach the model API, your package registries and your Git host, and nothing else. Most CI platforms let you enforce this at the runner network level. Do it there, not in the agent configuration where a prompt injection could disable it.

Gate on evaluations

Treat prompts, tool schemas and agent configurations as code with tests. Maintain a small suite of adversarial cases (injected instructions in a PR body, a malicious package name, a request to print environment variables) and fail the pipeline if the agent takes the bait. This is cheap and catches regressions when a model or prompt changes.

Budget and kill switch

Set a hard cap on tokens, tool calls and wall-clock time per job. An agent stuck in a loop, or one being driven by an attacker, should hit a limit before it hits your bill or your database. Have a single feature flag that disables all agent jobs organisation-wide, and rehearse using it.

Log tool calls, not just outcomes

A green tick tells you nothing about what the agent did to get there. Record every tool invocation with arguments and results, ship it to the same place as your other audit logs, and alert on anything touching secrets, network calls outside the allowlist, or writes outside the workspace.

A checklist you can paste into a ticket

Compute isolation

  • AI-generated code runs in gVisor or a microVM, never directly on the host
  • Sandboxes are ephemeral and destroyed after each session
  • Hard limits on CPU, memory, PIDs, disk and time

Network

  • Egress denied by default, with an allowlisting proxy for exceptions
  • DNS filtered and metadata endpoints blocked at the network layer

Identity

  • Agents have their own non-human identities with least privilege
  • Sandboxes hold no cloud credentials, or only short-lived, single-purpose ones
  • Developers' personal credentials never enter the agent environment

Engineering workflow

  • Coding agents run in devcontainers or throwaway VMs
  • Tool allowlists with human confirmation for writes, network, installs and push
  • MCP servers reviewed, pinned and given their own scoped credentials
  • Lockfiles and private registry mirrors enforced; SAST and secret scanning on agent commits

CI/CD

  • Agent jobs on untrusted input run with read-only tokens and no secrets
  • Privileged jobs never invoke a model on untrusted input
  • Ephemeral runners, one job per VM
  • Agents cannot approve or merge their own PRs; a human reviewer is required
  • Actions pinned by SHA, model version pinned, prompts under version control
  • CI egress restricted; an adversarial evaluation suite gates the pipeline
  • Token, tool-call and time budgets per job; organisation-wide kill switch tested
  • Every tool call logged and alertable

Closing thought

The sandbox is the easy part. gVisor and Firecracker are mature, the cloud providers now ship purpose-built agent runtimes, and a hardened container on a laptop is ten minutes of work. What separates teams that get burned from teams that don't is the discipline around what the agent can reach: which credentials, which networks, which parts of the pipeline. Start by assuming every agent will eventually be tricked into doing something you did not intend, and build so that when it happens, the blast radius is a scratch volume and a log entry.

If you are deciding where to start: sandbox the code interpreter this week, split your CI agent jobs from your privileged jobs next week, and get agents onto their own identities the week after. Everything else compounds from there.

Where Orbit3 fits

We build and run AI agents and LLM applications for clients, and the isolation, identity and pipeline controls above are how we build them. If you already have agents in your engineering workflow, our cloud security team will review where they run, what they can reach and how they are permissioned, and hand you a prioritised fix list. Where the gaps are in the pipeline itself, our cloud DevOps service puts the runner isolation, OIDC credentials and egress controls in place.

If you are not sure how exposed your agents are today, get in touch. A free 30-minute call is usually enough to tell you whether the risk is in the sandbox, the network or the credentials, and what to fix first.

About the author

Martin is the founder of Orbit3, a managed cloud services and AI consultancy. He works directly with every client on cloud operations, security and compliance. More about Orbit3.

Questions we get asked

Frequently asked questions

Is a Docker container enough to sandbox AI-generated code?

Not on its own. A container shares the host kernel, so a kernel exploit is a full escape. Hardened containers are a good inner layer, but for code an agent wrote you want gVisor or a microVM such as Firecracker as the boundary, with egress denied by default and no cloud credentials inside.

What is the difference between gVisor and Firecracker?

gVisor is a user-space kernel that intercepts syscalls so the host kernel sees a tiny surface. It is a drop-in container runtime with sub-second starts. Firecracker is a microVM: a hardware-virtualised boundary with its own kernel, boot times in the low hundreds of milliseconds, and snapshot support for warm pools. gVisor is the easier default; Firecracker is the stronger boundary for adversarial or multi-tenant workloads.

Does sandboxing stop prompt injection?

No. Prompt injection makes the model follow instructions embedded in content it reads, and the model is behaving as designed. Compute isolation does not help. The controls are egress restriction, tool permissioning with human confirmation for risky actions, least-privilege identities, and human review before anything irreversible.

How should coding agents like Claude Code or Cursor be run safely?

In their own environment, not your shell: a devcontainer or throwaway VM with a copy of the repository, a scoped Git credential and nothing else. Give the agent its own non-human identity, allowlist its tools with confirmation for writes, network calls, installs and pushes, and audit any MCP servers as you would any other dependency.

What is the biggest risk of running an AI agent in CI/CD?

Untrusted content meeting privileged tokens. A workflow that lets a model read pull request or issue text while holding a write-capable token or deploy credentials can be driven by anyone who can open a PR. Split the workflow so unprivileged agent jobs produce an artifact and a separate, gated privileged job acts on it, and run agent jobs on ephemeral runners with restricted egress.

Get started

Running agents that can execute code?

Book a free 30-minute call. We'll look at where your agents run, what they can reach and what they hold, and tell you what to fix first.