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-sandbox

Wrap 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 policy

secureSandbox() 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:

Landing page

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
* FUSE unavailable

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#

FieldTypeDescription
allow or denystring | 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)
redirectstringRedirect 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#

FieldTypeDescription
allow or denystring[]Domain patterns to match
portsnumber[]Restrict to specific ports. Omit for all ports.
decision'allow' | 'deny' | 'redirect'Override the decision
redirectstringRedirect 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#

FieldTypeDescription
allow or denystring[]Command names or patterns
decision'allow' | 'deny' | 'redirect'Override the decision
redirectstringRedirect 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.

FieldTypeDescription
matchPackageMatchConditions to match: vulnerability, malware, typosquat, license, newPackage
action'block' | 'warn' | 'approve'What to do when the rule matches

Match conditions:

ConditionTypeDescription
vulnerability{ severity: 'critical' | 'high' | 'medium' | 'low' }Match packages with known vulnerabilities at or above the given severity
malwaretrueMatch packages flagged as malware
typosquattrueMatch packages detected as typosquats of popular packages
license{ category: string }Match packages by license category (e.g. 'copyleft', 'unknown')
newPackagetrueMatch 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' },
  ],
});
Default package rules

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:

ProviderDescriptionData Source
localFast local checks — typosquat detection, license analysis, lockfile diffingLocal heuristics
osvKnown vulnerability lookup via the Open Source Vulnerabilities databaseosv.dev
depsdevPackage metadata, scorecard, and dependency graph analysisdeps.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'],
  },
});
FieldTypeDefaultDescription
scope'all' | 'new-only' | 'none''all''all' checks every package; 'new-only' checks only packages not in the lockfile; 'none' disables checks
providersstring[]['local', 'osv', 'depsdev']Which providers to query
Enabled by default

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:

ModeEnforcement LayersPlatforms
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:

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
});
Production recommendation

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:

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'],
  },
});
FieldTypeDescription
action'deny' | 'audit'Block or just log matches. Default: 'deny'
feeds[].namestringDisplay name for the feed
feeds[].urlstringURL to the feed file
feeds[].format'hostfile' | 'domain-list'Feed format
feeds[].refreshIntervalstringHow often to refresh. Default: '6h'
allowliststring[]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;
}
MethodDescription
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.
sessionIdUnique agentsh session identifier.
securityModeNegotiated security mode (full, landlock, landlock-only, minimal).

Config Options#

OptionTypeDefaultDescription
policyPolicyDefinitionagentDefault()Policy rules for file, network, command, env, and package access
workspacestring'/workspace'Root path for workspace-relative policy rules
installStrategystring'download''download', 'upload', 'preinstalled', or 'running'
agentshVersionstring'0.15.0'Pin to a specific agentsh release
minimumSecurityModeSecurityModeFail if kernel can’t enforce this level
threatFeedsfalse | ThreatFeedsConfigURLhaus + Phishing.DatabaseThreat intelligence configuration
packageChecksPackageChecksConfigAll providers enabledPackage install security check configuration (see Package Install Security Checks)
realPathsbooleanfalseUse real host paths instead of virtualizing
enforceRedirectsbooleanfalseMake redirects enforced instead of shadowed
serverConfigServerConfigAdvanced server options: gRPC settings, logging, session limits, audit config, and sandbox resource limits
watchtowerstringEvent sink URL for audit events
traceParentstringW3C traceparent header for OpenTelemetry tracing

Error Classes#

All errors extend AgentSHError:

ErrorWhen
PolicyValidationErrorPolicy object fails Zod schema validation
ProvisioningErrorBinary installation, config writing, or server startup fails
IntegrityErrorSHA256 checksum mismatch on downloaded binary
RuntimeErrorCommand execution failure (includes sessionId for debugging)
MissingPeerDependencyErrorSandbox provider SDK not installed
IncompatibleProviderVersionErrorProvider 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);
  }
}
Custom adapters

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);