---
title: "Secure Sandbox - TypeScript & Python"
description: "Documentation for @agentsh/secure-sandbox (TypeScript) and agentsh-secure-sandbox (Python) — drop-in runtime security for Vercel Sandbox, E2B Sandbox, Daytona Sandbox, Cloudflare Containers, Blaxel Sandbox, Sprites (Fly.io), Modal, Runloop, exe.dev, and Freestyle. Policy system, adapters, package install security, and API reference."
doc_version: "1.0"
last_updated: "2026-05-29"
canonical: "https://www.agentsh.org/docs/secure-sandbox/"
---

# Secure Sandbox

Documentation for `@agentsh/secure-sandbox``agentsh-secure-sandbox` — drop-in runtime security for hosted AI agent sandboxes. Wraps Vercel Sandbox, E2B Sandbox, Daytona Sandbox, Cloudflare Containers, Blaxel Sandbox, Sprites (Fly.io), Modal (gVisor), Runloop Devboxes, exe.dev VMs, and Freestyle VMs with kernel-level policy enforcement.

## Quick Start

Install the package:

```typescript
$ npm install @agentsh/secure-sandbox
```

```python
$ pip install agentsh-secure-sandbox
```

Wrap any supported sandbox in three lines:

```typescript
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
```

```python
from vercel_sandbox import Sandbox
from agentsh_secure_sandbox import secure_sandbox
from agentsh_secure_sandbox.adapters import vercel

raw     = await Sandbox.create(runtime="node24")
sandbox = await secure_sandbox(vercel(raw))

await sandbox.exec("npm install express")  # allowed
await sandbox.exec("cat ~/.ssh/id_rsa")   # blocked by policy
```

