Overview
Run untrusted or AI-generated code in an isolated environment, created per session at runtime.
A Sandbox runs code you do not trust — generated by a model, submitted by a user, pulled from a pull request — in an environment isolated from your application, your customer's cloud, and every other sandbox. Your app creates one per agent turn if it wants, runs commands in it, moves files in and out, and throws it away.
Unlike most Alien resources, a Sandbox is not one thing that exists for the life of your stack. The declaration provisions a durable parent; sessions are created and destroyed at runtime through the binding.
Platform Mapping
| Platform | Runtime | Isolation | Provisioned by |
|---|---|---|---|
| AWS | Lambda MicroVMs | Firecracker, VM-level | Alien |
| GCP | Cloud Run sandboxes | Not published by Google | Alien |
| Azure | Container Apps Sandboxes | Hyper-V, VM-level | Alien |
| Kubernetes / On-Prem | Pod under a sandboxed runtimeClassName | gVisor or Kata | Alien, on your cluster |
| Local | Docker container | Shared kernel | Alien local runtime |
Azure and GCP are in public preview upstream. Kubernetes needs a sandboxed runtime class on the cluster, and refuses to provision without one. See Behavior & Limits.
When to Use
Use Sandbox to execute code your application did not write — an agent's generated script, a customer's build step, a submitted notebook cell — where a crash, a fork bomb, or an attempt to read the filesystem must not reach your service.
Use Container or Worker for code you control; both are simpler and cheaper. Don't reach for a Sandbox when you need a durable filesystem — sessions are disposable, so keep state outside. And Local shares a kernel with the host, so it is development-only for untrusted code unless gVisor or Kata is present.
Capabilities differ by platform
Check this before you build. Create, exec and terminate work everywhere; everything else varies, and calling an unsupported capability returns a typed error naming the platform and the capability — never a silent no-op.
| Capability | AWS | Azure | GCP | Kubernetes | Local |
|---|---|---|---|---|---|
files — move files in and out | yes | no | yes | yes | yes |
reconnect — reach a session again | yes | yes | no | yes | yes |
preview — authenticated ingress | yes | no | no | no | yes |
suspendResume | yes | no | no | no | no |
snapshot | no | no | no | no | no |
egressDeny — deny is enforced | yes | no | yes | yes | yes |
domainEgressRules — hostname allowlist | no | no | no | no | no |
enforcedLimits — cpu / memory / disk | yes | no | no | yes | yes |
processLimit | no | no | no | no | yes |
sessionLifetime — platform deadline | yes | no | no | yes | no |
supervisorPidNamespace | no | no | no | no | no |
preview and snapshot are Rust-only: the TypeScript binding exposes no method for either, so
they never appear in the list capabilities() returns there.
File transfer is the one to check first, because it is the only floor operation a platform lacks: Alien's Azure binding does not implement it. Pass what the session needs on the command line there, or pick another platform.
A declaration a platform cannot honour is rejected when you deploy, not ignored at runtime. So
maxProcesses is accepted on Local alone, and maxLifetimeSeconds on AWS and Kubernetes. Leave
them out unless you are targeting a platform that applies them.
const caps = await box.capabilities()
if (caps.includes("reconnect")) {
// multi-turn: reuse the session across turns
} else {
// single-turn: finish inside one turn, or keep state outside the sandbox
}Quick Start
Declare the sandbox in your stack:
import * as alien from "@alienplatform/core"
const agent = new alien.Sandbox("agent")
.code({ type: "image", image: "ubuntu:24.04" })
.limits({ cpu: "1", memory: "2Gi", disk: "20Gi" })
.egress({ mode: "deny" })
.session({})
.build()
export default new alien.Stack("app")
.add(agent, "live")
.build()That declaration targets AWS, Kubernetes and Local: .limits(...) needs enforcedLimits and
deny needs egressDeny, and Azure has neither while GCP lacks the first. Drop both to deploy
everywhere, and see Configuration for which field needs which capability.
Then drive sessions from your application:
import { sandbox } from "@alienplatform/sdk"
const box = sandbox("agent")
const session = await box.create({ sessionId: "turn-1" })
for await (const frame of box.runCommand(
session.sessionId,
["python3", "-c", "print(2 + 2)"],
{ deadlineMs: 30_000 },
)) {
if (frame.kind === "stdout") process.stdout.write(frame.data)
if (frame.kind === "exit") console.log("exit", frame.exitCode)
}
await box.terminate(session.sessionId)Every method is in the API Reference.
Configuration
| Method | Type | Required | Description |
|---|---|---|---|
.code(...) | { type: "image", image } | Yes | A prebuilt image reference. Building from source is not supported on any backend yet. Not applied on Azure — see Behavior & Limits. |
.egress(...) | { mode: "deny" | "allow" } | Yes | Outbound network policy. allowDomains is refused on every platform. |
.session(...) | { idleSuspendSeconds?, maxLifetimeSeconds? } | Yes | Required, but both fields are optional — .session({}) deploys anywhere. idleSuspendSeconds needs suspendResume (AWS only); maxLifetimeSeconds needs sessionLifetime (AWS and Kubernetes). |
.limits(...) | { cpu, memory, disk, maxProcesses? } | No | Enforced ceilings. Needs enforcedLimits; maxProcesses needs processLimit. |
.previewPorts(...) | number[] | No | Ports eligible for a preview capability. A port not listed here can never be exposed, so an application cannot widen its own ingress. |
Omitting .limits(...) takes the platform's defaults. Naming it on a platform that cannot enforce
it is rejected at plan time rather than accepted and ignored.
Two things that bite if you skim
GCP sandboxes are single-turn. A session id is scoped to one Cloud Run instance, and session affinity was measured keeping 2 of 100 five-turn conversations. That is the absence of a reconnect guarantee, not a weak one. If your agent needs the same sandbox across turns, GCP is the wrong platform — see Behavior & Limits.
Limits are ceilings, not requests. The platform enforces them and your stack is validated against them when it is planned, which is why a GCP Sandbox declaring limits fails at plan time.