import { createSandboxObservability } from "./observability"; import type { PathTranslator } from "./contracts/path_translator"; import type { SandboxPolicy, DerivedRules, SandboxEpoch } from "./types"; import type { SandboxPolicyEnforcer } from "./contracts/policy_enforcer"; type RunOptions = Parameters[0]; /** * 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. */ export interface SandboxHandle { /** * Opaque token issued at construction and invalidated by {@link 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. */ readonly epoch: SandboxEpoch; /** * The 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. */ readonly effectivePolicy: () => DerivedRules | undefined; /** * Spawn under this session's policy, optionally narrowed for the single invocation. * * @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. */ run(options: RunOptions): ReturnType; /** * Narrow the session policy for subsequent operations. * * @param policy - Must 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. */ narrow(policy: SandboxPolicy): Promise; /** Predicate consumed by file-backed readers; disposal makes the epoch unusable. */ isEpochLive(epoch: SandboxEpoch): boolean; /** * Quiesce the session: it does not abandon in-flight work. * * @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. */ dispose(): Promise; } /** Options for {@link createSandbox}. */ export interface CreateSandboxOptions { /** * ADK-owned policy vocabulary, mapped to the backend's config inside the enforcer. * * @remarks * The per-axis defaults DIFFER and are not a symmetry worth "fixing": reads default to ALLOW * (absent `denyRead` means everything is readable, and `allowRead` re-permits WITHIN a deny), while * writes default to DENY and network is an allow-list. A checker that unified them would disagree * with the OS by construction. */ readonly policy: SandboxPolicy; /** The boundary itself. Node's SRT-backed enforcer lives on the `sandbox/node` subpath. */ readonly enforcer: SandboxPolicyEnforcer; /** * Model-path translation, and the redaction used on every observability event. * * @remarks * Supply it if you want host identifiers scrubbed from the event stream — no battery-generated * surface should carry the sandbox root, home directory, or user name. */ readonly translator?: PathTranslator; /** Observability firehose: bypasses, fallbacks, drift outcomes, and the SRT version rules came from. */ readonly onSandbox?: (event: Parameters>[0]) => void; /** * Permit degraded operation when the environment cannot provide OS containment. * * @remarks * Fires only for pre-execution conditions resolved once at construction — a platform the backend * cannot sandbox that we still run on, dependency errors, or an absent optional peer. It NEVER fires * for a violation (a violation means the sandbox worked), and native Windows is refused outright * rather than degraded. When it fires the handle is permanently marked and every invocation emits * a loud observability event. Execution STILL goes through `enforcer.run`; this option never runs * a command unsandboxed, and the tool descriptions tell the model it has no OS containment. */ readonly allowUnsandboxedFallback?: boolean; /** Ignore the per-call escape entirely, matching the reference consumer's strict mode. */ readonly strictMode?: boolean; /** Whether the optional peer resolved; feeds the preflight decision above. */ readonly optionalPeerPresent?: boolean; /** Recorded on observability events so a rules-versus-backend mismatch is diagnosable without a bisect. */ readonly fsNodeVersion?: string; /** Opt in to a real child-spawn liveness check during construction. */ readonly probeSpawn?: boolean; } /** * Admit one process-global sandbox. Drift is detection, not prevention: SRT consults its * proxies per request, so a widening can affect an already-spawned child for its lifetime. */ export declare const createSandbox: (options: CreateSandboxOptions) => Promise; export {};