Secure Sandbox
Documentation for @agentsh/secure-sandbox — drop-in runtime security for hosted AI agent sandboxes. Wraps Vercel Sandbox, E2B Sandbox, Daytona Sandbox, Cloudflare Containers, Blaxel Sandbox, and Sprites (Fly.io Firecracker microVMs) with kernel-level policy enforcement.
Quick Start#
Install the package:
$ npm install @agentsh/secure-sandboxWrap any supported sandbox in three lines:
import { Sandbox } from '@vercel/sandbox';
import { secureSandbox, adapters } from '@agentsh/secure-sandbox';
const raw = await Sandbox.create({ runtime: 'node24' });
const sandbox = await secureSandbox(adapters.vercel(raw));
await sandbox.exec('npm install express'); // allowed
await sandbox.exec('cat ~/.ssh/id_rsa'); // blocked by policysecureSandbox() is built on agentsh (agent shell), the open-source execution-layer security engine. It installs the agentsh binary into the sandbox, replaces /bin/bash with a policy-enforcing shell shim, and routes every operation through five kernel-level enforcement layers.
Unlike prompt-level guardrails that depend on the AI model complying with instructions, agentsh operates below the model layer — intercepting actual system calls (file access, network connections, process execution) and evaluating them against deterministic policy rules. No matter what prompt injection is attempted, policy enforcement is applied to the real operations the agent tries to perform. Learn more about this approach in the execution-layer security overview.
The five enforcement layers:
- seccomp — blocks dangerous syscalls (
sudo,nc, privilege escalation) before execution - Landlock — kernel-level filesystem restrictions, immutable after activation
- FUSE — virtual filesystem layer for file operation interception and soft-delete quarantine
- Network Proxy — domain and port-based connection filtering, prevents data exfiltration
- DLP — detects and redacts secrets (API keys, tokens, credentials) in command output
For a quick overview and interactive examples, see the secure-sandbox landing page.
Vercel AI SDK#
@agentsh/secure-sandbox pairs naturally with the Vercel AI SDK. Use createSandbox from the AI SDK to spin up a sandbox, then wrap it with secureSandbox so every tool call the model makes — file writes, shell commands, network requests — is policy-enforced at the kernel level.
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { Sandbox } from '@vercel/sandbox';
import { secureSandbox, adapters } from '@agentsh/secure-sandbox';
import { agentDefault } from '@agentsh/secure-sandbox/policies';
// 1. Create the sandbox and secure it
const raw = await Sandbox.create();
const sandbox = await secureSandbox(adapters.vercel(raw), {
policy: agentDefault({
network: [
{ allow: ['api.openai.com'], ports: [443] },
],
}),
});
// 2. Give the model tools that run inside the secured sandbox
const result = await generateText({
model: openai('gpt-5.4'),
prompt: 'Set up a Node.js project with Express and create a hello-world server.',
tools: {
exec: {
description: 'Run a shell command in the sandbox',
parameters: { command: { type: 'string' } },
async execute({ command }) {
const { stdout, stderr } = await sandbox.exec(command);
return { stdout, stderr };
},
},
writeFile: {
description: 'Write a file in the sandbox',
parameters: { path: { type: 'string' }, content: { type: 'string' } },
async execute({ path, content }) {
return await sandbox.writeFile(path, content);
},
},
},
});
// The model can npm install, write files, run code — but can't
// read ~/.ssh, curl evil domains, or escalate privileges.
await sandbox.stop();Every tool call the model makes goes through the agentsh policy engine. If a prompt injection tricks the model into running cat ~/.ssh/id_rsa or curl https://evil.com, it gets blocked at the kernel level — regardless of what the model intended.
Supported Platforms#
Built-in adapters for the major hosted AI sandbox providers. Each adapter maps the platform’s SDK to the SandboxAdapter interface with zero configuration.
Platform Matrix#
| Provider | seccomp | Landlock | FUSE | Network Proxy | DLP | Security Mode |
|---|---|---|---|---|---|---|
| Vercel Sandbox | Yes | Yes | No * | Yes | Yes | landlock |
| E2B Sandbox | Yes | Yes | Yes | Yes | Yes | full |
| Daytona Sandbox | Yes | Yes | Yes | Yes | Yes | full |
| Cloudflare Containers | Yes | Yes | No * | Yes | Yes | landlock |
| Blaxel Sandbox | Yes | Yes | Yes | Yes | Yes | full |
| Sprites (Fly.io) | Yes | Yes | Yes | Yes | Yes | full |
Some platforms do not expose /dev/fuse, so the FUSE virtual filesystem layer cannot be mounted. Without FUSE, soft-delete quarantine and real-paths virtualization are not available. File deletes are real and irreversible, and path remapping through realPaths has no effect. All other enforcement layers — seccomp, Landlock, Network Proxy, and DLP — operate normally. The security mode negotiates to landlock instead of full.
Adapters#
Each adapter wraps a provider’s SDK object. Import from adapters:
import { secureSandbox, adapters } from '@agentsh/secure-sandbox';
// Vercel
const sb = await secureSandbox(adapters.vercel(raw));
// E2B
const sb = await secureSandbox(adapters.e2b(raw));
// Daytona
const sb = await secureSandbox(adapters.daytona(raw));
// Cloudflare Containers
const sb = await secureSandbox(adapters.cloudflare(raw));
// Blaxel
const sb = await secureSandbox(adapters.blaxel(raw));
// Sprites (Fly.io Firecracker microVMs)
const sb = await secureSandbox(adapters.sprites(raw));You can also implement a custom adapter by providing an object with exec, writeFile, and readFile methods. See API Reference below.
Policy System#
Policies define what operations are allowed, denied, redirected, or audited inside a secured sandbox. Policies are TypeScript objects validated with Zod and serialized to YAML for the agentsh engine.
Presets#
Four built-in presets cover common use cases:
| Preset | Use Case | Network | Filesystem | Commands |
|---|---|---|---|---|
agentDefault |
Production AI agents | Allowlisted registries (npm, PyPI, Cargo, Go, GitHub) on port 443 | Workspace read/write/create, deny .ssh, .aws, .env, shell configs |
Safe utilities + dev tools, deny sudo/env/shutdown |
devSafe |
Local development | Permissive (no deny-all) | Workspace-focused, fewer restrictions | Fewer command restrictions |
ciStrict |
CI/CD runners | Strict registries only | Deny all files outside workspace | Restricted command set |
agentSandbox |
Untrusted code | No network access | Read-only workspace | Heavily restricted |
Usage:
import { agentDefault, devSafe, ciStrict, agentSandbox }
from '@agentsh/secure-sandbox/policies';
// Use a preset directly
const sandbox = await secureSandbox(adapter, { policy: ciStrict() });Extending Policies#
Every preset accepts an override object to add rules. Override rules are appended — the preset’s base rules take priority (first-match-wins).
import { agentDefault } from '@agentsh/secure-sandbox/policies';
const policy = agentDefault({
network: [
{ allow: ['api.stripe.com', 'api.openai.com'], ports: [443] }
],
file: [
{ allow: '/data/**', ops: ['read', 'write'] }
],
command: [
{ allow: ['ffmpeg'] }
],
});
const sandbox = await secureSandbox(adapter, { policy });Merging Policies#
For more control, use merge() or mergePrepend() to combine multiple policy objects:
import { merge, mergePrepend } from '@agentsh/secure-sandbox/policies';
// Append: base rules take priority (first-match-wins)
const combined = merge(basePolicy, overridePolicy);
// Prepend: override rules take priority
const combined = mergePrepend(basePolicy, overridePolicy);Policy Rules Reference#
A policy is a TypeScript object with five optional rule arrays: file, network, command, env, and packageRules. Here is the overall structure:
import { agentDefault } from '@agentsh/secure-sandbox/policies';
const policy = agentDefault({
file: [
// file rules go here (see below)
],
network: [
// network rules go here (see below)
],
command: [
// command rules go here (see below)
],
env: [
// environment rules go here (see below)
],
packageRules: [
// package install rules go here (see below)
],
});
const sandbox = await secureSandbox(adapter, { policy });Each rule type is an array of rule objects. Rules are evaluated top-to-bottom; the first matching rule wins. Package rules use a match/action pattern instead of allow/deny. The sections below show the available fields and examples for each rule type.
File Rules#
| Field | Type | Description |
|---|---|---|
allow or deny | string | string[] | Glob pattern(s) for file paths |
ops | ('read' | 'write' | 'create' | 'delete')[] | Which file operations this rule applies to. Omit for all operations. |
decision | 'allow' | 'deny' | 'redirect' | 'audit' | 'softDelete' | Override the decision (alternative to using allow/deny keys) |
redirect | string | Redirect path (when decision is redirect) |
const policy = agentDefault({
file: [
// Deny access to credential files
{ deny: ['/home/*/.ssh/**', '/home/*/.aws/**'] },
// Allow read-only access to /data
{ allow: '/data/**', ops: ['read'] },
// Soft-delete instead of real delete (files go to trash)
{ decision: 'softDelete', allow: '/workspace/**', ops: ['delete'] },
],
});Network Rules#
| Field | Type | Description |
|---|---|---|
allow or deny | string[] | Domain patterns to match |
ports | number[] | Restrict to specific ports. Omit for all ports. |
decision | 'allow' | 'deny' | 'redirect' | Override the decision |
redirect | string | Redirect destination (when decision is redirect) |
const policy = agentDefault({
network: [
// Allow only HTTPS to specific APIs
{ allow: ['api.stripe.com', 'api.openai.com'], ports: [443] },
// Block cloud metadata endpoints
{ deny: ['169.254.169.254', 'metadata.google.internal'] },
// Redirect npm to internal registry
{ decision: 'redirect', allow: ['registry.npmjs.org'], redirect: 'npm.internal.company.com' },
],
});Command Rules#
| Field | Type | Description |
|---|---|---|
allow or deny | string[] | Command names or patterns |
decision | 'allow' | 'deny' | 'redirect' | Override the decision |
redirect | string | Redirect to alternative command |
const policy = agentDefault({
command: [
// Block privilege escalation
{ deny: ['sudo', 'su', 'env', 'shutdown', 'reboot'] },
// Redirect curl to agentsh-fetch (prevents raw exfiltration)
{ decision: 'redirect', allow: ['curl', 'wget'], redirect: 'agentsh-fetch' },
],
});Environment Rules#
Control which environment variables are visible to commands, or inject new ones:
const policy = agentDefault({
env: [
// Hide specific environment variables
{ deny: ['AWS_SECRET_ACCESS_KEY', 'GITHUB_TOKEN'] },
// Inject environment variables
{ inject: { 'NODE_ENV': 'production', 'AGENTSH': '1' } },
],
});Package Rules#
Control what packages can be installed based on vulnerability data, malware detection, typosquatting analysis, and license checks. Package rules are evaluated when an agent runs npm install, pip install, or similar commands.
| Field | Type | Description |
|---|---|---|
match | PackageMatch | Conditions to match: vulnerability, malware, typosquat, license, newPackage |
action | 'block' | 'warn' | 'approve' | What to do when the rule matches |
Match conditions:
| Condition | Type | Description |
|---|---|---|
vulnerability | { severity: 'critical' | 'high' | 'medium' | 'low' } | Match packages with known vulnerabilities at or above the given severity |
malware | true | Match packages flagged as malware |
typosquat | true | Match packages detected as typosquats of popular packages |
license | { category: string } | Match packages by license category (e.g. 'copyleft', 'unknown') |
newPackage | true | Match packages not already in the lockfile |
const policy = agentDefault({
packageRules: [
// Block critical vulnerabilities and malware
{ match: { vulnerability: { severity: 'critical' } }, action: 'block' },
{ match: { malware: true }, action: 'block' },
{ match: { typosquat: true }, action: 'block' },
// Block copyleft licenses
{ match: { license: { category: 'copyleft' } }, action: 'block' },
// Warn on medium vulnerabilities
{ match: { vulnerability: { severity: 'medium' } }, action: 'warn' },
// Approve new packages (require review)
{ match: { newPackage: true }, action: 'approve' },
],
});The agentDefault() preset includes sensible package rules out of the box: block critical vulnerabilities, malware, and typosquats; block copyleft licenses; warn on medium vulnerabilities; and approve new packages for review.
Package Install Security Checks#
When an agent runs a package install command (npm install, pip install, cargo add, etc.), secure-sandbox can intercept the install and check each package against multiple vulnerability and quality databases before allowing it.
Providers#
Three providers supply package intelligence:
| Provider | Description | Data Source |
|---|---|---|
local | Fast local checks — typosquat detection, license analysis, lockfile diffing | Local heuristics |
osv | Known vulnerability lookup via the Open Source Vulnerabilities database | osv.dev |
depsdev | Package metadata, scorecard, and dependency graph analysis | deps.dev |
Configuration#
Enable package checks via the packageChecks config option:
const sandbox = await secureSandbox(adapter, {
packageChecks: {
scope: 'all', // 'all' | 'new-only' | 'none'
providers: ['local', 'osv', 'depsdev'],
},
});| Field | Type | Default | Description |
|---|---|---|---|
scope | 'all' | 'new-only' | 'none' | 'all' | 'all' checks every package; 'new-only' checks only packages not in the lockfile; 'none' disables checks |
providers | string[] | ['local', 'osv', 'depsdev'] | Which providers to query |
The agentDefault() preset enables package checks with all three providers. Set packageChecks: { scope: 'none' } to disable.
Security Modes#
The system automatically negotiates the strongest available security mode based on kernel capabilities. Ptrace mode is opt-in. Modes from strongest to weakest:
| Mode | Enforcement Layers | Platforms |
|---|---|---|
full |
seccomp + Landlock + FUSE + Network Proxy + DLP | E2B, Daytona, Blaxel, Sprites |
ptrace |
ptrace syscall interception + Network Proxy + DLP | AWS Fargate, restricted Kubernetes (opt-in) |
landlock |
seccomp + Landlock + Network Proxy + DLP (no FUSE) | Vercel, Cloudflare |
landlock-only |
Landlock filesystem restrictions only | Older kernels |
minimal |
Policy evaluation only, no kernel enforcement | Fallback |
Each layer provides specific protections:
- seccomp — blocks dangerous syscalls (
sudo,nc,env) before execution - Landlock — kernel-level filesystem restrictions, immutable after activation
- FUSE — virtual filesystem layer for soft-delete quarantine and real-paths virtualization
- Network Proxy — domain and port-based connection filtering, prevents exfiltration
- DLP — detects and redacts secrets in command output
- ptrace — syscall-level interception via Linux ptrace; enables enforcement on Fargate and restricted runtimes (opt-in)
Minimum Security#
Use minimumSecurityMode to fail provisioning if the kernel can’t meet your requirements:
const sandbox = await secureSandbox(adapter, {
minimumSecurityMode: 'landlock', // fail if weaker than landlock
});Always set minimumSecurityMode in production to ensure kernel-level enforcement. Without it, the system may silently fall back to minimal mode.
Threat Intelligence#
By default, secure-sandbox blocks connections to known-malicious domains using two threat feeds:
- URLhaus (abuse.ch) — malware distribution domains, refreshed every 6 hours
- Phishing.Database (GitHub) — active phishing domains, refreshed every 12 hours
Package registries (npm, PyPI, Cargo, Go, GitHub) are always allowlisted and never blocked by threat feeds.
Disable threat feeds entirely:
const sandbox = await secureSandbox(adapter, {
threatFeeds: false,
});Custom Feeds#
Add your own threat intelligence sources or replace the defaults:
const sandbox = await secureSandbox(adapter, {
threatFeeds: {
action: 'deny', // or 'audit' to log without blocking
feeds: [
{
name: 'internal-blocklist',
url: 'https://security.internal/blocked-domains.txt',
format: 'domain-list', // or 'hostfile'
refreshInterval: '1h',
},
],
allowlist: ['trusted.internal.com'],
},
});| Field | Type | Description |
|---|---|---|
action | 'deny' | 'audit' | Block or just log matches. Default: 'deny' |
feeds[].name | string | Display name for the feed |
feeds[].url | string | URL to the feed file |
feeds[].format | 'hostfile' | 'domain-list' | Feed format |
feeds[].refreshInterval | string | How often to refresh. Default: '6h' |
allowlist | string[] | Domains to never block |
API Reference#
secureSandbox(adapter, config?)#
Secures any sandbox via its adapter. Returns a SecuredSandbox that mediates every command, file read, and file write through the agentsh policy engine.
async function secureSandbox(
adapter: SandboxAdapter,
config?: SecureConfig
): Promise<SecuredSandbox>SecuredSandbox#
The secured wrapper returned by secureSandbox():
interface SecuredSandbox {
exec(command: string, opts?: {
cwd?: string;
timeout?: number;
}): Promise<ExecResult>;
writeFile(path: string, content: string): Promise<WriteFileResult>;
readFile(path: string): Promise<ReadFileResult>;
stop(): Promise<void>;
readonly sessionId: string;
readonly securityMode: SecurityMode;
}| Method | Description |
|---|---|
exec(command) | Run a shell command through the policy engine. Returns { stdout, stderr, exitCode }. |
writeFile(path, content) | Write a file after policy check. Returns { success, path, error? }. |
readFile(path) | Read a file after policy check. Returns { success, path, content?, error? }. |
stop() | Terminate the sandbox and release resources. |
sessionId | Unique agentsh session identifier. |
securityMode | Negotiated security mode (full, landlock, landlock-only, minimal). |
Config Options#
| Option | Type | Default | Description |
|---|---|---|---|
policy | PolicyDefinition | agentDefault() | Policy rules for file, network, command, env, and package access |
workspace | string | '/workspace' | Root path for workspace-relative policy rules |
installStrategy | string | 'download' | 'download', 'upload', 'preinstalled', or 'running' |
agentshVersion | string | '0.15.0' | Pin to a specific agentsh release |
minimumSecurityMode | SecurityMode | — | Fail if kernel can’t enforce this level |
threatFeeds | false | ThreatFeedsConfig | URLhaus + Phishing.Database | Threat intelligence configuration |
packageChecks | PackageChecksConfig | All providers enabled | Package install security check configuration (see Package Install Security Checks) |
realPaths | boolean | false | Use real host paths instead of virtualizing |
enforceRedirects | boolean | false | Make redirects enforced instead of shadowed |
serverConfig | ServerConfig | — | Advanced server options: gRPC settings, logging, session limits, audit config, and sandbox resource limits |
watchtower | string | — | Event sink URL for audit events |
traceParent | string | — | W3C traceparent header for OpenTelemetry tracing |
Error Classes#
All errors extend AgentSHError:
| Error | When |
|---|---|
PolicyValidationError | Policy object fails Zod schema validation |
ProvisioningError | Binary installation, config writing, or server startup fails |
IntegrityError | SHA256 checksum mismatch on downloaded binary |
RuntimeError | Command execution failure (includes sessionId for debugging) |
MissingPeerDependencyError | Sandbox provider SDK not installed |
IncompatibleProviderVersionError | Provider SDK version mismatch |
import { ProvisioningError, RuntimeError } from '@agentsh/secure-sandbox';
try {
const sandbox = await secureSandbox(adapter);
} catch (e) {
if (e instanceof ProvisioningError) {
console.error('Setup failed:', e.phase, e.stderr);
}
}To integrate a sandbox not listed above, implement the SandboxAdapter interface with exec, writeFile, and readFile methods, then pass it directly to secureSandbox().
const myAdapter: SandboxAdapter = {
async exec(cmd, args, opts) { /* ... */ },
async writeFile(path, content) { /* ... */ },
async readFile(path) { /* ... */ },
};
const sandbox = await secureSandbox(myAdapter);