`secureSandbox()` is built on [**agentsh**](https://github.com/canyonroad/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 multiple 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](https://www.agentsh.org/execution-layer-security/).

The enforcement layers:

- **seccomp** — blocks dangerous syscalls (`sudo`, `nc`, privilege escalation) before execution. When FUSE and Landlock are unavailable, seccomp user-notify also provides [file enforcement](https://www.agentsh.org/docs/features/#seccomp-file-enforcement) via AddFD emulation.

- **Landlock** — kernel-level filesystem restrictions, immutable after activation

- **FUSE** — virtual filesystem layer for file operation interception and soft-delete quarantine. Supports automatic mount method detection including the new mount API for Firecracker microVMs.

- **ptrace** — syscall-level enforcement for restricted runtimes (gVisor, Fargate) where seccomp/eBPF/FUSE are unavailable. Includes exit-time symlink verification and soft-delete.

- **Network Proxy** — domain and port-based connection filtering, prevents data exfiltration

- **DLP** — detects and redacts secrets (API keys, tokens, credentials) in command output

        Landing page

For a quick overview and interactive examples, see the [secure-sandbox landing page](https://www.agentsh.org/secure-sandbox/).

## Vercel AI SDKPydantic AI

`@agentsh/secure-sandbox` pairs naturally with the [Vercel AI SDK](https://sdk.vercel.ai/). 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.

```typescript
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.

`agentsh-secure-sandbox` integrates with [Pydantic AI](https://ai.pydantic.dev/) via the `SecureSandboxToolset`. It provides three tools — `run_command`, `write_file`, and `read_file` — that run inside the secured sandbox with full policy enforcement.

```python
from pydantic_ai import Agent
from daytona_sdk import Daytona
from agentsh_secure_sandbox import SecureSandboxToolset
from agentsh_secure_sandbox.adapters import daytona
from agentsh_secure_sandbox.policies import agent_default

# 1. Create the sandbox and secure it
raw = await Daytona().create()
toolset = SecureSandboxToolset(
    daytona(raw),
    policy=agent_default(
        network=[{"allow": ["api.openai.com"], "ports": [443]}],
    ),
)

# 2. Give the model tools that run inside the secured sandbox
agent = Agent("claude-sonnet-4-6", toolsets=[toolset])
result = await agent.run(
    "Set up a Python project with FastAPI and create a hello-world server."
)

# The model can pip install, write files, run code — but can't
# read ~/.ssh, curl evil domains, or escalate privileges.

await toolset.cleanup()
```

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 | Typical config |
| --- | --- | --- | --- | --- | --- | --- |
| **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` |
| **Modal** (gVisor) | No | No | No | Yes | Yes | `security.mode: minimal` + `sandbox.ptrace` |
| **Runloop** | Yes | Yes | Yes * | Yes | Yes | `full` |
| **exe.dev** | Yes | Yes | Yes * | Yes | Yes | `full` (hybrid) |
| **Freestyle** | Yes | No ** | Yes * | Yes | Yes | `minimal` |

        * FUSE availability

Some platforms (Vercel, Cloudflare) 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. Other platforms (Runloop, exe.dev, Freestyle) support FUSE via deferred activation — the binary configures FUSE at runtime after enabling `/dev/fuse` access. All other enforcement layers — seccomp, Landlock, Network Proxy, and DLP — operate normally.

        ** Landlock on Freestyle

The Freestyle kernel (`6.1.0-6-freestyle`) is currently compiled without `CONFIG_SECURITY_LANDLOCK=y`, so Landlock-based file enforcement is not available. `freestyleDefaults()` sets `allowDegraded: true` and the runtime settles into `minimal` security mode (seccomp wrapper + network proxy + FUSE workspace + cgroups). Command blocking, FUSE-protected workspaces, network policy, and DLP redaction all enforce normally.

### Adapters

Each adapter wraps a provider’s SDK object. Import from `adapters`:

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

// Modal (gVisor — uses ptrace enforcement)
import { modal, modalDefaults } from '@agentsh/secure-sandbox/adapters/modal';
const sb = await secureSandbox(modal(raw), { ...modalDefaults() });

// Runloop (seccomp + FUSE + Landlock + cgroups)
const sb = await secureSandbox(adapters.runloop({ client, id: devbox.id }), adapters.runloopDefaults());

// exe.dev (hybrid ptrace+seccomp, persistent VMs via SSH)
const sb = await secureSandbox(adapters.exe('my-vm'), adapters.exeDefaults());

// Freestyle (Firecracker VMs — minimal mode, no Landlock)
import { freestyle, freestyleDefaults } from '@agentsh/secure-sandbox/adapters/freestyle';
const sb = await secureSandbox(freestyle(vm), freestyleDefaults());
```

```python
from agentsh_secure_sandbox import secure_sandbox
from agentsh_secure_sandbox.adapters import vercel, e2b, daytona, cloudflare, blaxel, sprites
from agentsh_secure_sandbox.adapters.modal import modal, modal_defaults
from agentsh_secure_sandbox.adapters import runloop, runloop_defaults
from agentsh_secure_sandbox.adapters import exe, exe_defaults
from agentsh_secure_sandbox.adapters.freestyle import FreestyleClient, freestyle, freestyle_defaults

# Vercel
sb = await secure_sandbox(vercel(raw))

# E2B
sb = await secure_sandbox(e2b(raw))

# Daytona
sb = await secure_sandbox(daytona(raw))

# Cloudflare Containers
sb = await secure_sandbox(cloudflare(raw))

# Blaxel
sb = await secure_sandbox(blaxel(raw))

# Sprites (Fly.io)
sb = await secure_sandbox(sprites(raw))

# Modal (gVisor) — uses ptrace enforcement
sb = await secure_sandbox(modal(raw), **modal_defaults())

# Runloop
sb = await secure_sandbox(runloop(client=client, id=devbox.id), **runloop_defaults())

# exe.dev — hybrid ptrace+seccomp
sb = await secure_sandbox(exe("my-vm"), **exe_defaults())

# Freestyle — minimal mode (no Landlock)
async with FreestyleClient(api_key=FREESTYLE_API_KEY) as client:
    vm = await client.create_sandbox_vm()
    sb = await secure_sandbox(freestyle(vm), **freestyle_defaults())
```

You can also implement a custom adapter by providing an object with `exec`, `writeFile`, and `readFile` methods. See [API Reference](https://www.agentsh.org/docs/secure-sandbox/#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:

```typescript
import { agentDefault, devSafe, ciStrict, agentSandbox }
  from '@agentsh/secure-sandbox/policies';

// Use a preset directly
const sandbox = await secureSandbox(adapter, { policy: ciStrict() });
```

```python
from agentsh_secure_sandbox.policies import agent_default, dev_safe, ci_strict, agent_sandbox

# Use a preset directly
sandbox = await secure_sandbox(adapter, policy=ci_strict())
```

### 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).

```typescript
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 });
```

```python
from agentsh_secure_sandbox.policies import agent_default

policy = agent_default(
    network=[
        {"allow": ["api.stripe.com", "api.openai.com"], "ports": [443]}
    ],
    file=[
        {"allow": "/data/**", "ops": ["read", "write"]}
    ],
)
```

### Merging Policies

For more control, use `merge()` or `mergePrepend()` to combine multiple policy objects:

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

```python
from agentsh_secure_sandbox.policies import merge, merge_prepend

# Append: base rules take priority (first-match-wins)
combined = merge(base_policy, override_policy)

# Prepend: overrides take priority
combined = merge_prepend(base_policy, override_policy)
```

## 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:

```typescript
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 });
```

```python
from agentsh_secure_sandbox.policies import agent_default

