tool-exec
Governed tool-call / sandboxed-exec primitive, a default-deny command allowlist, Zod-strict argv validation, and execFile arg-arrays only, never a shell.
@caisson/tool-exec is the security floor Agentic-Dev's tool layer stands on: a governed gate
between an agent's tool call and a real process spawn. A call names a registered logical command;
anything unregistered is refused before anything spawns.
What it does
- Default-deny command allowlist.
createToolExec({ allowlist }): an empty or absent allowlist refuses every call, fail-closed. An unregistered name throwsNotFoundErrorbefore a process is spawned. execFilearg-arrays only. NeverexecSync/exec/shell: true, never a concatenated command string. Each registered command declares a real executable plus a Zod-.strict()schema its args must satisfy; args are validated withparseStrictand the validated result becomes the exact argv array passed toexecFile: no agent-supplied value ever reaches a shell.- Structured argument provenance. Every call returns
{ command, args, exitCode, stdout, stderr, ok, reason?, at }, a plain-data audit record, output bounded to 64KB. A non-zero exit resolves in the record rather than throwing; only an unregistered name or a schema failure throws. - Everything injected.
cwd,timeoutMs, the spawn seam (execFn), and the clock (now) are all config, no module-level secrets or constants for endpoints or executables.
Install
bun add @caisson/tool-execQuickstart
import { z } from "zod";
import { createToolExec } from "@caisson/tool-exec";
const toolExec = createToolExec({
allowlist: [
{
name: "git-status",
command: "/usr/bin/git",
argsSchema: z.array(z.string()).max(1).default(["status"]),
},
],
cwd: "/repo",
timeoutMs: 30_000,
});
const result = await toolExec.run("git-status", ["status"], "agent-turn-14");
// result.ok / result.exitCode / result.stdout / result.args (the resolved argv array)A name not on the allowlist, or args that fail the schema, throw before any process spawns.
Configuration
ToolExecConfig:
allowlist: CommandSpec[]: required. Each entry is{ name, command, argsSchema }.cwd?: string: defaults toprocess.cwd().timeoutMs?: number: defaults to30_000.execFn?: ExecFn: inject a spawn double for tests; defaults to a realexecFilecall.now?: () => number: inject a clock for testable provenance timestamps; defaults toDate.now.
Composition
tool-exec depends on @caisson/kernel for NotFoundError and parseStrict: the same
default-deny and schema-validation primitives the rest of the base substrate uses, so a tool call's
failure modes look like every other kernel-governed boundary. It's the gate Agentic-Dev's agent
kernel and agent-runner reach for whenever a running agent needs to touch a real executable.
Commercial, Agentic-Dev
@caisson/tool-exec ships under the Agentic-Dev bundle license.
agent-runner
Spawn a headless coding agent in an isolated worktree with a scrubbed, from-scratch env, zero secret leak by construction, an auditable .jsonl transcript, and a structured run report.
Provenance
Cryptographic provenance, detached signing over a canonical manifest, an append-only WORM audit chain, and per-tenant field encryption. Proof, not a promise.