import { type ModelTurn, type ModelRequest } from "./mock-model.js"; /** * How to treat code execution. `"auto"` (default) is safe-by-default: trusted * code runs directly, untrusted code is sandboxed if possible and otherwise * refuses. `false` is the dangerous opt-out — run unconfined (you audited it, or * you trust the outer container). `"strict"` forces confinement even for trusted * code and throws if no sandbox is available. */ export type SandboxMode = "auto" | "strict" | false; /** * Whether this environment can ACTUALLY confine untrusted code under bubblewrap. * **Linux only.** Critically, `bwrap --version` succeeding is NOT enough: many CI * runners and hardened hosts ship bubblewrap but disable the **unprivileged user * namespaces** it depends on, so a real confined exec fails even though the binary * is present. We probe that real capability — a throwaway `bwrap --unshare-all … * true` — and cache it, so we never *claim* confinement we can't deliver. * `decideSandbox` then correctly refuses untrusted code in such an environment * (rather than running it in a "sandbox" that doesn't actually sandbox), and the * sandbox-gated tests skip instead of failing. The result is cached because the * probe spawns a process and the answer can't change within a run. */ export declare function sandboxAvailable(): boolean; /** * Is this spec's executed code trusted? Inline `settings`/`files` you authored * are trusted; any external `plugin` / `pluginDir` brings in third-party hooks * and is NOT — committing it to your repo is the same trust decision as a * dependency, so the trust boundary follows provenance: foreign = confined. */ export declare function specTrusted(spec: { plugin?: string; pluginDir?: string; }): boolean; /** The chosen action for a run: execute directly, confine it, or refuse. */ export type SandboxDecision = { readonly action: "direct"; } | { readonly action: "sandbox"; } | { readonly action: "throw"; readonly reason: string; }; /** * The pure safe-by-default policy. Untrusted code NEVER runs unconfined unless * the caller explicitly opted out (`mode: false`). This is the whole security * contract, isolated as a pure function so it is exhaustively unit-tested. */ export declare function decideSandbox(opts: { trusted: boolean; mode: SandboxMode; available: boolean; }): SandboxDecision; /** * The bubblewrap confinement argv (everything before the command): a fresh * network namespace (`--unshare-all`, loopback-only, no egress), a read-only * system, writable mounts limited to the work dir, the IO dir, and a fresh empty * HOME (inside the IO dir so it needs no mountpoint on the read-only root, and so * no host credentials/config leak in), and a **cleared environment** — * `--clearenv` drops every host variable (API keys, cloud creds) and only PATH / * HOME / TMPDIR are set back, so untrusted code can't even read your secrets. * Pure, so the confinement shape is asserted in a unit test. */ export declare function bwrapArgs(opts: { cwd: string; ioDir: string; home: string; path: string; }): string[]; /** * `--setenv K V` pairs to add back specific variables after `--clearenv` — e.g. * a hook's configured env (the `GUARD=path` a plugin's command relies on), which * `bwrapArgs`' `--clearenv` would otherwise drop. Pure, so it's unit-tested. */ export declare function setenvArgs(env: Record): string[]; /** Parse the in-sandbox mock's ndjson request log into {@link ModelRequest}s. */ export declare function parseRequestLog(ndjson: string): ModelRequest[]; /** A network egress attempt a confined hook made — recorded, then blocked. */ export interface EgressAttempt { readonly host: string; readonly port: number; /** ms epoch when the attempt was recorded. */ readonly ts: number; /** * Allowlist mode (`egress: { allow }`) only: whether this host was on the * allowlist (and so reachable). Undefined for the `recordEgress` proxy, where * every attempt is blocked. */ readonly allowed?: boolean; /** Allowlist mode: packets the nftables counter recorded for this host. */ readonly packets?: number; /** Allowlist mode: bytes the nftables counter recorded for this host. */ readonly bytes?: number; } /** * Parse the egress recorder's ndjson log into {@link EgressAttempt}s. Pure, so * the record-shape and the malformed-line tolerance are unit-tested without a * sandbox. A line missing host/port is skipped (a partially-flushed final line). */ export declare function parseEgressLog(ndjson: string): EgressAttempt[]; /** * The files in `after` that are new or changed vs `before` — i.e. what a confined * run wrote to its work dir. Each tree maps a relative path to a content * signature (size + mtime). Pure, so the diff is unit-tested without a sandbox. */ export declare function diffTrees(before: Readonly>, after: Readonly>): string[]; /** The raw output of a sandboxed run: exit code, captured stdout, and requests. */ export interface SandboxRunOut { readonly code: number; readonly stdout: string; readonly requests: readonly ModelRequest[]; } export declare function runSandboxed(opts: { cwd: string; claudeArgs: readonly string[]; script: readonly ModelTurn[]; timeoutMs: number; }): Promise; //# sourceMappingURL=sandbox.d.ts.map