Jev Vercel Sandbox
npx claude-code-templates@latest --mod security/jev-vercel-sandbox Content
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 and the $ API may still change between releases.
Hooks module: hooks/jev-vercel-sandbox.tsx. Options: vercelToken, vercelTeamId, vercelProjectId, sandboxImage, sandboxVcpus, sandboxTimeoutMinutes, persistent, startOnSessionStart, stopOnExit, commandTimeoutMs, whenUnavailable, typesafeApiKey, gatewayApiKey, provider, typesafeBaseUrl, typesafeModel, gatewayBaseUrl, gatewayModel, threshold, severityThreshold, judgeTimeoutMs, onJudgeError, mode, openPanel, columns, vercelApiUrl, logDecisions (set in user or managed settings pluginConfigs, or /config). Run claude plugin validate on the installed folder to see every event it hooks and every $ call it makes.
jev-vercel-sandbox
Runs dangerous Bash commands in a Vercel Sandbox instead of on your machine. Every command Claude is about to run goes to Jev, TypeSafe's System One decision model, which scores how dangerous it is. One past the threshold never touches your disk: it runs in an isolated Linux microVM (Firecracker) that started with the session, and its output comes back as the Bash tool's result, with a note telling Claude where it ran.
[jev-vercel-sandbox] sandbox ready: sbx-quiet-owl · iad1 · 2 vCPU · judge typesafe jev-latest · threshold 0.50
[jev-vercel-sandbox] judge rm -rf node_modules dist .cache: destructive 0.88 · system_change 0.07 · … · severity 1.5 · 290ms
[jev-vercel-sandbox] sandbox rm -rf node_modules dist .cache (destructive 0.88)
[jev-vercel-sandbox] judge npm test: destructive 0.03 · remote_code 0.02 · … · severity 0.0 · 240ms
[jev-vercel-sandbox] local npm test (destructive 0.03)A side pane shows the sandbox's state (● ready, ◌ starting, ○ stopped, ✗ failed, or which settings are missing), its region and size, how long until Vercel stops it, the judge and its threshold, and the last commands that ran there with their exit codes. restart, stop and close buttons act on it.
It pairs with jev-auto-mode, which decides whether an action may run at all, and jev-guardrails, which screens what is said. This one decides where a command runs.
How a command is routed
For every Bash call, the main conversation's and subagents' alike:
Plain reads stay local, unjudged. One program from a short list (
ls,cat,grep,git status,git log,git branch/git remotewith listing flags only,findwithout-delete/-exec, …) with no;,|,&, redirect or substitution. Anything else goes to the judge, however harmless it looks.The judge. One request carries your latest message (the intent), the command, and a battery of yes/no questions plus a severity score:
Hazard Asks whether the command… destructivedeletes, overwrites or irreversibly changes files, git history, databases or infrastructure remote_codedownloads and runs code from the internet, or runs a package or binary not already part of the project system_changechanges the machine beyond the project: sudo, packages, services, users, permissions, profiles exfiltrationsends files, secrets or environment variables off the machine severity0 none · 1 mild · 2 serious · 3 severe, if it turned out to be a mistake here The decision. Any hazard at or above
threshold(0.5), or a severity at or aboveseverityThreshold(2.0), sends it to the sandbox. Otherwise it runs locally, through Claude Code's own permission prompt as usual. Verdicts are cached per request and command.In the sandbox, the command runs as
bash -c "<command>"in the sandbox's working directory, with no local environment variable forwarded, undertimeout(Claude's, elsecommandTimeoutMs). The Bash result is its stdout and stderr, headed by[ran in Vercel Sandbox <name>, not on this machine · <reason> · exit N · Xms]; a non-zero exit addsExit code Nto stderr. A call Claude asked to run in the background (run_in_background) still runs in the foreground there, so the tool call waits for it, up to its timeout (10 minutes at most). Claude also reads a note saying the command did not run on your machine, that the sandbox has none of your files or credentials, and that anything it wrote exists only there.
With no Jev key the engine's own $.model.classify decides between local and sandbox with the same questions as a rubric. That path has no probabilities, so the thresholds do not apply.
What the sandbox is not. It is a quarantine, not a copy of your project: your files are not there, so a command that needs them (a migration against your local database, rm -rf build meant to clean your build) runs against an empty machine and changes nothing of yours. That is the point for curl … | sh or a command you never meant to run; for one you did mean, Claude is told where it ran and should hand it to you to run yourself.
When the sandbox cannot run it
| Situation | whenUnavailable: "deny" (default) |
"local" |
|---|---|---|
| no Vercel credentials | refused; Claude is told why and not to retry another way | runs locally |
| the sandbox failed to start, or a call failed | refused (after one restart attempt if the session had stopped) | runs locally |
A judge that gives no answer (timeout past judgeTimeoutMs, an error) sends the command to the sandbox (onJudgeError: "sandbox"); set "local" to let it run here instead. mode: "audit" judges and logs every command but runs all of them locally, which is how to see what it would sandbox before turning it on.
The sandbox's lifecycle
- Session start: the sandbox is created (
startOnSessionStart) and the pane opens (openPanel). The session waits up to 8 s for it; past that it finishes in the background and the first dangerous command waits for it. Off: it starts on the first dangerous command. - Lifetime:
sandboxTimeoutMinutes(45). Vercel stops it then; the next dangerous command starts a new one. Your plan caps the maximum. - Session end: stopped (
stopOnExit). Withpersistent: false(default) nothing is kept;truelets Vercel snapshot the filesystem on stop. /jev-vercel-sandboxshows the state;/jev-vercel-sandbox open | restart | stop | log.
Billing is Vercel's Active CPU pricing plus provisioned memory while it runs: see pricing.
Options
Set them in user settings (~/.claude/settings.json, not the project's), --settings <file>, managed settings, or /config:
{
"pluginConfigs": {
"jev-vercel-sandbox@skills-dir": {
"options": {
"vercelToken": "<your Vercel access token>",
"vercelTeamId": "team_…",
"vercelProjectId": "prj_…",
"typesafeApiKey": "<your TypeSafe key>"
}
}
}
}The key is the plugin's id: "jev-vercel-sandbox@skills-dir" when installed with --mod, "jev-vercel-sandbox" with --plugin-dir. Under the wrong key every option stays at its default and the pane says the credentials are missing.
Vercel authentication takes three values, as the SDK's getCredentials() does:
- an access token (vercel.com/account/tokens) plus the team id and project id it is scoped to; or
- an OIDC token (
vercel linkthenvercel env pullwritesVERCEL_OIDC_TOKENto.env.local), which carries the team and project itself. It expires after about 12 hours and this mod cannot refresh it (the SDK does it through@vercel/oidc, which a mod cannot load), so an access token is the better fit for daily use.
Any of the three left empty falls back to VERCEL_TOKEN (then VERCEL_OIDC_TOKEN), VERCEL_TEAM_ID and VERCEL_PROJECT_ID in the environment Claude Code was started with.
vercelToken: string access token or OIDC token (sensitive); env VERCEL_TOKEN / VERCEL_OIDC_TOKEN
vercelTeamId: string team_…; env VERCEL_TEAM_ID; read from an OIDC token
vercelProjectId: string prj_…; env VERCEL_PROJECT_ID; read from an OIDC token
sandboxImage: string empty: vercel/sandbox/universal (Ubuntu, Node.js LTS, Python)
sandboxVcpus: number unset: Vercel's default
sandboxTimeoutMinutes: number lifetime before Vercel stops it (default 45)
persistent: boolean keep the filesystem across stops (default false)
startOnSessionStart: boolean start with the session (default true)
stopOnExit: boolean stop when the session ends (default true)
commandTimeoutMs: number when Claude sets none (default 120000, max 600000)
whenUnavailable: string "deny" | "local" (default "deny")
typesafeApiKey: string TypeSafe key (sensitive, preferred: calibrated probabilities)
gatewayApiKey: string Vercel AI Gateway key (sensitive)
provider: string "auto" | "typesafe" | "gateway" | "builtin"
typesafeBaseUrl / typesafeModel / gatewayBaseUrl / gatewayModel
threshold: number hazard probability that sandboxes (default 0.5)
severityThreshold: number severity (0-3) that sandboxes (default 2)
judgeTimeoutMs: number judge latency budget (default 2000)
onJudgeError: string "sandbox" | "local" (default "sandbox")
mode: string "enforce" | "audit" (default "enforce")
openPanel: boolean open the pane at start (default true)
columns: number pane width, 28-80 (default 44)
vercelApiUrl: string empty: https://api.vercel.com
logDecisions: boolean log each judgement (default true)Privacy
With a Jev key, your latest message and each judged command go to TypeSafe or the AI Gateway. Each sandboxed command goes to Vercel. No file content and no environment variable leaves the machine through this mod. Plain reads are never sent anywhere.
Install
npx claude-code-templates@latest --mod security/jev-vercel-sandbox
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude--mod writes the plugin to .claude/skills/jev-vercel-sandbox/, which Claude Code auto-loads as jev-vercel-sandbox@skills-dir in a trusted project (accept the trust prompt on the first interactive claude there; -p never asks). In the fullscreen layout (/tui fullscreen) the pane docks beside the transcript; below 144 columns the engine keeps an unasked pane hidden until /jev-vercel-sandbox opens it.
For one session, or in a folder you do not want to trust:
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude --plugin-dir .claude/skills/jev-vercel-sandboxclaude plugin validate .claude/skills/jev-vercel-sandbox lists every event it hooks, every $ call, and the four environment variables it reads.
No lines at all in the transcript: in claude -p there is no transcript or pane, and every line goes to ~/.claude/debug/<session-id>.txt; otherwise check that the project is trusted and CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 is set (see jev-guardrails).
Tests
cd cli-tool/components/mods
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude plugin test security/jev-vercel-sandboxThey run the hooks against a fake Vercel API: the sandbox starts with the session, a dangerous command reaches /cmd and never runs locally, a safe one does, missing credentials and a failed start refuse, an OIDC token supplies its own team and project, the session end stops it, and the pane shows each state.
Early access. Mods need Claude Code 2.1.259+ with CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1; the $ API may change between releases. Typed against Anthropic's declarations: https://github.com/anthropics/claude-code/tree/main/mods
A mod runs without node_modules, so @vercel/sandbox is not available: the Sandbox is driven over HTTP through $.http.fetch, with the endpoints of Vercel's REST API reference (POST /v3/sandboxes, GET /v2/sandboxes/sessions/{id}, POST …/cmd with wait and logs, answering application/x-ndjson, POST …/stop, each with ?teamId=), called the way @vercel/sandbox 3.5.0 calls them. The Jev wire shapes are jev-auto-mode's.