Skip to content
4 min read · 858 words

Interface: SandboxHandle ​

Defined in: src/batteries/sandbox/manager.ts:28

A live sandbox session: the object every sandbox tool is built against.

Remarks ​

THIS IS A PROCESS-GLOBAL CAPABILITY WEARING A PER-HANDLE API, and the shape is a promise narrowed rather than a promise kept. SandboxManager is a singleton, so the first createSandbox() in a process establishes the policy and a later one may only NARROW it — a request that is not a subset throws E_SANDBOX_POLICY_CONFLICT naming both. One policy per process is the safe deployment; multi-tenant agents with different policies want separate processes.

Every run() re-validates against the admission baseline before spawning, so a widening of the live policy is DETECTED. Detection is not prevention: SRT's proxies consult policy per request, so a widening already affects an in-flight child for its whole lifetime.

Properties ​

PropertyModifierTypeDescriptionDefined in
effectivePolicyreadonly() => DerivedRules | undefinedThe live DERIVED rules — not the SandboxPolicy that was requested. Remarks A policy cannot express what the drift check has to compare (denyOnly/allowWithinDeny, the unioned default write paths, Linux-expanded read globs, the profile-only mandatory denies), so returning one here would compare the wrong thing and pass while the live sandbox had widened. Under ADOPTION this re-derives from the live manager on every call; an owned session is cached.src/batteries/sandbox/manager.ts:46
epochreadonlySandboxEpochOpaque token issued at construction and invalidated by SandboxHandle.dispose. Remarks File-backed readers hold this rather than a filesystem reference, which is what makes disposal deterministic: a staged reader outliving its TURN is legitimate, outliving its HANDLE is not.src/batteries/sandbox/manager.ts:36

Methods ​

dispose() ​

ts
dispose(): Promise<void>;

Defined in: src/batteries/sandbox/manager.ts:88

Quiesce the session: it does not abandon in-flight work.

Returns ​

Promise<void>

Remarks ​

Rejects new work with E_SANDBOX_NOT_INITIALIZED, aborts in-flight invocations through their signals, kills spawned children, then releases the enforcer. On an ADOPTED session this is a reported NO-OP — resetting a manager we did not create would strip ACEs a host application depends on.


isEpochLive() ​

ts
isEpochLive(epoch: SandboxEpoch): boolean;

Defined in: src/batteries/sandbox/manager.ts:78

Predicate consumed by file-backed readers; disposal makes the epoch unusable.

Parameters ​

ParameterType
epochSandboxEpoch

Returns ​

boolean


narrow() ​

ts
narrow(policy: SandboxPolicy): Promise<void>;

Defined in: src/batteries/sandbox/manager.ts:76

Narrow the session policy for subsequent operations.

Parameters ​

ParameterTypeDescription
policySandboxPolicyMust be a subset per the per-axis rules; reads are deny-then-allow while writes are allow-only, so "narrower" is not symmetric. Throws E_SANDBOX_NARROWING_UNSUPPORTED naming the axis where the platform cannot narrow it.

Returns ​

Promise<void>


run() ​

ts
run(options: {
  argv: string[];
  correlationId: string;
  cwd: string;
  env?: Record<string, string>;
  policy: SandboxPolicy;
  signal?: AbortSignal;
}): Promise<{
  completed: Promise<{
     exitCode: number;
     failed: boolean;
     signalCode?: string;
  }>;
  stderr: ReadableStream<Uint8Array<ArrayBufferLike>>;
  stdout: ReadableStream<Uint8Array<ArrayBufferLike>>;
}>;

Defined in: src/batteries/sandbox/manager.ts:68

Spawn under this session's policy, optionally narrowed for the single invocation.

Parameters ​

ParameterTypeDescription
options{ argv: string[]; correlationId: string; cwd: string; env?: Record<string, string>; policy: SandboxPolicy; signal?: AbortSignal; }-
options.argvstring[]-
options.correlationIdstring-
options.cwdstring-
options.env?Record<string, string>An ADDITIVE per-call overlay on the child's environment, applied LAST. Remarks NOT the host-inheritance control. What a child inherits from the host is the ADAPTER's decision (the Node/SRT one denies by default and takes an allow-list at construction); this field only adds to whatever that produced, and an adapter MUST NOT let it silently widen what the deployment allowed. Leaving these semantics unstated is how the Node adapter came to spread the entire process.env into every child while this field sat unused.
options.policySandboxPolicy-
options.signal?AbortSignalLifecycle cancellation for this child. Implementations must kill the spawned child and settle completed when this signal aborts.

Returns ​

Promise<{ completed: Promise<{ exitCode: number; failed: boolean; signalCode?: string; }>; stderr: ReadableStream<Uint8Array<ArrayBufferLike>>; stdout: ReadableStream<Uint8Array<ArrayBufferLike>>; }>

Remarks ​

Resolves as soon as the child is SPAWNED, handing back live stdout/stderr streams plus a separate completed promise. Drain BOTH concurrently: pipe buffers are per-fd, so draining one to completion first can block the other and hang the child. A non-zero exit is data on completed, never a rejection.

options.policy is a PER-INVOCATION policy. Its effect is backend-specific and, under the SRT enforcer, axis-specific (measured on macOS/seatbelt AND Linux/bwrap, issue #50, both identical): a per-call FILESYSTEM grant is honoured for that ONE child, REPLACING the session baseline rather than unioning with it — the per-call policy is that child's complete filesystem policy, so it must repeat any baseline writes it still wants. A per-call NETWORK section that DIFFERS from the session's — a new domain, a narrowing, an added deny, a different disabled — is REFUSED with E_SANDBOX_NETWORK_GRANT_UNSUPPORTED (SRT's proxy filters every request against the process-global session config, so neither a per-call grant NOR a per-call narrowing is representable), so network policy is established for the session; a per-call policy must repeat the session's network section exactly (order-insensitive set equality). See per-call policy semantics.