
Product
PHP and Composer Support Is Now in Beta
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.
@vercel/sandbox
Advanced tools
Vercel Sandbox allows you to run arbitrary code in isolated, ephemeral Linux VMs. View the documentation here.
@vercel/sandbox (this package) - The SDK for programmatic access to Vercel Sandbox. Source | Documentationsandbox - The CLI for interacting with Vercel Sandbox from the command line. Source | DocumentationA sandbox is an isolated Linux system for your experimentation and use. Internally, it is a Firecracker MicroVM that is powered by the same infrastructure that powers 2M+ builds a day at Vercel.
To get started using Ubuntu with Node.js 24, create a new project:
mkdir my-sandbox-app && cd my-sandbox-app
npm init -y
vercel link
Pull your authentication token:
vercel env pull
Install the Sandbox SDK:
pnpm i @vercel/sandbox
Install the Sandbox Skill:
npx skills add vercel/sandbox
Create a index.mts file:
import { Sandbox } from "@vercel/sandbox";
import { setTimeout } from "timers/promises";
import { spawn } from "child_process";
async function main() {
const sandbox = await Sandbox.create({
source: {
url: "https://github.com/vercel/sandbox-example-next.git",
type: "git",
},
resources: { vcpus: 4 },
ports: [3000],
name: "vercel-sandbox-example",
});
console.log(`Sandbox ${sandbox.name} created`);
console.log(`Installing dependencies...`);
const install = await sandbox.runCommand({
cmd: "npm",
args: ["install", "--loglevel", "info"],
cwd: "sandbox-example-next",
stderr: process.stderr,
stdout: process.stdout,
});
if (install.exitCode !== 0) {
console.log("installing packages failed");
process.exit(1);
}
console.log(`Starting the development server...`);
await sandbox.runCommand({
cmd: "npm",
args: ["run", "dev"],
cwd: "sandbox-example-next",
stderr: process.stderr,
stdout: process.stdout,
detached: true,
});
await setTimeout(500);
spawn("open", [sandbox.domain(3000)]);
}
main().catch(console.error);
Run it:
node --experimental-strip-types --env-file .env.local index.mts
This will:
next dev serverAll while streaming logs to your local terminal.
Sandboxes are persistent by default. To resume a sandbox with its previous state:
Create a resume.mts file:
import { Sandbox } from "@vercel/sandbox";
import { setTimeout } from "timers/promises";
import { spawn } from "child_process";
async function main() {
const sandbox = await Sandbox.get({
name: "vercel-sandbox-example",
});
console.log(`Sandbox ${sandbox.name} resumed`);
console.log(`Starting the development server...`);
await sandbox.runCommand({
cmd: "npm",
args: ["run", "dev"],
cwd: "sandbox-example-next",
stderr: process.stderr,
stdout: process.stdout,
detached: true,
});
await setTimeout(500);
spawn("open", [sandbox.domain(3000)]);
}
main().catch(console.error);
Run it:
node --experimental-strip-types --env-file .env.local resume.mts
The SDK uses Vercel OIDC tokens to authenticate whenever available. This is the most straightforward and recommended way to authenticate.
When developing locally, you can download a development token to .env.local
using vercel env pull. After 12 hours the development token expires, meaning
you will have to call vercel env pull again.
In production, Vercel manages token expiration for you.
If you want to use the SDK from an environment where VERCEL_OIDC_TOKEN is
unavailable, you can also authenticate using an access token:
Set your team ID, project ID, and token to the environment variables
VERCEL_TEAM_ID, VERCEL_PROJECT_ID, and VERCEL_TOKEN. Then pass these to
the create method:
const sandbox = await Sandbox.create({
teamId: process.env.VERCEL_TEAM_ID!,
projectId: process.env.VERCEL_PROJECT_ID!,
token: process.env.VERCEL_TOKEN!,
source: {
url: "https://github.com/vercel/sandbox-example-next.git",
type: "git",
},
resources: { vcpus: 4 },
// Defaults to 5 minutes. The maximum is 24 hours for Pro/Enterprise, and 45 minutes for Hobby.
timeout: ms("5m"),
ports: [3000],
});
Sandbox and CommandFinished support serialization with the
Workflow DevKit. When a sandbox instance
crosses a step boundary the SDK serializes sandbox metadata and routes, then
rehydrates synchronously from that snapshot. Deserialized instances lazily
recreate an API client using OIDC or environment credentials when needed.
timeout option of Sandbox.create().Sandboxes use
vercel/sandbox/universal:latest
by default. This Ubuntu-based image includes Node.js 24, Bun, Python 3.14,
coding agents, and common development and debugging utilities. It runs as the
ubuntu user with passwordless sudo.
Vercel provides several public images optimized to use in Sandbox. The
Dockerfiles for Vercel Managed Images published under vercel/sandbox/* live
in the images/
directory:
vercel/sandbox/universal:latest: Default image with Node.js, Python, coding agents, and utilities.vercel/sandbox/node:22|24|26: Node.js with pnpm.vercel/sandbox/python:3.14: Python with pip, venv, and uv.vercel/sandbox/ubuntu:latest: Minimal Ubuntu base.vercel/sandbox/arch:latest: Arch Linux with yay/AUR support.See the images README for build instructions.
A sandbox can boot from any OCI image by pushing it to
Vercel Container Registry (VCR) and passing image to
Sandbox.create().
Build and push a linux/amd64 image to VCR:
vercel vcr login docker
vercel vcr build docker . my-repository:latest --push
The CLI uses the linked project, defaults to linux/amd64, and constructs the
full VCR reference automatically.
VCR implements the Docker Registry API, so any OCI compatible tooling can also
be used, such as buildah or podman.
Then start a sandbox from it:
import { Sandbox } from "@vercel/sandbox";
const sandbox = await Sandbox.create({
image: "my-repository:latest",
});
The image option accepts a repository in the sandbox's project, with an
optional tag or digest. A bare repository name resolves to the latest tag.
You can also pass a fully-qualified VCR URL:
await Sandbox.create({ image: "my-repo" }); // latest tag
await Sandbox.create({ image: "my-repo:v1" }); // specific tag
await Sandbox.create({ image: "my-repo@sha256:..." }); // specific digest
await Sandbox.create({
image: "vcr.vercel.com/my-team/my-project/my-repo:v1", // fully-qualified
});
See the images documentation for more details.
The default image allows users to run commands as root. This can be used to install packages and system tools:
import { Sandbox } from "@vercel/sandbox";
const sandbox = await Sandbox.create();
await sandbox.runCommand({
cmd: "apt-get",
args: ["update"],
sudo: true,
});
await sandbox.runCommand({
cmd: "apt-get",
args: ["install", "-y", "golang-go"],
sudo: true,
});
Sandbox runs sudo in the following configuration:
HOME is set to /root – Executed commands will source root's configuration
files (e.g. .gitconfig, .bashrc, etc).PATH is left unchanged – sudo won't change the value of PATH, so local or
project-specific binaries will still be found.Sandboxes support creating isolated Linux users with their own home directories, file permissions, and process ownership. This is useful for multi-agent workflows where each agent needs its own workspace, or for simulating multi-user environments.
Note: The sandbox image must have
/bin/bashinstalled. It is the login shell for created users and is used to wrap commands that run as a user. The Vercel managed images include it.
import { Sandbox } from "@vercel/sandbox";
const sandbox = await Sandbox.create();
// Creates /home/alice with isolated permissions
const alice = await sandbox.createUser("alice");
alice.username; // "alice"
alice.homeDir; // "/home/alice"
createUser sets up:
/bin/bash as the default shell/home/<username> group-owned by the sandbox's default user group with 770 permissionsAll commands run as the user by default, with the working directory set to their home:
const alice = await sandbox.createUser("alice");
const whoami = await alice.runCommand("whoami");
await whoami.stdout(); // "alice\n"
const pwd = await alice.runCommand("pwd");
await pwd.stdout(); // "/home/alice\n"
You can pass environment variables, override the working directory, or use the
full RunCommandParams interface:
// Environment variables
await alice.runCommand({
cmd: "node",
args: ["-e", "console.log(process.env.API_KEY)"],
env: { API_KEY: "secret" },
});
// Custom working directory
await alice.runCommand({ cmd: "ls", cwd: "/tmp" });
// Detached mode for long-running processes
const server = await alice.runCommand({
cmd: "node",
args: ["server.js"],
detached: true,
});
To escalate to root, pass sudo: true:
await alice.runCommand({
cmd: "apt-get",
args: ["update"],
sudo: true,
});
await alice.runCommand({
cmd: "apt-get",
args: ["install", "-y", "git"],
sudo: true,
});
writeFiles, readFile, readFileToBuffer, and mkDir all resolve relative
paths against the user's home directory. Written files are owned by the user:
const alice = await sandbox.createUser("alice");
// Writes to /home/alice/app.js, owned by alice:alice
await alice.writeFiles([
{ path: "app.js", content: Buffer.from('console.log("hi")') },
]);
// Read it back
const buf = await alice.readFileToBuffer({ path: "app.js" });
buf?.toString(); // 'console.log("hi")'
// Stream reads
const stream = await alice.readFile({ path: "app.js" });
// Create directories owned by the user
await alice.mkDir("projects/my-app");
// Absolute paths also work
await alice.writeFiles([
{ path: "/tmp/output.txt", content: Buffer.from("data") },
]);
Users cannot access each other's home directories:
const alice = await sandbox.createUser("alice");
const bob = await sandbox.createUser("bob");
await alice.writeFiles([
{ path: "secret.txt", content: Buffer.from("alice only") },
]);
// Bob cannot read, list, or write to alice's home
const cat = await bob.runCommand({
cmd: "cat",
args: ["/home/alice/secret.txt"],
});
cat.exitCode; // non-zero — Permission denied
The SDK can read all users' files because home directories are group-owned
by the sandbox's default user group. Both SandboxUser methods and direct
sandbox methods work:
// Via SandboxUser (relative paths resolve to home dir)
const buf = await alice.readFileToBuffer({ path: "secret.txt" });
buf?.toString(); // "alice only"
// Via sandbox directly (absolute path required)
const buf2 = await sandbox.readFileToBuffer({ path: "/home/alice/secret.txt" });
buf2?.toString(); // "alice only"
Create groups to let users collaborate through a shared directory:
const devs = await sandbox.createGroup("devs");
devs.sharedDir; // "/shared/devs"
await sandbox.addUserToGroup("alice", "devs");
await sandbox.addUserToGroup("bob", "devs");
// Alice writes to the shared directory
await alice.runCommand({
cmd: "bash",
args: ["-c", 'echo "spec v2" > /shared/devs/spec.txt'],
});
// Bob can read it — files inherit group ownership via setgid
const spec = await bob.runCommand({
cmd: "cat",
args: ["/shared/devs/spec.txt"],
});
await spec.stdout(); // "spec v2\n"
// Non-members are blocked
const charlie = await sandbox.createUser("charlie");
const ls = await charlie.runCommand({ cmd: "ls", args: ["/shared/devs"] });
ls.exitCode; // non-zero — Permission denied
Shared directories use setgid (2770), so files created inside them
automatically inherit the group. All group members get read/write access.
Convenience methods are available on SandboxUser:
await alice.addToGroup("devs");
await alice.removeFromGroup("devs");
asUser for existing usersIf a user already exists (e.g., from a snapshot or manual creation), use
asUser to get a handle without re-creating:
const existing = sandbox.asUser("bob");
await existing.runCommand("whoami"); // "bob"
Usernames and group names must match /^[a-z_][a-z0-9_-]*$/ and be at most 32
characters. Invalid names throw an error immediately:
sandbox.asUser("Alice"); // throws — uppercase
sandbox.asUser("user name"); // throws — space
sandbox.asUser("$(whoami)"); // throws — special characters
sandbox.asUser("a".repeat(33)); // throws — too long
const sandbox = await Sandbox.create();
// Each agent gets its own isolated workspace
const researcher = await sandbox.createUser("researcher");
const coder = await sandbox.createUser("coder");
const reviewer = await sandbox.createUser("reviewer");
// Shared workspace for collaboration
await sandbox.createGroup("project");
await sandbox.addUserToGroup("researcher", "project");
await sandbox.addUserToGroup("coder", "project");
await sandbox.addUserToGroup("reviewer", "project");
// Researcher writes findings to shared dir
await researcher.runCommand({
cmd: "bash",
args: ["-c", 'echo "API spec v2" > /shared/project/spec.txt'],
});
// Coder reads spec, writes code in their own home
const spec = await coder.runCommand({
cmd: "cat",
args: ["/shared/project/spec.txt"],
});
await coder.writeFiles([
{ path: "app.js", content: Buffer.from(`// ${await spec.stdout()}`) },
]);
// Reviewer can read the shared spec but not coder's private files
const blocked = await reviewer.runCommand({
cmd: "cat",
args: ["/home/coder/app.js"],
});
blocked.exitCode; // non-zero — isolation enforced
This library is created by Vercel team members, with contributions from the Open Source Community welcome and highly appreciated.
FAQs
Software Development Kit for Vercel Sandbox
The npm package @vercel/sandbox receives a total of 3,848,657 weekly downloads. As such, @vercel/sandbox popularity was classified as popular.
We found that @vercel/sandbox demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.

Product
Socket’s PHP and Composer support is now in Beta for all customers, with PHP reachability analysis generally available.

Product
Socket is bringing experimental protection to Firefox, scanning 97,000+ extensions in Mozilla's official directory for malware and risky updates.

Research
/Security News
Three compromised Rust crates pulled in a malicious dependency that downloaded and executed cross-platform malware during Cargo builds.