policy = agent_default(
    file=[
        # file rules go here (see below)
    ],
    network=[
        # network rules go here
    ],
    command=[
        # command rules go here
    ],
    env=[
        # environment variable rules go here
    ],
    package_rules=[
        # package install rules go here
    ],
)
```

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

```typescript
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'] },
  ],
});
```

```python
policy = agent_default(
    file=[
        # Deny access to credential files
        {"deny": ["/home/*/.ssh/**", "/home/*/.aws/**"]},
        # Allow read-only access to workspace
        {"allow": "/workspace/**", "ops": ["read"]},
        # Enable soft-delete (quarantine instead of hard delete)
        {"allow": "/workspace/**", "ops": ["write"], "softDelete": True},
    ],
)
```

### 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`) |

```typescript
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' },
  ],
});
```

```python
policy = agent_default(
    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 registry traffic
        {"allow": ["registry.npmjs.org", "pypi.org"]},
    ],
)
```

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

```typescript
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' },
  ],
});
```

```python
policy = agent_default(
    command=[
        # Block privilege escalation
        {"deny": ["sudo", "su", "env", "shutdown", "reboot"]},
        # Redirect curl/wget to safe wrapper
        {"redirect": {"from": "curl", "to": "safe-curl"}},
    ],
)
```

### Environment Rules

Control which environment variables are visible to commands, or inject new ones:

```typescript
const policy = agentDefault({
  env: [
    // Hide specific environment variables
    { deny: ['AWS_SECRET_ACCESS_KEY', 'GITHUB_TOKEN'] },

    // Inject environment variables
    { inject: { 'NODE_ENV': 'production', 'AGENTSH': '1' } },
  ],
});
```

```python
policy = agent_default(
    env=[
        # Hide specific environment variables
        {"deny": ["AWS_SECRET_ACCESS_KEY", "GITHUB_TOKEN"]},
        # Inject safe defaults
        {"set": {"NODE_ENV": "sandbox", "HOME": "/workspace"}},
    ],
)
```

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

```typescript
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' },
  ],
});
```

```python
policy = agent_default(
    package_rules=[
        # Block critical vulnerabilities and malware
        {"match": {"vulnerability": {"severity": "critical"}}, "action": "block"},
        {"match": {"malware": True}, "action": "block"},
        # Block known typosquats
        {"match": {"typosquat": True}, "action": "block"},
        # Audit copyleft licenses
        {"match": {"license": {"spdx": ["GPL-3.0", "AGPL-3.0"]}}, "action": "audit"},
    ],
)
```

        Default package rules

The `agentDefault()` preset includes sensible package rules: 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:

```typescript
const sandbox = await secureSandbox(adapter, {
  packageChecks: {
    scope: 'all',           // 'all' | 'new-only' | 'none'
    providers: ['local', 'osv', 'depsdev'],
  },
});
```

```python
sandbox = await secure_sandbox(adapter,
    package_checks={
        "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 |

        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 is opt-in under `sandbox.ptrace`, so ptrace-backed rows describe the effective configuration rather than a separate `security.mode` enum.

| Configuration | Enforcement Layers | Platforms |
| --- | --- | --- |
| `security.mode: full` | seccomp + Landlock + FUSE + Network Proxy + DLP | E2B, Daytona, Blaxel, Sprites, Runloop |
| `security.mode: full` + `sandbox.ptrace` | ptrace (execve) + seccomp (file monitor) + Landlock + FUSE + Network Proxy + DLP | exe.dev |
| `security.mode: minimal` + `sandbox.ptrace` | ptrace syscall interception + Network Proxy + DLP | Modal (gVisor), AWS Fargate, restricted Kubernetes (opt-in) |
| `security.mode: landlock` | seccomp + Landlock + Network Proxy + DLP (no FUSE) | Vercel, Cloudflare |
| `security.mode: landlock-only` | Landlock filesystem restrictions only | Older kernels |
| `security.mode: 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 for execve, file I/O, network, and signal enforcement; enables full policy enforcement on Modal (gVisor), Fargate, and restricted runtimes (opt-in)

