Deploy securely
These tools grant read/write access to a workspace and arbitrary shell execution with the privileges of the host process. This page is the short, blunt version of how to run them without handing your machine to a model.
The one rule
Run the process inside an OS-level sandbox, scoped to the project you intend the agent to work in:
- a container (Docker/Podman) with only the project mounted, or
- a VM, or
- a dedicated low-privilege user with filesystem ACLs, or
- seccomp / AppArmor / Landlock confinement, or equivalent.
Everything below is defense-in-depth on top of that boundary — not a replacement for it.
Why the sandbox is non-negotiable
Workspace confinement guards the file tools, but bash runs sh -c <command> with the full privileges of the process. A shell command can read, write, and execute anything the process can, regardless of the confinement setting. So the trust boundary is the process: assume whatever drives these tools can run arbitrary code as that user.
Layer these on top
- Confine to the workspace (default). Keep
confineToWorkspace: trueso a stray path in a file tool is apath_escape, not a host read. Only disable it when the root genuinely isn't your trust boundary. - Drop write and shell when you can. If the agent only needs to read, use
readOnly: true— the mutating tools andbasharen't even registered. - Run as an unprivileged user with no access outside the project directory, no ambient cloud credentials, and a minimal
PATH. - Scope the environment. Don't export secrets the agent doesn't need into the process env —
bashcan read all of it. Inject only what a run requires.
Tune the limits
The limits exist so one call can't exhaust memory or flood context. In a long-lived or multi-tenant deployment:
- Set
maxOutputBytesandmaxFileBytesto match your context budget and the repos you expect. - Set a
bashTimeoutMs/bashTimeoutMaxMsthat fits your longest legitimate build or test, and no more — a runaway command is killed at the ceiling. - Sweep spill files.
bashoverflow is written under.clarvis/. CallsweepSpillDir(root)periodically (startup, or on a timer) so old spill files don't accumulate.
import { createAgentTools, sweepSpillDir } from "@clarvis/agent-tools";
const root = "/srv/project";
const tools = createAgentTools({
workspaceRoot: root,
maxOutputBytes: 65536,
maxFileBytes: 10_000_000,
bashTimeoutMs: 60000,
bashTimeoutMaxMs: 300000,
});
setInterval(() => void sweepSpillDir(root), 60 * 60 * 1000); // hourly cleanupChecklist
- [ ] Process runs in a container / VM / low-priv user scoped to one project
- [ ]
confineToWorkspaceleft on (or off only with a deliberate reason) - [ ]
readOnlyon where writes and shell aren't needed - [ ] No unnecessary secrets in the process environment
- [ ]
maxOutputBytes/maxFileBytes/bashtimeouts tuned for the workload - [ ] Spill files swept on a schedule
See also
- Workspace confinement — what the path guard does and doesn't cover
- Read-only mode — the smallest surface
- Limits & spill — the runtime bounds and cleanup