Skip to main content

vercel-sandbox

Vercel Sandbox guidance — ephemeral Firecracker microVMs for running untrusted code safely. Supports AI agents, code generation, and experimentation. Use when executing user-generated or AI-generated code in isolation.

معلومات المصدر

المستودع
vercel/vercel-plugin
آخر نشاط في المصدر
٦ أكتوبر ٢٠٢٦ في ٠٨:٣٢
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٣٠١
التفرعات
٦٤

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
4 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
vercel-sandbox
description
Vercel Sandbox guidance — ephemeral Firecracker microVMs for running untrusted code safely. Supports AI agents, code generation, and experimentation. Use when executing user-generated or AI-generated code in isolation.
summary
Run untrusted/AI-generated code in ephemeral Firecracker microVMs via @vercel/sandbox. Core loop: `const s = await Sandbox.create(); try { const r = await s.runCommand('python3', ['-c', code]); } finally { await s.stop(); }`. runCommand has no shell (wrap pipes/redirects in `bash -c`) and does not throw on non-zero exit (check r.exitCode). Default image is Ubuntu (`apt-get update` before install). Persistence is on by default (auto-snapshot on stop, resume by name; only the filesystem survives). For untrusted code use `networkPolicy: 'deny-all'`, a short `timeout`, and `persistent: false`. Credential brokering: a firewall `transform` injects a secret header on egress so the VM never holds it. AI agents reach models with no API key via the AI Gateway (`https://ai-gateway.vercel.sh`, `Authorization: Bearer $VERCEL_OIDC_TOKEN`) — the token is not auto-injected, so pass it via `env` or broker it. Full docs: https://vercel.com/docs/sandbox
metadata
{"priority":4,"docs":["https://vercel.com/docs/sandbox"],"sitemap":"https://vercel.com/sitemap.xml","pathPatterns":[],"importPatterns":["@vercel/sandbox"],"bashPatterns":["\\bnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/sandbox\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/sandbox\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@vercel/sandbox\\b","\\byarn\\s+add\\s+[^\\n]*@vercel/sandbox\\b"],"promptSignals":{"phrases":["@vercel/sandbox","sandbox","code sandbox","vercel sandbox","isolated environment","sandboxed execution"],"allOf":["[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]"],"anyOf":["sandbox","isolated","isolation","untrusted","safely","microvm","ffmpeg","playground"],"noneOf":["iframe sandbox","sandbox attribute","codesandbox.io","stackblitz"],"minScore":4}}
retrieval
{"aliases":["code sandbox","microvm","isolated execution","safe code runner"],"intents":["run untrusted code","execute code safely","create sandbox","isolate code execution"],"entities":["Vercel Sandbox","Firecracker","microVM","isolated execution"]}
chainTo
[{"pattern":"from\\s+['\"\"]vm2['\"\"]|require\\s*\\(\\s*['\"\"]vm2['\"\"\\)]|new\\s+VM\\(","targetSkill":"vercel-sandbox","message":"vm2 detected — it has known security vulnerabilities. Reloading Vercel Sandbox guidance for Firecracker microVM-based safe execution."},{"pattern":"child_process.*exec\\(|execSync\\(|spawn\\(.*\\{.*shell:\\s*true","targetSkill":"ai-sdk","message":"Shell exec for code execution detected — loading AI SDK guidance for tool-calling patterns that pair with Vercel Sandbox for safe agent execution."}]
# Vercel Sandbox Vercel Sandbox runs untrusted or AI-generated code inside an ephemeral Firecracker microVM. You get a real Linux VM with a filesystem and network — created on demand over an API, and stopped (or snapshotted) when you're done. Reach for it when code you don't fully trust needs to run: AI agent tool calls, code generation, user submissions, builds, or experiments. Do **not** use in-process sandboxes like `vm2` (known escapes) or `child_process`/`eval` for untrusted code. Those share your process; a Sandbox is a separate VM. ## Install ```bash pnpm add @vercel/sandbox # or npm i / yarn add / bun add ``` There is also a Python SDK (`vercel` package, `vercel.sandbox`) and a `sandbox` CLI. This skill shows the JS SDK unless noted. ## Minimal example The core loop is create → run → stop. For one-off work, stop in a `finally` so a thrown error can't leak a running VM (you're billed while it runs). `stop()` is safe to call more than once. ```ts import { Sandbox } from "@vercel/sandbox"; const sandbox = await Sandbox.create(); try { const result = await sandbox.runCommand("python3", ["-c", "print(2 + 2)"]); console.log(await result.stdout()); // "4\n" console.log(result.exitCode); // 0 } finally { await sandbox.stop(); } ``` `Sandbox.create()` with no arguments boots the default image (`vercel/sandbox/universal`, Ubuntu with Node.js 24, Python 3.14 as `python3`, and common tools), 2 vCPUs, and a 5-minute timeout. ## Authentication - **On Vercel** (Functions, Cron, builds): the SDK authenticates automatically via the deployment's OIDC token. No config. - **Local dev**: run `vercel link` then `vercel env pull` to get a `VERCEL_OIDC_TOKEN` in `.env.local` (valid ~12h; re-pull when it expires). - **External / CI** (no OIDC available): set `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID` and pass them as `token`, `teamId`, `projectId` to `Sandbox.create()` (the SDK does not read them from the environment). This is auth for the process **calling** the SDK. It is separate from any credential you want available **inside** the VM — the sandbox does not automatically carry your `VERCEL_OIDC_TOKEN` (see [Running AI agents](#running-ai-agents-in-a-sandbox)). ## Creating a sandbox Common `Sandbox.create()` options (all optional): | Option | Default | Notes | |---|---|---| | `image` | `vercel/sandbox/universal` | Managed image, or a custom/public VCR image. See [Images](#images). | | `resources` | `{ vcpus: 2 }` | `vcpus` can be `1` or an even number up to the plan max (Hobby 4, Pro 8, Enterprise 32). Each vCPU includes 2 GB RAM. Use `1` for cheap, low-intensity untrusted runs. | | `timeout` | `300_000` (5 min) | Session timeout in ms. When it elapses the session is stopped and any in-flight `runCommand` **rejects**. Extend with `sandbox.extendTimeout(ms)` up to the plan max session (Hobby 45 min, Pro/Ent 24h). | | `ports` | `[]` | Ports to expose, up to 15. Reach them with `sandbox.domain(port)`. Your server must listen on `0.0.0.0` (not `127.0.0.1`) to be reachable. | | `region` | project default or `iad1` | One of 19 regions. | | `persistent` | `true` | Auto-snapshots on stop and resumes on next call. Pass `false` for one-off work to avoid snapshot storage cost. | | `networkPolicy` | `"allow-all"` | Use `"deny-all"` or an allow-list for untrusted code. See [Network policy](#network-policy-and-credential-brokering). | | `env` | – | Environment variables for every command. Per-command `env` overrides these. Use this to inject a credential into the VM. | | `name` | auto-generated | Unique per project, immutable. Used to retrieve/resume a persistent sandbox. | | `tags` | – | Up to 5 key-value pairs for filtering in `Sandbox.list()`. | ## Running commands `runCommand` runs a binary directly — **there is no shell**, so pipes, redirects, `&&`, and globs do not work unless you invoke a shell yourself. It **resolves with the finished command regardless of exit code** (it does not throw on a non-zero exit); check `result.exitCode`. It only rejects on an actual failure to run — e.g. the session timing out mid-command. Commands run as a **non-root** user (`ubuntu`, in the sudo group) by default; pass `sudo: true` for root. ```ts const r = await sandbox.runCommand("npm", ["install"]); if (r.exitCode !== 0) throw new Error(await r.stderr()); // non-zero does NOT throw // Needs a shell for the redirect / pipe. Use absolute paths (see below). await sandbox.runCommand("bash", ["-c", "echo hi > /vercel/sandbox/out.txt && cat /vercel/sandbox/out.txt"]); // Root for one command (sudo is object-form only) await sandbox.runCommand({ cmd: "apt-get", args: ["update"], sudo: true }); // Long-running process: detached (object form only) returns immediately const server = await sandbox.runCommand({ cmd: "npm", args: ["run", "dev"], detached: true }); ``` `runCommand` returns a finished command with `await result.stdout()`, `await result.stderr()`, and `result.exitCode` (or a live `Command` when `detached: true`). The object form also takes `cwd`, `env`, and `stdout`/`stderr` (a `Writable` to stream into). `sudo` and `detached` are object-form only. **Working directory**: the file methods below are rooted at `/vercel/sandbox`, but do not assume a command's default working directory is the same. Whenever a command reads or writes files you created with `writeFiles`/`readFileToBuffer`, use **absolute paths under `/vercel/sandbox`** (or pass an explicit `cwd`) so both sides point at the same place. ## Files File-method paths are relative to `/vercel/sandbox` unless absolute. `content` must be a `Buffer`. ```ts await sandbox.writeFiles([ { path: "input.txt", content: Buffer.from("line one\nline two\n") }, { path: "run.sh", content: Buffer.from("#!/bin/bash\necho hi"), mode: 0o755 }, ]); // readFileToBuffer returns a Buffer, or null if the file is missing — guard it. // (readFile returns a ReadableStream; neither returns a string, so convert yourself.) const buf = await sandbox.readFileToBuffer({ path: "input.txt" }); const text = buf?.toString("utf8") ?? ""; await sandbox.mkDir("src/generated"); ``` To pull source in at create time, use `source`: a git repo (`{ type: "git", url, username, password, depth?, revision? }` — `username`/`password` authenticate a private repo), a `tarball` (`{ type: "tarball", url }`), or a `snapshot` (`{ type: "snapshot", snapshotId }`). ## Installing system packages The default image is **Ubuntu** — use `apt-get`, and run `apt-get update` first (package lists ship empty, so install fails without it). This needs `sudo`, and there is no shell, so run it through `bash -c` and check the exit code: ```ts const install = await sandbox.runCommand({ cmd: "bash", args: ["-c", "apt-get update && apt-get install -y ffmpeg"], sudo: true, }); if (install.exitCode !== 0) throw new Error(await install.stderr()); ``` For a different base, use a managed image (`vercel/sandbox/arch` uses `pacman`/`yay`) or build a [custom image](#images) so packages are baked in and there's nothing to install at runtime. ## Ports and preview URLs Expose ports at create time (up to 15), start a server **listening on `0.0.0.0`** (not `127.0.0.1`, or it's unreachable — the host flag is framework-specific), then read its public URL. `detached` returns when the process spawns, not when it's listening, so poll for readiness before using the URL: ```ts const sandbox = await Sandbox.create({ ports: [3000] }); // Bind 0.0.0.0 — e.g. Next/Vite: `run dev -- --host 0.0.0.0`; node http: listen("0.0.0.0") await sandbox.runCommand({ cmd: "npm", args: ["run", "dev", "--", "--host", "0.0.0.0"], detached: true }); // Wait until the port actually answers inside the VM for (let i = 0; i < 30; i++) { const ping = await sandbox.runCommand("bash", ["-c", "curl -sf http://localhost:3000 >/dev/null && echo up || true"]); if ((await ping.stdout()).includes("up")) break; await new Promise((r) => setTimeout(r, 1000)); } const url = sandbox.domain(3000); // public HTTPS URL for port 3000 ``` The URL is served by the running session. If the sandbox is stopped, nothing is listening until you resume it and restart the server — so for a durable preview keep the session alive (`extendTimeout`) rather than relying on the URL between sessions. Traffic to and from exposed ports is billable (requests and responses both count). ## Lifecycle and persistence **Persistence is the default.** When a persistent sandbox stops, its **filesystem** is snapshotted automatically; a later call resumes it into a fresh session. Only the filesystem is saved — **running processes do not survive a stop/resume**, so restart long-running servers on resume (see below). - **Sandbox vs session**: a *sandbox* is a long-lived entity identified by `name`; a *session* is one VM boot. The max session duration caps each session, not the sandbox — resuming starts a new session with a fresh timeout, so a persistent sandbox's total lifetime is effectively unbounded. - **Retrieve / resume**: `Sandbox.get({ name })` returns the handle immediately and auto-resumes on the next call that needs a running VM (`resume: false` only skips resuming inside `get`; it doesn't disable this). `getOrCreate` does not resume before returning by default; pass `resume: true` to resume and await `onResume` immediately. `stop()` and `update()` never auto-resume. Use `getOrCreate` when the sandbox may not exist yet, `get` when you know it does. - **`getOrCreate` accepts the same create options** as `create` (`ports`, `persistent`, `resources`, `networkPolicy`, `env`, …). They apply **only when it creates** the sandbox; if the named sandbox already exists it's returned with its existing config (use `sandbox.update({ … })` to change it). - **Hooks are per call**, and fire on mutually exclusive events: `onCreate` runs once, the first time `getOrCreate` creates the sandbox; `onResume` runs on a resume. So to start a service **exactly once per session**, start it in **both** `onCreate` (first boot) and `onResume` (later boots). Hooks are arguments to *this* `getOrCreate` call, not stored on the sandbox — a *different process* resuming via `Sandbox.get` won't run them, so restart what it needs itself. - A detached server returns as soon as the process spawns, **not** when it's listening — so after starting it (in `onCreate` for the first boot and `onResume` for later ones) poll until the port answers before treating `domain(port)` as live (see [Ports](#ports-and-preview-urls)). ```ts const startDev = (s) => s.runCommand({ cmd: "npm", args: ["run", "dev"], detached: true, cwd: "/vercel/sandbox" }); const sandbox = await Sandbox.getOrCreate({ name: "agent-ws", ports: [3000], onCreate: async (s) => { // once, on first creation await s.runCommand({ cmd: "git", args: ["clone", repoUrl, "."], cwd: "/vercel/sandbox" }); await s.runCommand({ cmd: "npm", args: ["install"], cwd: "/vercel/sandbox" }); await startDev(s); // up on first boot, before domain() is read }, onResume: async (s) => startDev(s), // every later resume }); // Poll for the server to be listening (see Ports) before using the URL. const url = sandbox.domain(3000); // Don't stop this sandbox in a finally — stopping kills the dev server and the URL. // If this process might find the sandbox already existing (not freshly created), pass // `resume: true` above and start the server yourself — the hooks only fire on create/this call. ``` A separate later process reconnects by name and resumes on the first command. It won't run the hooks above, so restart anything it needs: ```ts const sandbox = await Sandbox.get({ name: "agent-ws" }); const test = await sandbox.runCommand({ cmd: "npm", args: ["test"], cwd: "/vercel/sandbox" }); // resumes, then runs ``` Opt out for one-off work: `Sandbox.create({ persistent: false })` — the filesystem is discarded on stop and you accrue no snapshot-storage cost. Recommended for scratch/CI tasks. ## Snapshots A snapshot is a saved full-filesystem image you can boot new sandboxes from — the way to skip repeated dependency installs (create-from-snapshot is much faster than installing from scratch). ```ts const running = await Sandbox.create({ image: "vercel/sandbox/node:24" }); await running.runCommand({ cmd: "bash", args: ["-c", "apt-get update && apt-get install -y ffmpeg"], sudo: true }); const snap = await running.snapshot(); // sandbox stops automatically after; do NOT call stop() const fast = await Sandbox.create({ source: { type: "snapshot", snapshotId: snap.snapshotId } }); ``` Snapshots expire 30 days after last use by default. Control retention with `snapshotExpiration` (ms; `0` = never) and `keepLastSnapshots: { count: 1 }` (keep only the latest — keeps storage flat). Persistent sandboxes create these automatically on stop. ## Images Pass `image` to control the environment. Managed images live under `vercel/sandbox`: | Image | Contents | |---|---| | `vercel/sandbox/universal` (default) | Ubuntu + Node.js 24, Python 3.14, coding agents, utilities | | `vercel/sandbox/node:22\|24\|26` | Ubuntu + pinned Node.js, pnpm | | `vercel/sandbox/python:3.14` | Ubuntu + Python 3.14, pip, venv, uv | | `vercel/sandbox/ubuntu` | Minimal Ubuntu 26.04 + sudo | | `vercel/sandbox/arch` | Arch Linux, yay, base-devel |
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub