Allowlist-first runtime for shell automation. Scripts are written in Rhai; every external command must be declared in a YAML pact. Anything not declared is rejected before it runs. No shell, no globbing, no environment injection.
let host = exec("hostname", ["-s"]);
let kern = exec("uname", ["-a"]);
print(`Running on ${host.stdout.trim()} (${kern.stdout.trim()})`);
// exec("rm", ["-rf", "/"]); // → BinaryNotAllowed at runtimeBash gives you everything by default and asks you to remove it. Reeve gives you nothing and asks you to declare what's needed. Concretely:
| Bash | Reeve | |
|---|---|---|
| Run a binary not on the allowlist | ✅ | ❌ rejected |
| Pass a flag the policy didn't approve | ✅ | ❌ rejected |
eval, source, dynamic command construction |
✅ | ❌ disabled |
| Network, fs, env access without declaring it | ✅ | ❌ sandboxed |
Trade-offs: less expressive than bash, slower to author for one-off work, and the pact file adds review surface. Use Reeve when scripts run in CI/CD, runbooks, or as the tool surface for an AI agent — places where "what could this script do?" needs an answer you can read in 30 seconds.
Install from crates.io:
cargo install reeve
reeve versionOr install from source:
git clone https://github.com/thaitype/reeve
cd reeve
cargo install --path .
reeve versionRequires Rust 1.75+ (rustup install stable).
Note:
cargo installdrops the binary into~/.cargo/bin. Make sure that directory is on yourPATH, otherwisereeve versionwill report "command not found" even after a successful install. If Cargo's installer prints a path warning, follow its instructions; on most shells, addingexport PATH="$HOME/.cargo/bin:$PATH"to your shell rc file is enough.
reeve run examples/sysinfo.rhai # print host/user/kernel/date via exec()
reeve run examples/workspace-demo.rhai # write, append, read a file in the sandboxOutput:
=== sysinfo ===
user: thada
host: macbook
kernel: Darwin macbook 25.2.0 ... arm64
date: 2026-05-04
Each line was gathered by an allowlisted binary (whoami, hostname,
uname, date). Anything else throws a typed error and exits non-zero.
Pact — the shipped pact (pacts/unix-readonly.yaml) permits:
echo,date,uname,whoami,hostname— read-only POSIX info commands, with safe flags only.
That's it. The whole point is that the surface is small and visible.
To allow more binaries, fork the repo, edit pacts/unix-readonly.yaml,
and rebuild. There is intentionally no --pact runtime flag — the
trust boundary is the binary itself, so AI agents calling Reeve cannot
swap policy.
Host functions available to scripts:
| Function | Description |
|---|---|
exec(bin, args) |
Run an allowlisted binary; throws on non-zero exit |
exec_allow_fail(bin, args) |
Same but returns map with exit_code instead of throwing |
read_file(path) |
Read file from workspace; throws FileNotFound if missing |
read_lines(path) |
Read file as array of lines |
write_file(path, content) |
Create file in workspace; throws FileAlreadyExists if present |
append_file(path, content) |
Append to file; creates if missing |
exists(path) |
Returns bool |
env(key) |
Read env var declared in env_passthrough; throws EnvDenied or EnvUnset |
parse_json(str) |
Parse JSON string to Rhai map |
to_json(value) |
Serialise any Rhai value to JSON string |
log_info(msg) / log_warn / log_error |
Emit to stderr + audit log |
exec("rm", [...])→BinaryNotAllowed. Not in the pact.eval(...),import,require— disabled in the Rhai engine.- File I/O outside the sandbox — scripts can only read/write within
<reeve_home>/workspace/via the scopedread_file/write_filehost functions. - Custom pacts at runtime — see above.
- Long-running tail-style commands (
tail -f,kubectl logs -f) —exec()waits for the binary to exit, so a non-terminating command hangs the run. Run a watcher externally and call Reeve per snapshot.
Experimental. v0.2.0 ships with sandboxed exec, workspace-scoped
file I/O, a JSONL audit log, env isolation, and a 5 MB binary. Breaking
changes are likely as the design lands; consult draft/spec-v3.md for
the target shape.
Delivered so far:
- ✅ Rhai scripting engine + YAML pact allowlist.
- ✅ Sandboxed
exec()over an allowlisted binary set. - ✅ Filesystem host functions (
read_file,write_file,append_file,read_lines,exists) scoped to<reeve_home>/workspace/. - ✅ JSONL audit log — every run, every
execcall, every log event. - ✅
env()host fn gated byenv_passthroughallowlist; child processes run with a clean environment. - ✅
to_json()/parse_json()for structured data.
Roadmap, in rough order:
- Trusted-caller binary (
reeve-flex) with runtime pact selection for MCP servers and CI orchestrators. pipe()— chain binaries without temp files.- Additional presets (
k8s-readonly,git-readonly).
To hack on Reeve without installing, run from the checkout:
cargo run --release -- run examples/sysinfo.rhai
cargo run --release -- versionNote the -- separator: anything after it goes to reeve, not to
cargo. Drop --release for faster rebuilds during iteration; the
5× cold-start gain only matters when you're measuring.
Single-crate flat layout at the repo root, with two internal modules:
src/pact/— YAML schema, allowlist engine, named kinds, embedded presets. Pure logic, no I/O.src/core/— Rhai engine, host functions, process executor, audit log.
The CLI binary lives in src/bin/reeve.rs.
Useful loops:
cargo test # all tests
cargo clippy --all-targets -- -D warnings # lint gate
cargo build --release # ship binaryBoth test and clippy must be clean before opening a PR.
Issues and PRs welcome. Notable areas where help is useful right now:
- New named kinds for pact validators (
filepath,duration, ...). - Cross-platform CI matrix.
- Additional read-only presets backed by real-world tools.
Run cargo test --workspace and cargo clippy --workspace --all-targets -- -D warnings before opening a PR. Both must be
clean.