Sandboxed bash, read, write, and edit tools for
@mariozechner/pi-coding-agent
(badlogic/pi-mono) backed by
just-bash.
Out of the box, pi runs shell and filesystem tools against the host. When
pi must be contained to a per-session workspace — no access to the rest of
the host, deterministic cleanup on exit — this extension:
- Replaces
bashwith ajust-bashsandbox running inside a per-session root directory (ReadWriteFswithallowSymlinks: false). - Replaces
read/write/editso they only touch the sandbox root. - Disables
grepat both the tool layer and thetool_callgate — usebash grepinside the sandbox instead. - Creates and cleans up the sandbox root on every
session_start/session_shutdown, onsession_before_switchandsession_before_fork, and onSIGINT/SIGTERM/SIGHUP. - Reaps orphaned sandboxes older than 12 hours at load time.
See docs/ARCHITECTURE.md for the data flow,
docs/RESEARCH.md for the source-verified upstream
facts the design is anchored to, and docs/AGENT_BRIEF.md
for the phase-by-phase implementation roadmap.
This package is not published to npm. Install it from GitHub, a
local clone, or a pinned git ref — pi-mono supports all three natively.
The package ships its built factory under dist/, so consumers only
need to point pi at the repository; no intermediate npm registry is
ever involved.
Prerequisites:
pi(aka@mariozechner/pi-coding-agent) >= 0.67.68 installed and on yourPATH. See badlogic/pi-mono for install instructions. pi supplies@mariozechner/pi-coding-agent,@mariozechner/pi-agent-core, and@sinclair/typeboxat runtime, so consumers do not need to install those separately.
pi-mono's package manager speaks git and local paths directly. Each
form below pulls the repo, runs npm install in it (for just-bash),
builds nothing itself — this repo already commits dist/ on releases
(see Release flow) — and loads the extension.
# Pinned git tag or branch (recommended)
pi install git:github.com/sionic-ai/pi-justbash-sandbox@main
# SSH
pi install git:git@github.com:sionic-ai/pi-justbash-sandbox
# Raw URL also works
pi install https://github.com/sionic-ai/pi-justbash-sandbox
# Local clone (absolute or relative path)
pi install /absolute/path/to/pi-justbash-sandbox
pi install ./pi-justbash-sandboxTo try it without persisting to settings:
pi -e git:github.com/sionic-ai/pi-justbash-sandbox
pi -e /absolute/path/to/pi-justbash-sandboxpi auto-installs missing packages on startup, so this file alone is
enough for collaborators to pick up the sandbox on their next pi run.
Use .pi/settings.json for project-scoped installs and
~/.pi/agent/settings.json for global installs.
When you embed pi inside your own Node or Bun program, import the default factory directly from the cloned repo (no registry lookup):
import { DefaultResourceLoader } from "@mariozechner/pi-coding-agent";
import justbashSandbox from "./vendor/pi-justbash-sandbox/dist/index.js";
const resourceLoader = new DefaultResourceLoader({
extensionFactories: [justbashSandbox],
});Any path resolvable by Node / Bun ESM works here — a git submodule, a
vendored subtree, a workspace package, a file: spec in package.json,
etc. The only hard dependency on the filesystem side is that dist/
has been built (pnpm build / bun run build).
Once loaded, the extension takes over the host-touching tools as soon as pi
emits session_start; from there every bash / read / write / edit
tool call runs against the per-session sandbox root.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--sandbox-root <path> |
string | $TMPDIR/pi-justbash |
Base directory under which per-session sandbox roots are created. |
--sandbox-max-file-size-mb <n> |
string | 10 |
Override the maximum file read size for the sandbox fs. |
--sandbox-redact-env <bool> |
string | true |
Master switch. Redact host env-var values (API keys, tokens, database URLs, ...) from bash output, file read/write, edit, and host-binary-bridge stdout/stderr. Also controls whether the agent's shell inherits secret entries (see --sandbox-strip-bash-env). |
--sandbox-strip-bash-env <bool> |
string | follows --sandbox-redact-env |
Strip secret-classified entries from the env handed to just-bash so the agent cannot expand $SECRET inline. Defense-in-depth on top of output redaction. |
--sandbox-redaction-marker <s> |
string | [REDACTED] |
Replacement string used when a secret value is redacted. |
--sandbox-redact-env-allow <csv> |
string | — | Comma-separated env var names to exempt from redaction + stripping even when they match the secret heuristic. Propagated to bash env filter, host bridge env filter, and the output redactor. |
--sandbox-redact-env-deny <csv> |
string | — | Comma-separated env var names to force-redact regardless of the default heuristic. Also forces stripping from the bash env and host bridge env. |
--sandbox-redact-min-length <n> |
string | 4 |
Minimum value length required for a secret value to enter the output-value replacement table (prevents over-redaction of short strings like "0" / "1"). The name-form redactor (NAME=value) runs regardless of this threshold. |
--sandbox-host-binary-env-allow <csv> |
string | — | Comma-separated env var names allowed to pass through to host binary bridges (e.g. storm) despite being classified secret. Independent of --sandbox-redact-env-allow. |
All redaction flags also accept SANDBOX_* env var equivalents
(SANDBOX_REDACT_ENV, SANDBOX_STRIP_BASH_ENV,
SANDBOX_REDACTION_MARKER, SANDBOX_REDACT_ENV_ALLOW,
SANDBOX_REDACT_ENV_DENY, SANDBOX_REDACT_MIN_LENGTH,
SANDBOX_HOST_BINARY_ENV_ALLOW).
The sandbox treats the LLM agent as untrusted and assumes host env
vars (API keys, access tokens, connection strings, SSH_AUTH_SOCK)
are secrets that must not reach the agent. Three cooperating layers
enforce this:
- Output redaction. Every byte streamed back through
bash,read,write, andeditpasses through aRedactorthat replaces known-secret values with the redaction marker and rewrites anyNAME=valueit spots whose name matches the secret heuristic (catchesprintenv,env,declare -p,(NAME=v),$(NAME=v),${NAME=v}, and`NAME=v`). ANSI escape sequences are stripped before matching so a split-by-colour bypass cannot sneak a secret through. - Bash env stripping. Before
just-bashconstructs its shell, secret entries are removed from the env map so the agent's$ANTHROPIC_API_KEYis empty andenv/printenvcannot find a value to print. This prevents the agent from using a secret (e.g. as a curl header) even when the output would have been redacted. Controlled by--sandbox-strip-bash-env. - Host binary bridge env filtering. Host binaries exposed via
--sandbox-host-binaries(e.g.storm) inherit only the non-secret subset ofprocess.env, with an explicit opt-in list via--sandbox-host-binary-env-allowfor the tokens the bridge itself needs. Stdout / stderr of the spawned binary also pass through the redactor.
Binary safety. read / edit only redact content that passes a
strict UTF-8 whitelist (no NUL byte in the first 8 KiB, strictly
decodable as UTF-8, no image magic bytes). Image and binary files are
returned untouched so MIME sniffing and downstream image tooling keep
working.
What redaction does NOT cover. Redaction hides values from LLM
output; it does not prevent a host binary you chose to bridge from
writing wherever its flags allow (e.g. storm -o /absolute/path).
Restrict --sandbox-host-binaries to tools whose filesystem reach you
are comfortable granting.
| pi tool | In this extension |
|---|---|
bash |
Runs inside a fresh just-bash Bash per call, bound to the session's ReadWriteFs. stdout + stderr are flushed through pi's onData; non-zero exits and 124/130 (timeout / abort) propagate unchanged. |
read |
Reads through ReadWriteFs.readFileBuffer; image MIME detection covers PNG, JPEG, GIF, and WebP via their magic bytes. |
write |
Writes through ReadWriteFs.writeFile after auto-creating the virtual parent chain. |
edit |
Shares the sandbox fs with read + write, so pi's diff always applies to the exact bytes it just read. |
grep |
Disabled. grep is registered as a same-named stub that throws a sandbox notice, and a tool_call handler returns { block: true, reason } so lingering grep calls from other extensions are rejected too. |
ls, find |
Left untouched — pi's defaults still operate on the host cwd. |
- "cwd ... is outside the sandbox" in bash output — pi handed the bash
tool a cwd that does not live under the sandbox root. The adapter
returns exit code
126in that case so the agent can recover. Point--sandbox-rootat a directory that actually contains the cwd, or do the work via a command thatcds from the sandbox root. - Hitting file-size limits on read — raise
--sandbox-max-file-size-mb; the default matchesReadWriteFs(10 MiB). - Stale sandbox dirs piling up in
$TMPDIR/pi-justbash— the orphan reaper only removes dirs older than 12 hours on startup. Delete the base dir manually if you need to reclaim space sooner; it is safe while no pi process is running.
pnpm install # or: bun install
pnpm test # or: bun run test
pnpm lint # or: bun run lint
pnpm typecheck # or: bun run typecheck
pnpm build # or: bun run buildBoth pnpm and bun are first-class; CI runs the full matrix on each
push against Ubuntu and macOS.
This package is not distributed through npm (package.json sets
"private": true and a prepublishOnly guard blocks accidental
npm publish). Releases are plain git tags and the compiled dist/
output is checked into the repository on tag boundaries so that
pi install git:...@<tag> works without a separate build step on the
consumer's side.
To cut a release locally:
pnpm install
pnpm lint && pnpm typecheck && pnpm test && pnpm build
git add dist
git commit -m "build(dist): release vX.Y.Z"
git tag vX.Y.Z
git push --follow-tagsConsumers then pin the tag:
pi install git:github.com/sionic-ai/pi-justbash-sandbox@vX.Y.ZMIT — see LICENSE.