Secures any sandbox via its adapter. Returns a SecuredSandbox that mediates every command, file read, and file write through the agentsh policy engine.
import { secureSandbox } from '@agentsh/secure-sandbox';
const sandbox = await secureSandbox(adapter, {
policy: agentDefault(), // Policy to enforce (default: agentDefault())
installStrategy: 'download', // 'download' | 'upload' | 'preinstalled' | 'running'
agentshVersion: '0.18.0', // agentsh binary version
minimumSecurityMode: 'landlock', // Fail if kernel can't enforce this level
threatFeeds: true, // Enable/disable/customize threat intelligence feeds
packageChecks: {}, // Enable package install security checks
});| Option | Type | Default | Description |
|---|---|---|---|
policy |
Policy |
agentDefault() |
Policy rules for file, network, and command access |
installStrategy |
string |
'download' |
How to install the agentsh binary in the sandbox |
agentshVersion |
string |
Latest | Pin a specific agentsh version |
minimumSecurityMode |
SecurityMode |
undefined |
Fail if the sandbox kernel can't enforce at least this level |
securityMode |
SecurityMode |
undefined |
Override detected security mode. Only used with 'running' strategy (defaults to 'full'). |
sessionId |
string |
undefined |
Existing agentsh session ID. Only used with 'running' strategy. Falls back to $AGENTSH_SESSION_ID. |
threatFeeds |
boolean | ThreatFeedConfig |
true |
Threat intelligence feed configuration |
packageChecks |
false | PackageChecksConfig |
false |
Package install security checks (OSV, deps.dev, Socket, Snyk) |
| Strategy | When to Use |
|---|---|
'download' |
Default — downloads agentsh from GitHub releases inside the sandbox |
'upload' |
Upload a local agentsh binary to the sandbox (useful for air-gapped environments) |
'preinstalled' |
agentsh is already installed in the sandbox image |
'running' |
agentsh is already running — skip installation and startup entirely |
The interface returned by secureSandbox(). Every operation is mediated by the policy engine.
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;
}Run a shell command. The command is routed through the agentsh shell shim, which evaluates it against the policy before execution.
const result = await sandbox.exec('npm install express');
// { stdout: '...', stderr: '...', exitCode: 0 }
const result = await sandbox.exec('cat .env');
// { stdout: '', stderr: 'denied by policy: file access blocked', exitCode: 126 }Write a file to the sandbox. File path is checked against file policy rules.
await sandbox.writeFile('/workspace/index.js', 'console.log("hello")');Read a file from the sandbox. File path is checked against file policy rules.
const content = await sandbox.readFile('/workspace/index.js');Stop the sandbox and clean up resources.
Unique identifier for this secured sandbox session.
The actual security mode negotiated with the sandbox kernel. See Security Modes.
The security level depends on what the sandbox kernel supports. secureSandbox() automatically negotiates the highest available mode.
| Mode | Enforcement | Typical Platform |
|---|---|---|
full |
seccomp + FUSE + Landlock + network proxy | Full Linux with FUSE support (E2B, Daytona, Blaxel, exe.dev) |
ptrace |
ptrace syscall interception + network proxy (exec, file, network, signal) | gVisor-based platforms (Modal) |
landlock |
Landlock + network proxy (no FUSE) | Firecracker VMs (Vercel, Cloudflare) |
landlock-only |
Landlock filesystem restrictions only | Limited kernel support |
minimal |
Per-command seccomp wrapper + network proxy + FUSE soft-delete + cgroups (no seccomp user-notify, no Landlock) | Kernels lacking Yama / user-notify support (Freestyle) |
Note: seccomp and FUSE are disabled by default for compatibility. The detected security mode reflects kernel capabilities, not the active config. Landlock and network proxy are the default enforcement layers. Enable seccomp/FUSE explicitly via
serverConfigif your environment supports them.
Use minimumSecurityMode to fail fast if the sandbox can't meet your security requirements:
const sandbox = await secureSandbox(vercel(raw), {
minimumSecurityMode: 'landlock', // Throws if kernel can't enforce this level
});
console.log(sandbox.securityMode); // 'landlock'Intercept package install commands (npm install, pip install, yarn add, etc.) and check packages against security providers before allowing installation. Disabled by default.
const sandbox = await secureSandbox(vercel(raw), {
packageChecks: {}, // Enable with free defaults (OSV, deps.dev, local)
});| Option | Type | Default | Description |
|---|---|---|---|
scope |
'new_packages_only' | 'all_installs' |
'new_packages_only' |
Check new packages only, or re-check on every install |
providers |
Record<string, boolean | ProviderConfig> |
3 free defaults | Map of provider name to config |
When enabled, three free providers are active:
| Provider | Priority | What It Checks |
|---|---|---|
local |
0 | Local package metadata analysis |
osv |
1 | Known vulnerabilities via OSV.dev |
depsdev |
2 | License, scorecard, and dependency info via deps.dev |
const sandbox = await secureSandbox(vercel(raw), {
packageChecks: {
providers: {
socket: { apiKeyEnv: 'SOCKET_API_KEY' }, // Add Socket.dev
snyk: { apiKeyEnv: 'SNYK_TOKEN' }, // Add Snyk
},
},
});const sandbox = await secureSandbox(vercel(raw), {
packageChecks: {
providers: { depsdev: false }, // Disable deps.dev
},
});| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Enable/disable this provider |
priority |
number |
varies | Lower = higher priority |
timeout |
string |
provider default | Timeout duration (e.g. '30s') |
onFailure |
'warn' | 'deny' | 'allow' | 'approve' |
provider default | Action when provider fails |
apiKeyEnv |
string |
— | Environment variable holding the API key |
type |
'exec' |
— | Provider type (use 'exec' for custom command-based providers) |
command |
string |
— | Command to execute (for 'exec' type providers) |
options |
Record<string, unknown> |
— | Additional provider-specific options |
Package rules in the policy control what happens when a provider reports a finding. The agentDefault() preset includes sensible defaults:
- Block: critical vulnerabilities, malware, typosquats, AGPL/SSPL licenses
- Warn: medium vulnerabilities
- Approve (human-in-the-loop): packages less than 30 days old
Customize via the policy:
import { agentDefault, merge } from '@agentsh/secure-sandbox/policies';
const policy = merge(agentDefault(), {
packageRules: [
{ match: { packages: ['lodash'] }, action: 'allow', reason: 'Trusted package' },
{ match: { findingType: 'vulnerability', severity: 'low' }, action: 'allow' },
],
});
const sandbox = await secureSandbox(vercel(raw), { policy, packageChecks: {} });| Field | Type | Description |
|---|---|---|
packages |
string[] |
Exact package names to match |
namePatterns |
string[] |
Glob/regex patterns for package names |
findingType |
string |
Finding type ('vulnerability', 'malware', 'license', 'reputation') |
severity |
string | string[] |
Severity level(s) to match |
reasons |
string[] |
Reasons to match (e.g. ['typosquat']) |
licenseSpdx |
{ allow?: string[]; deny?: string[] } |
SPDX license criteria |
ecosystem |
string |
Package ecosystem (e.g. 'npm', 'pip') |
options |
Record<string, unknown> |
Additional match options |
Define external secret backends in the policy so HTTP services can reference secrets by name. Secrets are fetched at runtime by the agentsh server — the agent never sees raw credentials.
import { agentDefault, merge } from '@agentsh/secure-sandbox/policies';
const policy = merge(agentDefault(), {
providers: {
'my-api-key': { type: 'keyring' },
'vault-secret': {
type: 'vault',
address: 'https://vault.example.com',
namespace: 'prod',
auth: { method: 'approle', roleId: 'my-role', secretId: 'my-secret' },
},
'aws-secret': { type: 'aws-sm', region: 'us-east-1' },
'gcp-secret': { type: 'gcp-sm', projectId: 'my-project' },
'azure-secret': { type: 'azure-kv', vaultUrl: 'https://my-vault.vault.azure.net' },
'op-secret': { type: 'op', serverUrl: 'https://my.1password.com' },
},
});| Type | Required Fields | Description |
|---|---|---|
keyring |
— | OS keyring (no config needed) |
vault |
address |
HashiCorp Vault with optional namespace and auth |
aws-sm |
region |
AWS Secrets Manager |
gcp-sm |
projectId |
Google Cloud Secret Manager |
azure-kv |
vaultUrl |
Azure Key Vault |
op |
serverUrl |
1Password Connect with optional apiKey / apiKeyRef |
The vault provider supports three auth methods via the auth object:
| Method | Fields |
|---|---|
token |
token or tokenRef (reference to a secret containing the token) |
approle |
roleId / roleIdRef, secretId / secretIdRef |
kubernetes |
kubeRole, kubeMountPath, kubeTokenPath |
Expose external HTTP APIs to the agent through the agentsh proxy with automatic credential injection. The agent calls a local URL; agentsh injects credentials from a secret provider and proxies to the upstream — the agent never sees the raw API key.
import { agentDefault, merge } from '@agentsh/secure-sandbox/policies';
const policy = merge(agentDefault(), {
providers: {
'github-token': { type: 'keyring' },
},
httpServices: [
{
name: 'github',
upstream: 'https://api.github.com',
exposeAs: 'github.local',
default: 'deny',
secret: { ref: 'github-token', format: 'bearer' },
inject: {
header: { name: 'Authorization', template: '{{secret}}' },
},
rules: [
{ name: 'repos', paths: ['/repos/**'], decision: 'allow' },
{ name: 'user', paths: ['/user'], decision: 'allow' },
],
scrubResponse: true,
},
],
});The agent sees github.local and can curl http://github.local/repos/org/repo — agentsh resolves it to api.github.com, injects the Authorization: Bearer <token> header, and proxies the request.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Service identifier |
upstream |
string |
Yes | Upstream URL to proxy to |
exposeAs |
string |
No | Local hostname the agent uses to reach this service |
aliases |
string[] |
No | Additional local hostnames |
allowDirect |
boolean |
No | Allow direct access to upstream (bypass proxy) |
default |
'allow' | 'deny' |
No | Default decision for unmatched paths |
rules |
HttpServiceRule[] |
No | Path-based access rules |
secret |
{ ref, format } |
No | Secret provider reference for credential injection |
inject |
{ header: { name, template } } |
No | How to inject the secret into requests |
scrubResponse |
boolean |
No | Strip injected credentials from responses |
Each rule controls access to specific paths:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Rule identifier |
paths |
string[] |
Yes | Glob patterns for matching request paths |
methods |
string[] |
No | HTTP methods to match (default: all) |
decision |
'allow' | 'deny' | 'approve' | 'audit' |
Yes | What to do when matched |
message |
string |
No | Message shown when rule triggers |
timeout |
string |
No | Request timeout (e.g. '30s') |
HMAC-chain audit log integrity (v0.18.0+). Each audit record includes a cryptographic hash linking it to the previous record, creating a tamper-evident chain. If any record is modified or deleted, the chain breaks.
const sandbox = await secureSandbox(adapter, {
serverConfig: {
audit: {
enabled: true,
sqlitePath: '/var/lib/agentsh/audit.db',
integrity: {
enabled: true,
algorithm: 'hmac-sha256',
keySource: 'file',
keyFile: '/etc/agentsh/hmac.key',
},
},
},
});| Source | Required Fields | Description |
|---|---|---|
file |
keyFile |
Read HMAC key from a local file |
env |
keyEnv |
Read HMAC key from an environment variable |
aws_kms |
awsKms: { keyId, region } |
AWS KMS envelope encryption (optional encryptedDekFile) |
azure_keyvault |
azureKeyVault: { vaultUrl, keyName } |
Azure Key Vault (optional keyVersion) |
hashicorp_vault |
hashicorpVault: { address, secretPath } |
HashiCorp Vault (optional authMethod, tokenFile, kubernetesRole, approleId, secretId, keyField) |
gcp_kms |
gcpKms: { keyName } |
Google Cloud KMS (optional encryptedDekFile) |
The Sprites adapter wraps a @fly/sprites Sprite instance for use with Firecracker microVMs on Sprites.dev.
import { secureSandbox } from '@agentsh/secure-sandbox';
import { sprites, spritesDefaults } from '@agentsh/secure-sandbox/adapters/sprites';
import { SpritesClient } from '@fly/sprites';
const client = new SpritesClient(process.env.SPRITES_TOKEN);
const sprite = client.sprite('my-sprite');
const sandbox = await secureSandbox(sprites(sprite), {
...spritesDefaults(),
// your overrides
});
const result = await sandbox.exec('echo hello');
await sandbox.stop(); // calls sprite.delete()Creates a SandboxAdapter from a Sprites Sprite instance. Uses sprite.execFile('sh', ['-c', cmd]) internally because the SDK's sprite.exec() does a naive whitespace split without shell parsing. File operations use base64 encode/decode piped through sh.
Returns Sprites-optimized Partial<SecureConfig> with sensible defaults for Firecracker microVMs:
installStrategy: 'preinstalled'— binary baked into the VM imagerealPaths: true— use real host paths- Extended server config: gRPC, audit logging, resource limits, cgroups, DLP, metrics, health checks
Spread into your config and override as needed:
const sandbox = await secureSandbox(sprites(sprite), {
...spritesDefaults(),
policy: myPolicy,
watchtower: 'https://watchtower.example.com',
});The serverConfig field on SecureConfig accepts additional server configuration sections that are merged into the generated config.yml. These are primarily useful for Sprites, Modal, and other advanced deployments:
| Section | Description |
|---|---|
grpc |
gRPC server endpoint |
serverTimeouts |
HTTP read/write timeouts and max request size |
logging |
Log level, format, and output |
sessions |
Session base dir, limits, timeouts, cleanup |
audit |
SQLite audit logging with optional HMAC integrity chain |
sandboxLimits |
Memory, CPU, and process limits |
allowDegraded |
Start sandbox even if FUSE/seccomp fail (useful for gVisor) |
fuse |
FUSE enable flag, deferred mode, marker file, and enable command |
networkIntercept |
Network intercept mode and proxy address |
seccompDetails |
Execve filtering and file monitor |
cgroups |
Cgroup isolation |
unixSockets |
Unix socket support |
ptrace |
Ptrace-based syscall interception (see Ptrace Config) |
envInject |
Environment variables to inject into sandbox processes |
proxy |
MITM proxy mode, port, and provider URLs |
dlp |
Data loss prevention (redact mode, patterns) |
policiesOverride |
Override default policies directory |
approvals |
Human-in-the-loop approval settings |
metrics |
Prometheus metrics endpoint |
health |
Health and readiness check paths |
development |
Development mode flags |
The ptrace section enables ptrace-based syscall interception, used on gVisor platforms where seccomp user-notify is unavailable. Ptrace is mutually exclusive with seccomp execve filtering and unix socket interception.
serverConfig: {
ptrace: {
enabled: true, // Master switch (default: false)
attachMode: 'children', // 'children' (PTRACE_SEIZE on children) or 'pid' (attach to specific PID)
maskTracerPid: 'off', // Hide tracer PID from /proc/*/status
trace: {
execve: true, // Intercept command execution (execve/execveat)
file: true, // Intercept file operations (openat, unlinkat, renameat2, etc.)
network: true, // Intercept network calls (connect, bind) + DNS proxy
signal: true, // Intercept signals (kill, tgkill, tkill)
},
performance: {
seccompPrefilter: false, // BPF pre-filter for performance (disable on gVisor)
maxTracees: 500, // Maximum concurrent traced threads
maxHoldMs: 5000, // Maximum time to hold a syscall (ms)
},
onAttachFailure: 'fail_open', // 'fail_open' (continue) or 'fail_closed' (abort)
},
}The Modal adapter wraps a Modal sandbox for use with gVisor-based sandboxes on Modal. Since gVisor doesn't support seccomp user-notify or Landlock, the adapter uses ptrace-based enforcement.
import { secureSandbox } from '@agentsh/secure-sandbox';
import { modal, modalDefaults } from '@agentsh/secure-sandbox/adapters/modal';
const sandbox = await secureSandbox(modal(modalSandbox), {
...modalDefaults(),
// your overrides
});
const result = await sandbox.exec('echo hello');
await sandbox.stop(); // calls sandbox.terminate()Creates a SandboxAdapter from a Modal sandbox object. The sandbox must implement exec(...args) returning a process with wait(), stdout.read(), stderr.read(), and returncode. File operations use base64 encode/decode piped through sh. Modal containers run as root so the sudo flag is dropped.
Returns Modal-optimized Partial<SecureConfig> with sensible defaults for gVisor/ptrace environments:
installStrategy: 'download'— download agentsh binary from GitHub releasesrealPaths: true— use real host paths- ptrace enabled with all trace subsystems (execve, file, network, signal)
seccompPrefilter: false— gVisor blocks BPF injectionallowDegraded: true— graceful fallback for FUSE/seccomp- FUSE deferred with marker file
- unix sockets and cgroups disabled (Modal handles resource limits)
- DLP, audit logging, metrics, and health checks enabled
Spread into your config and override as needed:
const sandbox = await secureSandbox(modal(modalSandbox), {
...modalDefaults(),
policy: myPolicy,
watchtower: 'https://watchtower.example.com',
});The Runloop adapter wraps a Runloop devbox for use with persistent cloud development environments.
import { secureSandbox } from '@agentsh/secure-sandbox';
import { runloop, runloopDefaults } from '@agentsh/secure-sandbox/adapters/runloop';
import RunloopSdk from '@runloop/api-client';
const client = new RunloopSdk();
const devbox = await client.devboxes.createAndAwaitRunning({});
const sandbox = await secureSandbox(runloop({ client, id: devbox.id }), {
...runloopDefaults(),
});
const result = await sandbox.exec('echo hello');
await sandbox.stop(); // shuts down the devboxCreates a SandboxAdapter from a Runloop SDK client and devbox ID. Commands are executed via client.devboxes.executeSync(). File operations use base64 encode/decode.
Returns Runloop-optimized Partial<SecureConfig> with a standalone high-security PolicyDefinition:
installStrategy: 'download'— download agentsh from GitHub releasesrealPaths: true— use real host paths- Deny-by-default file, network, and command rules
- Full server config: gRPC, audit logging, FUSE deferred, cgroups, DLP, ptrace (execve only)
The exe.dev adapter wraps a persistent exe.dev VM accessed via SSH through the exe.dev gateway. exe.dev VMs have full kernel capabilities, enabling all enforcement layers.
import { secureSandbox } from '@agentsh/secure-sandbox';
import { exe, exeDefaults } from '@agentsh/secure-sandbox/adapters/exe';
// VM already created: ssh exe.dev new --name=my-vm --image=ubuntu:22.04
const sandbox = await secureSandbox(exe('my-vm'), {
...exeDefaults(),
});
const result = await sandbox.exec('echo hello');
// stop() is a no-op — exe.dev VMs are persistent
// Destroy externally: ssh exe.dev rm my-vm
await sandbox.stop();Creates a SandboxAdapter from an exe.dev VM name. The VM must already exist (created via ssh exe.dev new). All commands route through the exe.dev SSH gateway: ssh exe.dev ssh <vmName> <command>.
Uses child_process.execFile (no local shell) with single-quote escaping for the gateway shell layer. Default timeout: 120s, max buffer: 50MB.
exec()— shell-escapes commands, supportssudo,cwd,env, anddetachedoptionswriteFile()— base64-encodes content and pipes through SSHreadFile()—catvia SSHstop()— no-op (exe.dev VMs are persistent; destroy externally viassh exe.dev rm <vmName>)fileExists()—test -fvia SSH (enables skipping agentsh download when already installed)
Returns exe.dev-optimized Partial<SecureConfig> with full enforcement and a standalone high-security PolicyDefinition:
installStrategy: 'download'— skips download automatically if agentsh is already installedallowDegraded: false— exe.dev has full kernel capabilities; do not degrade- All enforcement layers: ptrace + seccomp + Landlock + FUSE + cgroups
- Deny-by-default file rules with explicit workspace allows (
/root,/workspace) - Network: localhost + package registries only (no LLM providers, no GitHub)
- Blocks exe.dev internals:
shelley,iptables,systemctl - DLP with custom patterns for OpenAI, Anthropic, AWS, GitHub, JWT, Slack tokens
- Conservative resource limits: 2GB RAM, 50% CPU, 100 PIDs
- Full audit logging (all operations logged)
Spread into your config and override as needed:
const sandbox = await secureSandbox(exe('my-vm'), {
...exeDefaults(),
policy: myPolicy,
});The Freestyle adapter wraps a Freestyle Firecracker-backed Linux VM created via the freestyle-sandboxes SDK. Freestyle exposes a typed filesystem API and a declarative VmSpec builder, which lets you bake agentsh into the VM image at snapshot time and skip the cold-start install entirely.
import { secureSandbox } from '@agentsh/secure-sandbox';
import {
freestyle,
freestyleDefaults,
configureFreestyleSpec,
} from '@agentsh/secure-sandbox/adapters/freestyle';
import { freestyle as freestyleClient, VmSpec } from 'freestyle-sandboxes';
const fs = freestyleClient({ apiKey: process.env.FREESTYLE_API_KEY });
// Bake agentsh into a snapshot — fastest cold boots
const { vm } = await fs.vms.create({
spec: configureFreestyleSpec(new VmSpec().snapshot()),
});
const sandbox = await secureSandbox(freestyle(vm), {
...freestyleDefaults(),
installStrategy: 'preinstalled',
});
const result = await sandbox.exec('echo hello');
await sandbox.stop(); // calls vm.stop()Creates a SandboxAdapter from a Freestyle VM instance. Unlike shell-only adapters, Freestyle's typed vm.fs.* API is used directly for file I/O — no base64-over-exec workaround.
The adapter expects these methods on vm:
vm.exec({ command, timeoutMs? }) → Promise<{ stdout?, stderr?, statusCode? }>vm.fs.writeTextFile(path, content)/vm.fs.writeFile(path, Buffer)— string vs binaryvm.fs.readTextFile(path)vm.fs.exists(path)vm.stop()
exec() wraps every command in sh -c so cwd, env, sudo, and shell metacharacters work consistently. detached commands are wrapped in nohup sh -c ... & so they survive the parent shell exit. fileExists() lets the provisioner skip the agentsh download when the binary is already baked into the snapshot.
Returns Freestyle-optimized Partial<SecureConfig> with a standalone PolicyDefinition ported from the production agentsh-freestyle/default.yaml:
installStrategy: 'download'— defaults to runtime install; set'preinstalled'when usingconfigureFreestyleSpecrealPaths: trueworkspace: '/home/user'— matches Freestyle VM homeallowDegraded: true— Freestyle kernels lack Yama, so seccompfileMonitoris disabled and the sandbox settles intominimalsecurity mode (per-command seccomp wrapper + network proxy + FUSE soft-delete + cgroups)- FUSE deferred — enabled at first session start via
sudo /bin/chmod 666 /dev/fuse, guarded by/tmp/.agentsh-fuse-enabled - seccomp
fileMonitor: false— conflicts with FUSE on Yama-less kernels - DLP with custom patterns for OpenAI, Anthropic, AWS, GitHub, JWT, Slack tokens
- Two-tier resource caps: outer server bound at 4 GB / 100% CPU / 256 procs, inner per-policy
resourceLimitsat 2 GB / 50% CPU / 100 PIDs - Workspace allows
/home/user/**and/workspace/**; denies/etc/systemd/**,/run/systemd/**,/usr/bin/envd,/usr/bin/socat, and other Freestyle infrastructure - Network: localhost + npm/PyPI/crates/Go module registries only; blocks cloud metadata IPs and the Freestyle internal events service
Note on
commands: the reference YAML usesargs_patternsto gatenpm install,pip install, etc. behind an approval step. The TSCommandRuleSchemais a{allow}/{deny}/{redirect}union with noargs_patternsfield, so dev tools stay allowed at the command layer. The real enforcement for untrusted installs is the network allowlist (only registries are reachable). Consumers who need per-subcommand approval should bypassfreestyleDefaults()and load the raw YAML via agentsh's own policy loader.
Bakes agentsh into a Freestyle VmSpec by adding apt deps, install/startup scripts, and two systemd units: a oneshot installer and the agentsh server. Call this on a fresh VmSpec (typically new VmSpec().snapshot()) before passing it to fs.vms.create.
const spec = configureFreestyleSpec(new VmSpec().snapshot(), {
agentshVersion: '0.18.0', // optional, defaults to library-pinned version
policyYaml: customPolicyYaml, // optional, defaults to freestyleDefaults() policy
configYaml: customServerConfigYaml, // optional, defaults to freestyleDefaults() server config
});The agentsh systemd service is wired with Requires=install-agentsh.service (hard dependency) so the server never starts if installation failed.
agentshVersion is validated against ^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$ before being substituted into the install script — invalid values throw rather than risking shell injection.
After the snapshot boots, agentsh is already running and the shell shim is installed, so pair this with installStrategy: 'preinstalled' to skip the runtime install path entirely.
Any sandbox that can run commands works with secureSandbox(). Implement the SandboxAdapter interface:
import { secureSandbox } from '@agentsh/secure-sandbox';
import type { SandboxAdapter } from '@agentsh/secure-sandbox';
const myAdapter: SandboxAdapter = {
async exec(cmd, args, opts) {
// Your sandbox's exec implementation
return { stdout: '', stderr: '', exitCode: 0 };
},
async writeFile(path, content) { /* ... */ },
async readFile(path) { /* ... */ },
};
const sandbox = await secureSandbox(myAdapter);The adapter must implement three methods:
| Method | Signature | Description |
|---|---|---|
exec |
(cmd: string, args: string[], opts?: ExecOpts) => Promise<ExecResult> |
Execute a command in the sandbox |
writeFile |
(path: string, content: string) => Promise<void> |
Write a file to the sandbox filesystem |
readFile |
(path: string) => Promise<string> |
Read a file from the sandbox filesystem |
Mock utilities for unit testing without a real sandbox:
import { mockSecuredSandbox } from '@agentsh/secure-sandbox/testing';
const sandbox = mockSecuredSandbox({
execResults: [{ stdout: 'hello\n', stderr: '', exitCode: 0 }],
securityMode: 'full',
});
const result = await sandbox.exec('echo hello');
expect(result.stdout).toBe('hello\n');mockSecuredSandbox returns a SecuredSandbox that replays canned responses without any sandbox or policy engine. Use it in unit tests to verify your agent logic without spinning up real infrastructure.
| Option | Type | Default | Description |
|---|---|---|---|
execResults |
ExecResult[] |
[] |
Responses to return from exec(), consumed in order |
readFileResults |
string[] |
[] |
Responses to return from readFile() |
writeFileResults |
WriteFileResult[] |
[] |
Responses to return from writeFile() |
securityMode |
SecurityMode |
'full' |
The securityMode property value |
sessionId |
string |
Random UUID | The sessionId property value |