Skip to content

@amends-dev/sandbox-e2b

@amends-dev/sandbox-e2b implements the sandbox contract with E2B’s TypeScript SDK. Hosts install this adapter; shared packages depend only on the contract.

import { E2bSandboxProvider } from "@amends-dev/sandbox-e2b"
import { BunCrypto } from "@effect/platform-bun"
import { Config, Effect, Layer } from "effect"
const sandboxes = Layer.unwrap(
Effect.map(Config.Redacted("E2B_API_KEY"), (apiKey) => E2bSandboxProvider.layer({ apiKey }))
).pipe(Layer.provide(BunCrypto.layer))

make constructs the provider and needs Effect’s Crypto service. layer delegates to it. Options include apiKey, apiUrl, domain, template, timeoutMs, and requestTimeoutMs. providerOptions.e2b can override template and timeoutMs for a create. The SDK version is pinned in the root catalog. Credentials are supplied explicitly; the adapter does not read them from SDK environment variables.

Each logical sandbox ID is recorded in E2B metadata beside amends-provider=e2b. Resume queries both running and paused sandboxes, including every page, so a fresh host can find the same machine. Unknown IDs fail without allocation. A duplicate logical ID fails without modifying the existing machine. Sandboxes outlive the provider; createScoped destroys its sandbox on scope exit.

E2B does not provide atomic reservation of a custom sandbox name. Creates are serialized within one provider and check metadata before and after allocation. A detected concurrent create deletes only its own allocation. Two hosts can still race while each sees no other allocation. If duplicates exist, resume fails with UnknownError instead of selecting a machine arbitrarily. Applications that create the same explicit ID on different hosts must serialize those creates externally. IDs generated by the adapter are UUIDs. E2B volumes are not used for naming because they require access to a separate private beta.

stop uses pause({ mode: "filesystem" }), which preserves files and ends processes. Resume cold-boots the machine. Repeated stop and destroy succeed. E2B requires a maximum running lifetime: the default here is one hour, after which E2B pauses with filesystem-only persistence. Set timeoutMs within the account’s permitted limit. The host must resume the sandbox and restart its services after that pause; the adapter does not renew timeouts in the background. Auto-resume on port traffic is disabled because it cannot restart services after a filesystem-only pause.

A contract template prepares each new machine, then its network policy applies. Preparation failure or interruption destroys that allocation before returning. Template snapshots are not cached; template can select an E2B template or previously prepared snapshot supplied by the operator.

The image must provide Linux /proc, POSIX shells, Python 3 for the conformance server, and GNU core utilities including setsid, stat, find, base64, and mktemp. E2B’s base image supplies the tools used by the adapter. File reads and writes transfer whole files. File handles, watch, glob, and append flags are unsupported and fail with PlatformError. Exclusive-write checks and mode application use multiple API calls and are not atomic against other writers.

Each command runs in a process group with stdout and stderr written to private files. Polling reads them as bytes, preserving non-UTF-8 data and final output without a newline. stdin uses E2B’s byte API and closes when its sink ends. Environment scripts avoid placing environment values in recorded command lines. Commands support cwd, environment replacement or extension, pipelines, shell mode, ignored streams, and sinks. Extra file descriptors and host-inherited streams fail.

Scope exit kills a referenced command’s process group and removes its scratch directory. unref leaves the process running after its scope closes. kill uses SIGKILL regardless of the requested signal. Handles capture the Linux boot ID and refuse to signal an old PID after resume. Unreferenced commands keep their output files until removed by sandbox code or sandbox destruction. Output polling costs API requests and holds at most one MiB per read, while output files can grow until their command’s scope closes. Output is unavailable while the sandbox is paused.

Port markers in /var/tmp/amends/ports survive pause and make ports exposed through one handle visible to another. Independent markers avoid lost updates when hosts expose different ports. Endpoints use HTTPS or WSS and E2B’s e2b-traffic-access-token header; other headers, including Authorization, remain available to the application. E2B’s private traffic mode applies to the machine, while port markers describe which endpoints this adapter returns; they are not per-port firewall rules.

Allow-all, deny-all, and allow lists of hosts and CIDRs without denied exceptions are supported. An allow list with deniedCidrs fails with UnsupportedError before allocation: E2B’s allow rules override deny rules, while the contract requires the opposite. An empty allow list blocks egress. Host filtering follows E2B’s HTTP Host/TLS SNI filtering on ports 80/443 and its DNS exception; other protocols require IP/CIDR allowances. See E2B’s network documentation.

Request transformations fail with UnsupportedError. E2B’s per-host transforms lack the contract’s path-prefix and matching-header conditions; the adapter never broadens credential injection.

Terminal window
bun run --cwd adapters/sandbox-e2b typecheck
bun run test -- adapters/sandbox-e2b/test/provider.test.ts
AMENDS_TEST_E2B=1 E2B_API_KEY=... bun run test -- adapters/sandbox-e2b/test

The default tests exercise the real SDK against a local fake control plane, including error mapping, network preflight, duplicate detection, cleanup ownership, pagination, and lifecycle payloads. Live tests run the shared conformance suites and E2B-specific reboot, binary stdin/EOF, and network regressions. They allocate paid cloud machines and are skipped unless both variables are set. Live verification requires an E2B region that supports filesystem-only persistence; a rejected pause remains an error and is never silently substituted with memory persistence.