### Minimum Security

Use `minimumSecurityMode` to fail provisioning if the kernel can’t meet your requirements:

```typescript
const sandbox = await secureSandbox(adapter, {
  minimumSecurityMode: 'landlock',  // fail if weaker than landlock
});
```

```python
sandbox = await secure_sandbox(adapter,
    minimum_security_mode="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:

- **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:

```typescript
const sandbox = await secureSandbox(adapter, {
  threatFeeds: false,
});
```

```python
sandbox = await secure_sandbox(adapter,
    threat_feeds=False,
)
```

### Custom Feeds

Add your own threat intelligence sources or replace the defaults:

```typescript
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'],
  },
});
```

```python
sandbox = await secure_sandbox(adapter,
    threat_feeds={
        "action": "deny",        # or "audit" to log without blocking
        "feeds": [
            {
                "name": "internal-blocklist",
                "url": "https://security.internal/feeds/domains.txt",
                "type": "domain",
                "refresh_hours": 6,
            },
            {
                "name": "urlhaus",
                "url": "https://urlhaus.abuse.ch/downloads/text_online/",
                "type": "domain",
            },
        ],
    },
)
```

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

```typescript
async function secureSandbox(
  adapter: SandboxAdapter,
  config?: SecureConfig
): Promise<SecuredSandbox>
```

```python
async def secure_sandbox(
    adapter: SandboxAdapter,
    *,
    policy: Policy | None = None,
    package_checks: PackageChecksConfig | None = None,
    minimum_security_mode: str | None = None,
    threat_feeds: bool | ThreatFeedsConfig | None = None,
    server_config: ServerConfig | None = None,
) -> SecuredSandbox: ...
```

### `SecuredSandbox`

The secured wrapper returned by `secureSandbox()`:

```typescript
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;
}
```

```python
class SecuredSandbox:
    async def exec(self, command: str, *, cwd: str | None = None, timeout: int | None = None) -> ExecResult: ...
    async def write_file(self, path: str, content: str | bytes) -> None: ...
    async def read_file(self, path: str) -> str: ...
    async def stop(self) -> None: ...
    @property
    def security_mode(self) -> str: ...
    @property
    def audit_log(self) -> list[AuditEntry]: ...
```

| 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`, `ptrace`, `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, sandbox resource limits, and ptrace configuration |
| `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 |

```typescript
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);
  }
}
```

```python
from agentsh_secure_sandbox import ProvisioningError, RuntimeError

try:
    sandbox = await secure_sandbox(adapter)
except ProvisioningError as e:
    if e.reason == "unsupported_platform":
        print(f"Platform not supported: {e.message}")
    elif e.reason == "binary_install_failed":
        print(f"Failed to install agentsh: {e.message}")
except RuntimeError as e:
    print(f"Runtime error: {e.message}")
```

        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()`.

```typescript
const myAdapter: SandboxAdapter = {
  async exec(cmd, args, opts) { /* ... */ },
  async writeFile(path, content) { /* ... */ },
  async readFile(path) { /* ... */ },
};

const sandbox = await secureSandbox(myAdapter);
```

```python
class MyAdapter:
    async def exec(self, cmd: str, args: list[str], opts: dict | None = None) -> ExecResult: ...
    async def write_file(self, path: str, content: str | bytes) -> None: ...
    async def read_file(self, path: str) -> str: ...
