@b4run/sandbox
Use this whenCopy link to section: Use this when
Use this Node-only package when a B4.run application needs the reference Docker or Kubernetes SandboxProvider, or when a provider author needs the shared conformance suite. The portable sandbox interfaces are re-exported for discovery, but their canonical field owner is Workspace.
Install and importCopy link to section: Install and import
npm install @b4run/sandbox
npm install -D vitestimport { dockerSandbox, kubernetesSandbox } from "@b4run/sandbox"
import { fakeSandbox, runProviderConformance } from "@b4run/sandbox/testing"Compatibility and audienceCopy link to section: Compatibility and audience
| Surface | Runtime | Purity | Audience | Stability |
|---|---|---|---|---|
@b4run/sandbox | node-only | not-claimed | application | supported |
@b4run/sandbox/testing | node-only | not-claimed | testing | supported |
The root uses Node APIs, Docker process execution, and the Kubernetes client. /testing imports Vitest and is test-facing. Ordinary package tests use fakes. Real-provider evidence lives in gated Docker and Kubernetes integration lanes.
Public exportsCopy link to section: Public exports
@b4run/sandboxCopy link to section: @b4run/sandbox
| Export | Responsibility |
|---|---|
SandboxConfig | Re-export the portable application sandbox configuration from Workspace. |
SandboxHandle | Re-export the portable acquired-handle contract from Workspace. |
SandboxPolicy | Re-export portable network, environment, resource, and security intent from Workspace. |
SandboxProvider | Re-export the portable provider lifecycle contract from Workspace, including the optional workspace read capability. |
DockerSandboxOptions | Configure the default Docker image, the images a per-thread sandbox may name, and the optional injected Docker seam. |
dockerSandbox | Create the Docker provider. |
KubeClient | Describe the public Kubernetes client seam; helper request shapes remain implementation details. |
KubePermission | Describe one Kubernetes authorization check used by the public client seam. |
KubeAuthorizationReviewError | Preserve API-versus-transport failure classification from custom Kubernetes clients. |
KubernetesSandboxOptions | Configure the Kubernetes image, namespace, storage class, timeout, and client seam. |
kubernetesSandbox | Create the Kubernetes provider. |
@b4run/sandbox/testingCopy link to section: @b4run/sandbox/testing
| Export | Responsibility |
|---|---|
fakeSandbox | Create an in-memory provider for unit and wiring tests. |
runProviderConformance | Register Vitest cases for persistence, isolation, and execution behavior. |
Key contractsCopy link to section: Key contracts
export interface KubernetesSandboxOptions {
readonly scope: string
readonly image: string
readonly namespace?: string
readonly storageClass?: string
readonly startupTimeoutMs?: number
readonly client?: KubeClient
}export declare class KubeAuthorizationReviewError extends Error {
readonly kind: "api" | "transport"
constructor(kind: "api" | "transport", message: string, options?: ErrorOptions)
}Custom KubeClient.canI implementations should throw this error when an
authorization review cannot complete. Use api when the Kubernetes API answers
but rejects or cannot process the review, and transport when the API cannot be
reached.
export declare function dockerSandbox(opts: DockerSandboxOptions): SandboxProviderexport declare function kubernetesSandbox(opts: KubernetesSandboxOptions): SandboxProviderexport declare function runProviderConformance(opts: {
readonly name: string
readonly makeProvider: () => SandboxProvider
readonly describe: (name: string, fn: () => void) => void
readonly workspaceReads?: boolean
}): voidworkspaceReads declares the optional openWorkspaceReader capability. Setting it registers the workspace-read contract. Omitting it asserts the provider does not implement it. Either way, the kit registers a test that checks the declaration against the provider. No test is skipped, because a skipped test would make an unchecked contract look like a passing one.
Behavior contractsCopy link to section: Behavior contracts
Behavior contract sandbox.docker.release
Docker release removes the container but retains its volume; destroy removes both.
Behavior contract sandbox.kubernetes.release
Kubernetes release deletes the Pod but retains the PVC; destroy removes both.
Operational cleanup caveat
These are successful-path lifecycle contracts. The cleanup calls swallow provider deletion errors, and the Kubernetes PVC wait stops after 30 seconds. Operators must detect and reap leftovers.
Behavior contract sandbox.kubernetes.allow-network
Kubernetes network:allow without an allowlist emits no NetworkPolicy.
Network enforcement caveat
The absence of a provider-created policy does not promise unrestricted egress: cluster defaults, infrastructure policies, and CNI behavior still apply. Preflight checks Kubernetes API reachability and the complete runtime permission set, reporting authorization denials, review failures, and transport failures separately. Unknown CNI enforcement produces a warning. Preflight does not prove NetworkPolicy enforcement.
Behavior contract sandbox.error.create
A failed sandbox container creation is tagged B4_E2001.
Workspace read capability
dockerSandbox implements the optional openWorkspaceReader member of SandboxProvider. It attaches the thread's existing workspace into a separate, ephemeral, networkless container as a read-only bind of the volume's own backing directory, and reads through that container, so the thread's keeper is never inspected, started, stopped, or replaced. Writes fail at the kernel. It reads a thread whose compute was already released, and rejects with B4_E2001 when the thread has no workspace storage. The reader runs as uid/gid 1000:1000 unless the input says otherwise.
The bind is deliberate: a -v <volume>:/workspace:ro mount would create the volume if it were missing, so a reader racing a thread delete would resurrect that thread's workspace as an empty volume and the next acquire() would reattach it. A bind refuses a missing source instead. The cost is that reading needs a volume with a host-visible backing path, which is what the default local driver provides. A volume that reports none is rejected rather than read.
kubernetesSandbox omits the member: a ReadWriteOnce PersistentVolumeClaim cannot be mounted by a second Pod unless it lands on the same node.
fakeSandbox implements it in memory, so runProviderConformance exercises the capability block in ordinary CI.
Provider identity and reattachment caveat
Both provider factories require a nonblank scope: string. Resource names use a stable hash of the scope and thread ID, and handles keep the logical thread ID. Scope is case-sensitive, isn't authorization, and adds no cross-process coordination.
Changing scope selects different storage. That's a breaking change from unscoped providers, because legacy storage is not automatically reattached, migrated, or deleted. A running Kubernetes Pod is reattached without applying a changed image, environment, resources, or security, and a later allow acquire leaves a prior deny policy in place. Release before you change provider options or policy.
Examples and related guidesCopy link to section: Examples and related guides
import { dockerSandbox } from "@b4run/sandbox"
const provider = dockerSandbox({ scope: "my-app", image: "node:24-slim" })
const status = await provider.preflight?.()
if (!status?.ok) throw new Error(status?.detail ?? "Sandbox unavailable")The image must provide the shell and utilities the backends invoke, including sleep, timeout, realpath, stat, and core file utilities. Continue with Execution Sandbox, Kubernetes Sandbox, and Workspace API.