```

### Ptrace Configuration

For platforms that require ptrace-based enforcement (Modal, AWS Fargate, restricted Kubernetes), configure ptrace via `serverConfig.ptrace`:

```typescript
const sandbox = await secureSandbox(adapter, {
  serverConfig: {
    ptrace: {
      enabled: true,
      attachMode: 'children',        // 'children' or 'pid'
      maskTracerPid: 'off',          // hide tracer from /proc/*/status
      trace: {
        execve: true,
        file: true,
        network: true,
        signal: true,
      },
      performance: {
        seccompPrefilter: false,     // disable on gVisor
        maxTracees: 500,
        maxHoldMs: 5000,
      },
      onAttachFailure: 'fail_open', // or 'fail_closed'
    },
  },
});
```

```python
sandbox = await secure_sandbox(adapter,
    server_config={
        "ptrace": {
            "enabled": True,
            "attach_mode": "children",        # "children" or "pid"
            "trace": {
                "file_ops": True,
                "network_ops": True,
                "process_ops": True,
            },
            "performance": {
                "cache_decisions": True,
                "batch_signals": True,
            },
            "behavior": {
                "follow_forks": True,
                "exit_symlink_verify": True,
            },
        },
    },
)
```

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `enabled` | `boolean` | `false` | Enable ptrace-based syscall tracing |
| `attachMode` | `'children' \| 'pid'` | `'children'` | `'children'` traces all child processes; `'pid'` attaches to a specific PID |
| `maskTracerPid` | `string` | `'off'` | Hide the tracer PID from `/proc/*/status` reads |
| `trace.execve` | `boolean` | `true` | Intercept execve/execveat syscalls |
| `trace.file` | `boolean` | `true` | Intercept file I/O syscalls (openat, unlinkat, renameat2, etc.) |
| `trace.network` | `boolean` | `true` | Intercept connect/bind + DNS proxy |
| `trace.signal` | `boolean` | `true` | Intercept kill/tgkill/tkill |
| `performance.seccompPrefilter` | `boolean` | `true` | Inject BPF pre-filter into tracees. Disable on gVisor. |
| `performance.maxTracees` | `number` | `500` | Maximum concurrent traced threads |
| `performance.maxHoldMs` | `number` | `5000` | Timeout for held syscalls (ms) |
| `onAttachFailure` | `'fail_open' \| 'fail_closed'` | `'fail_open'` | Behavior when ptrace attach fails |

### Modal Adapter

Modal sandboxes run on gVisor, which does not support seccomp user-notify, Landlock, or FUSE. The Modal adapter uses ptrace-based enforcement for full policy coverage.

```typescript
import { secureSandbox } from '@agentsh/secure-sandbox';
import { modal, modalDefaults } from '@agentsh/secure-sandbox/adapters/modal';

const sandbox = await secureSandbox(modal(modalSandbox), {
  ...modalDefaults(),   // pre-configured for gVisor/ptrace
  // your policy overrides here
});
```

```python
from agentsh_secure_sandbox import secure_sandbox
from agentsh_secure_sandbox.adapters.modal import modal, modal_defaults

sandbox = await secure_sandbox(modal(modal_sandbox), **modal_defaults())
    # pre-configured for gVisor/ptrace
```

`modalDefaults()` returns a `Partial<SecureConfig>` pre-configured for gVisor:

- Ptrace enabled with all four trace subsystems (execve, file, network, signal)

- seccomp pre-filter disabled (not supported on gVisor)

- FUSE deferred mode enabled

- Unix sockets and cgroups disabled

- `allowDegraded: true` — starts even if FUSE/seccomp fail

### Runloop Adapter

Runloop Devboxes run as a non-root user (`runloop`) with passwordless sudo. The Runloop adapter uses the Runloop API client for command execution and file operations.

```typescript
import Runloop from '@runloop/api-client';
import { secureSandbox, adapters } from '@agentsh/secure-sandbox';

const client = new Runloop();
const devbox = await client.devboxes.createAndAwaitRunning({ blueprint_id: 'my-bp' });

const sandbox = await secureSandbox(
  adapters.runloop({ client, id: devbox.id }),
  adapters.runloopDefaults(),   // pre-configured for Runloop
);
```

```python
from runloop import Runloop
from agentsh_secure_sandbox import secure_sandbox
from agentsh_secure_sandbox.adapters import runloop, runloop_defaults

client = Runloop()
devbox = await client.devboxes.create_and_await_running(blueprint_id="my-bp")
sandbox = await secure_sandbox(
    runloop(client=client, id=devbox.id),
    **runloop_defaults(),
)
```

`runloopDefaults()` returns a `Partial<SecureConfig>` pre-configured for Runloop Devboxes:

- seccomp file_monitor with `enforce_without_fuse: true`

- FUSE deferred mode (activated via `sudo chmod 666 /dev/fuse`)

- Landlock, cgroups, and unix sockets enabled

- Network proxy in `all` intercept mode

- LLM proxy with DLP redaction (including custom patterns for Runloop, OpenAI, Anthropic, AWS, GitHub keys)

- `allowDegraded: true` — starts even if some enforcement layers fail

### exe.dev Adapter

exe.dev VMs are persistent, SSH-accessible VMs accessed through the exe.dev gateway. The adapter routes commands as `ssh exe.dev ssh <vmName> <command>`. VMs must be created externally via `ssh exe.dev new`.

```typescript
import { secureSandbox, adapters } from '@agentsh/secure-sandbox';

// VM already created: ssh exe.dev new --name=my-vm --image=ubuntu:22.04
const sandbox = await secureSandbox(
  adapters.exe('my-vm'),
  adapters.exeDefaults(),   // pre-configured hybrid ptrace+seccomp mode
);
```

```python
from agentsh_secure_sandbox import secure_sandbox
from agentsh_secure_sandbox.adapters import exe, exe_defaults

# VM already created: ssh exe.dev new --name=my-vm --image=ubuntu:22.04
sandbox = await secure_sandbox(
    exe("my-vm"),
    **exe_defaults(),
)
```

`exeDefaults()` returns a `Partial<SecureConfig>` pre-configured for exe.dev’s hybrid enforcement mode:

- **Hybrid ptrace+seccomp** — ptrace handles execve interception while seccomp file_monitor handles file enforcement

- seccomp file_monitor with `enforce_without_fuse: true`

- FUSE deferred mode (activated via `chmod 666 /dev/fuse`)

- Landlock, cgroups, and unix sockets enabled

- Network proxy in `all` intercept mode

- LLM proxy with DLP redaction

- `allowDegraded: false` — fails if enforcement layers cannot start

### Freestyle Adapter

[Freestyle](https://freestyle.sh) provides Firecracker-backed Linux VMs accessed via the `freestyle-sandboxes` SDK. The adapter uses Freestyle’s typed filesystem API (`vm.fs.*`) for file operations and the VM exec API for commands. Use `configureFreestyleSpec` to bake agentsh into a `VmSpec` snapshot, or boot a stock VM and let `freestyleDefaults()` install agentsh on first start.

```typescript
import { freestyle as freestyleClient, VmSpec } from 'freestyle-sandboxes';
import { secureSandbox } from '@agentsh/secure-sandbox';
import { freestyle, freestyleDefaults, configureFreestyleSpec } from '@agentsh/secure-sandbox/adapters/freestyle';

const fs = freestyleClient({ apiKey: process.env.FREESTYLE_API_KEY });

// Option A: bake agentsh into a snapshot (one-time, faster cold-start)
const spec = configureFreestyleSpec(new VmSpec().snapshot());
const { vm } = await fs.vms.create({ spec });

// Option B: stock VM — agentsh installs on first start
// const { vm } = await fs.vms.create({ spec: new VmSpec() });

const sandbox = await secureSandbox(freestyle(vm), freestyleDefaults());
await sandbox.exec('echo hello');
await sandbox.stop();
```

```python
import os
from agentsh_secure_sandbox import secure_sandbox
from agentsh_secure_sandbox.adapters.freestyle import (
    FreestyleClient, freestyle, freestyle_defaults,
)

async with FreestyleClient(api_key=os.environ["FREESTYLE_API_KEY"]) as client:
    vm = await client.create_sandbox_vm()
    try:
        sandbox = await secure_sandbox(freestyle(vm), **freestyle_defaults())
        await sandbox.exec("echo hello")
    finally:
        await vm.stop()
```

`freestyleDefaults()` returns a `Partial<SecureConfig>` tuned for the Freestyle kernel:

- seccomp execve interception enabled (command blocking)

- seccomp file_monitor disabled — conflicts with FUSE without Yama

- FUSE in deferred mode (activated via `sudo /bin/chmod 666 /dev/fuse` at first session)

- Network proxy in `all` intercept mode with cloud-metadata and RFC1918 blocked

- LLM proxy with DLP redaction (OpenAI / Anthropic / AWS / GitHub / JWT / Slack patterns)

- cgroups + unix sockets enabled, workspace at `/home/user`

- `allowDegraded: true` — the runtime settles into `minimal` security mode (no Landlock on the current Freestyle kernel; see callout above)

## Sitemap

- [Canonical HTML](https://www.agentsh.org/docs/secure-sandbox/)
- [Site map](https://www.agentsh.org/sitemap.md)
- [Full documentation](https://www.agentsh.org/llms-full.md)
