Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ Fix root cause. Unsure: read more code; if stuck, ask w/ short options. Unrecogn

### Documentation

- **Public docs** live in `docs/` user-facing articles (security, guides, etc.)
- **Public docs** live in `docs/`, user-facing articles (security, guides, etc.)
- **Rustdoc guides** live in `crates/bashkit/docs/` as markdown files
- Rustdoc guides embedded via `include_str!` (see `knowledge/operations/documentation.md`)
- Edit `crates/bashkit/docs/*.md`, not the doc modules in `lib.rs`
- Add "See also" cross-links when creating new guides
- Cross-tree links must be source-relative (`../crates/bashkit/docs/jq.md`, not
a bare `jq.md`) the site rewrites by basename and hides the breakage, GitHub
a bare `jq.md`), the site rewrites by basename and hides the breakage, GitHub
does not. Titles/descriptions go in `site/src/pages/docs/_meta.ts`, never in
markdown frontmatter. Enforced by `just check-doc-links`
- Run `cargo doc --open` to preview rustdoc changes
Expand Down Expand Up @@ -137,7 +137,7 @@ Bashkit's integration tests live under `crates/bashkit/tests/integration/`
and are aggregated by `crates/bashkit/tests/integration/main.rs` into a single binary.
New behavioral tests go there. A small number of files stay as top-level
`tests/*.rs` because they need their own binary (process-global env
mutation, `--test-threads=1`, ssh-only feature isolation) the list and
mutation, `--test-threads=1`, ssh-only feature isolation), the list and
criteria live in `knowledge/operations/testing.md`.

### Rust
Expand All @@ -155,14 +155,14 @@ criteria live in `knowledge/operations/testing.md`.
(reference: `format_compile_errors` in `builtins/jq/errors.rs`); cap
diagnostics ≤ 1 KB; test-only Debug needs `// debug-ok: <reason>`.
Enforced by `cargo test` (static scan, per-tool `assert_no_leak`, fuzz
invariants) see `bashkit::testing` rustdoc for the layers.
invariants), see `bashkit::testing` rustdoc for the layers.
New library-wrapping builtin: add a `no_leak_*` test (see `jq/tests.rs`);
fuzz targets must use `bashkit::testing::fuzz_exec(...)`, not bare
`bash.exec(...)`.

### Benches

Two distinct harnesses, two distinct result locations keep them separate.
Two distinct harnesses, two distinct result locations, keep them separate.

- **Criterion benches** for the `bashkit` crate live in `crates/bashkit/benches/`.
Run via `cargo bench --bench <name>` or `just bench-parallel` / `just bench-sqlite`.
Expand Down Expand Up @@ -190,7 +190,7 @@ Do not mix criterion `.md` files into `crates/bashkit-bench/results/`.
1. `just pre-pr` (runs 2-4 automatically)
2. `cargo fmt --check`
3. `cargo clippy --all-targets --all-features -- -D warnings`
4. `just test` (feature-sliced; never `cargo test --all-features` in one invocation see Local Dev)
4. `just test` (feature-sliced; never `cargo test --all-features` in one invocation, see Local Dev)
5. Unit tests cover both positive (expected behavior) and negative (error handling, edge cases) scenarios
6. Security tests if change touches user input, parsing, sandboxing, or permissions (see `knowledge/security/security-testing.md`)
7. Compatibility/differential tests if change affects Bash behavior parity (compare against real Bash)
Expand Down Expand Up @@ -220,25 +220,25 @@ Types: feat, fix, docs, refactor, test, chore

### Commit Attribution

All commits (incl. merge commits) attributed to the real human user
All commits (incl. merge commits) attributed to the real human user,
never a bot/agent identity, no AI `Co-authored-by` trailers, no
"generated by" text. Verify `git config user.name`/`user.email` are
human before committing; if missing or bot-like, set from
`$GIT_USER_NAME`/`$GIT_USER_EMAIL`. If those are also missing, stop and
ask never commit with a default/bot identity. Pre-push script warns on
ask, never commit with a default/bot identity. Pre-push script warns on
bot-like author names.

### PRs

Squash and Merge. Use `.github/pull_request_template.md` for the description.

Center the description on functional change and impact, not a code-location
walkthrough (the diff shows that). Add a Before / After with proof CLI output,
logs, differential-test results, or screenshots for UI whenever behavior changes.
walkthrough (the diff shows that). Add a Before / After with proof, CLI output,
logs, differential-test results, or screenshots for UI, whenever behavior changes.

**NEVER add links to Claude sessions in PR body or commits. Never attribute commit or merge commit to coding agents, always use real user.**

- Prefer small, shippable PRs. Split large changes into independent, reviewable units.
- When asked to create separate PRs, follow that instructiondo not bundle unrelated changes.
- When asked to create separate PRs, follow that instruction, do not bundle unrelated changes.

See `CONTRIBUTING.md` for details.
48 changes: 24 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ assert_eq!(output.result["stdout"], "hello\nworld\n");

## Script Analysis

`analyze()` reports what a script statically refers to commands, arguments,
redirect targets, functions without running it. Hosts use it to decide whether
`analyze()` reports what a script statically refers to, commands, arguments,
redirect targets, functions, without running it. Hosts use it to decide whether
a model-produced command needs user approval.

```rust
Expand All @@ -136,7 +136,7 @@ assert!(!analysis.is_opaque());
```

Advisory only: names built at runtime (`$cmd`, `$(echo rm)`), `eval`/`source`,
and truncated walks report as *unknown* and set `is_opaque()` an allowlist
and truncated walks report as *unknown* and set `is_opaque()`, an allowlist
check must consult it. Available in Rust, Node (`bash.analyze()`), and Python
(`bash.analyze()`). See [docs/script-analysis.md](docs/script-analysis.md).

Expand Down Expand Up @@ -319,7 +319,7 @@ bash.removeBuiltin("greet");
```

Resolution order: shell function → POSIX special builtin → custom builtin →
baked-in builtin → `$PATH` so custom builtins can override baked-ins
baked-in builtin → `$PATH`, so custom builtins can override baked-ins
(e.g. wrap `cat` with tracing) but a shell function defined in the script
still wins.

Expand All @@ -330,7 +330,7 @@ Working example: [`examples/custom_builtins.mjs`](examples/custom_builtins.mjs).
## Experimental: Git Support

Enable the `git` feature for virtual git operations on the virtual filesystem.
All git data lives in the VFS no host filesystem access.
All git data lives in the VFS, no host filesystem access.

```bash
cargo add bashkit --features git
Expand All @@ -354,7 +354,7 @@ See [knowledge/integrations/git-support.md](knowledge/integrations/git-support.m
## Experimental: Python Support

Enable the `python` feature to embed the [Monty](https://github.com/pydantic/monty) Python interpreter (pure Rust, Python 3.12).
Python code runs in-memory with configurable resource limits and VFS bridging files created
Python code runs in-memory with configurable resource limits and VFS bridging, files created
by bash are readable from Python and vice versa.

```bash
Expand Down Expand Up @@ -427,7 +427,7 @@ See [crates/bashkit/docs/typescript.md](crates/bashkit/docs/typescript.md) for t

## Experimental: SQLite Support

Enable the `sqlite` feature to embed [Turso](https://github.com/tursodatabase/turso)a pure-Rust, SQLite-compatible engine backed by the bashkit virtual filesystem. Turso is BETA upstream, so the builtin is opt-in at both the cargo and runtime layer.
Enable the `sqlite` feature to embed [Turso](https://github.com/tursodatabase/turso), a pure-Rust, SQLite-compatible engine, backed by the bashkit virtual filesystem. Turso is BETA upstream, so the builtin is opt-in at both the cargo and runtime layer.

```toml
[dependencies]
Expand All @@ -453,7 +453,7 @@ bash.exec(r#"sqlite /tmp/notes.sqlite '
bash.exec("sqlite -header /tmp/notes.sqlite 'SELECT * FROM notes'").await?;
```

Sqlite3-shell-compatible flags (`-csv`, `-json`, `-markdown`, `-header`, `-separator`, `-nullvalue`, `-cmd`) and dot-commands (`.tables`, `.schema`, `.dump`, `.read`, `.headers`, `.mode`) are supported. Two IO backends are available: `Memory` (default load/flush against the VFS at command boundaries) and `Vfs` (custom turso `IO` impl).
Sqlite3-shell-compatible flags (`-csv`, `-json`, `-markdown`, `-header`, `-separator`, `-nullvalue`, `-cmd`) and dot-commands (`.tables`, `.schema`, `.dump`, `.read`, `.headers`, `.mode`) are supported. Two IO backends are available: `Memory` (default, load/flush against the VFS at command boundaries) and `Vfs` (custom turso `IO` impl).

Limits via [`SqliteLimits`](crates/bashkit/src/builtins/sqlite/mod.rs) cap script size, result-set rows, DB file size, wall-clock duration, and statement count. See [crates/bashkit/docs/sqlite.md](crates/bashkit/docs/sqlite.md) for the full guide.

Expand Down Expand Up @@ -498,7 +498,7 @@ just pre-pr # Pre-PR checks

## LLM Eval Results

Bashkit includes a [mira eval study](crates/bashkit-eval/) that measures how well LLMs use bashkit as a bash tool in agentic workloads 58 tasks across 15 categories.
Bashkit includes a [mira eval study](crates/bashkit-eval/) that measures how well LLMs use bashkit as a bash tool in agentic workloads, 58 tasks across 15 categories.

_Latest run: 2026-06-27, on the mira eval framework (58 tasks)._

Expand All @@ -510,7 +510,7 @@ _Latest run: 2026-06-27, on the mira eval framework (58 tasks)._
| GPT-5.5 | 88% | 51/58 | 90% | 8.2 min |
| Claude Sonnet 4.6 | 84% | 49/58 | 93% | 19.9 min |

Opus 4.8 and Haiku 4.5 lead at 55/58 Haiku matches Opus in ~⅗ the wall-clock
Opus 4.8 and Haiku 4.5 lead at 55/58, Haiku matches Opus in ~⅗ the wall-clock
time. Two tasks trip every model (`file_path_organizer`, `script_getopts_parser`).
See the [detailed analysis](crates/bashkit-eval/README.md#results).

Expand Down Expand Up @@ -579,7 +579,7 @@ See [crates/bashkit-js](crates/bashkit-js/) for details.
### Browser / edge (WebAssembly)

A slim, single-threaded WebAssembly build for the browser and any other
JavaScript runtime edge/serverless workers (Cloudflare Workers, Vercel Edge,
JavaScript runtime, edge/serverless workers (Cloudflare Workers, Vercel Edge,
Deno Deploy), Node, Deno, and Bun. Available as `@everruns/bashkit-wasm` on npm.
It needs **no `SharedArrayBuffer` and no `COOP`/`COEP` headers**, so it drops
into any web app (including iframes) and into thread-less edge runtimes.
Expand All @@ -604,42 +604,42 @@ Bashkit is built for running untrusted scripts from AI agents and users. Securit

| Layer | Protection |
|-------|------------|
| **No process spawning** | All 164 commands are reimplemented in Rust no `fork`, `exec`, or shell escape |
| **No process spawning** | All 164 commands are reimplemented in Rust, no `fork`, `exec`, or shell escape |
| **Virtual filesystem** | Scripts see an in-memory FS by default; no host filesystem access unless explicitly mounted |
| **Network allowlist** | HTTP access is denied by default; each domain must be explicitly allowed |
| **Resource limits** | Configurable caps on commands (10K), loop iterations (100K), function depth (100), output (10MB), input (10MB) |
| **Filesystem limits** | Max total bytes (100MB), max file size (10MB), max file count (10K) prevents zip bombs, tar bombs, and append floods |
| **Parser limits** | Timeout (5s), fuel budget (100K ops), AST depth (100) prevents pathological input from hanging the interpreter |
| **Multi-tenant isolation** | Each `Bash` instance is fully isolated no shared state between tenants |
| **Panic recovery** | All builtins wrapped in `catch_unwind` a panic in one command doesn't crash the host |
| **Filesystem limits** | Max total bytes (100MB), max file size (10MB), max file count (10K), prevents zip bombs, tar bombs, and append floods |
| **Parser limits** | Timeout (5s), fuel budget (100K ops), AST depth (100), prevents pathological input from hanging the interpreter |
| **Multi-tenant isolation** | Each `Bash` instance is fully isolated, no shared state between tenants |
| **Panic recovery** | All builtins wrapped in `catch_unwind`, a panic in one command doesn't crash the host |
| **Path traversal prevention** | RealFs backend canonicalizes paths to prevent `../../etc/passwd` escapes |
| **Unicode security** | 68 byte-boundary tests across builtins; zero-width character rejection in VFS paths |

### Threat Model

280+ identified threats across 17 categories (DoS, sandbox escape, info disclosure, injection, network, isolation, internal errors, git, SSH, logging, crypto, Python, TypeScript, SQLite, Unicode, filesystem, snapshots) each with a stable ID, mitigation status, and test coverage.
280+ identified threats across 17 categories (DoS, sandbox escape, info disclosure, injection, network, isolation, internal errors, git, SSH, logging, crypto, Python, TypeScript, SQLite, Unicode, filesystem, snapshots), each with a stable ID, mitigation status, and test coverage.

See the [threat model](knowledge/security/threat-model.md) for the full analysis and [security policy](SECURITY.md) for reporting vulnerabilities.

## Other Virtual Bash Implementations

- **[just-bash](https://github.com/vercel-labs/just-bash)** (TypeScript, Apache-2.0) Virtual bash interpreter for AI agents by Vercel Labs. Custom recursive descent parser, 75+ reimplemented commands (including full awk/sed/jq), in-memory VFS, defense-in-depth sandboxing, AST transform plugins. Runs in Node.js and browser.
- **[gbash](https://github.com/ewhauser/gbash)** (Go, Apache-2.0) Deterministic, sandbox-only bash runtime for AI agents. Delegates parsing to `mvdan/sh`. Registry-backed commands, policy enforcement, structured tracing, JSON-RPC server mode.
- **[just-bash](https://github.com/vercel-labs/just-bash)** (TypeScript, Apache-2.0), Virtual bash interpreter for AI agents by Vercel Labs. Custom recursive descent parser, 75+ reimplemented commands (including full awk/sed/jq), in-memory VFS, defense-in-depth sandboxing, AST transform plugins. Runs in Node.js and browser.
- **[gbash](https://github.com/ewhauser/gbash)** (Go, Apache-2.0), Deterministic, sandbox-only bash runtime for AI agents. Delegates parsing to `mvdan/sh`. Registry-backed commands, policy enforcement, structured tracing, JSON-RPC server mode.

## Acknowledgments

Bashkit is an independent implementation that draws design inspiration from several open source projects:

- **[just-bash](https://github.com/vercel-labs/just-bash)** (Vercel Labs, Apache-2.0) Pioneered the idea of a virtual bash interpreter for AI-powered environments. Bashkit's sandboxing architecture and multi-tenant design was inspired by their approach.
- **[Oils](https://github.com/oilshell/oil)** (Andy Chu, Apache-2.0) Comprehensive bash compatibility testing approach inspired our spec test methodology.
- **[One True AWK](https://github.com/onetrueawk/awk)** (Lucent Technologies) AWK language semantics reference for our awk builtin.
- **[jq](https://github.com/jqlang/jq)** (Stephen Dolan, MIT) jq query syntax and behavior reference. Our implementation uses the [jaq](https://github.com/01mf02/jaq) Rust crates.
- **[just-bash](https://github.com/vercel-labs/just-bash)** (Vercel Labs, Apache-2.0), Pioneered the idea of a virtual bash interpreter for AI-powered environments. Bashkit's sandboxing architecture and multi-tenant design was inspired by their approach.
- **[Oils](https://github.com/oilshell/oil)** (Andy Chu, Apache-2.0), Comprehensive bash compatibility testing approach inspired our spec test methodology.
- **[One True AWK](https://github.com/onetrueawk/awk)** (Lucent Technologies), AWK language semantics reference for our awk builtin.
- **[jq](https://github.com/jqlang/jq)** (Stephen Dolan, MIT), jq query syntax and behavior reference. Our implementation uses the [jaq](https://github.com/01mf02/jaq) Rust crates.

No code was copied from any of these projects. See [NOTICE](NOTICE) for full details.

## Contributing

The best way to contribute is to [open an issue](https://github.com/everruns/bashkit/issues) bug reports, feature requests, and questions all help improve bashkit. If you'd like to contribute code, see [CONTRIBUTING.md](CONTRIBUTING.md) for setup and workflow details.
The best way to contribute is to [open an issue](https://github.com/everruns/bashkit/issues), bug reports, feature requests, and questions all help improve bashkit. If you'd like to contribute code, see [CONTRIBUTING.md](CONTRIBUTING.md) for setup and workflow details.

## Ecosystem

Expand Down
10 changes: 5 additions & 5 deletions crates/bashkit-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Benchmark tool for comparing bashkit against bash and just-bash across multiple

### In-process / persistent-child lineup (vm, 4 CPUs, 2026-05-26)

96 cases, 10 iterations. Apples-to-apples interpreter cost only, no
96 cases, 10 iterations. Apples-to-apples, interpreter cost only, no
per-call process spawn (except `bash`, kept as the cold-start reference).

| Runner | Avg/Case (ms) | Total (ms) | vs bashkit | Errors | Output Match |
Expand All @@ -40,18 +40,18 @@ Bashkit speedup (geometric mean / median across 96 cases):
|----|---------:|-------:|
| bash | 24.7x | 31.1x |
| just-bash-inproc | 25.4x | 34.2x |
| gbash-server | 17.6x | 18.8x (N=83 gbash failed 13 awk/jq cases with exit 127) |
| gbash-server | 17.6x | 18.8x (N=83, gbash failed 13 awk/jq cases with exit 127) |

Subprocess-mode lineup (`just-bash` CLI 380 ms/case, `gbash` CLI 12.6 ms/case)
is dominated by per-call Node/Go startup, not interpreter cost see commit
is dominated by per-call Node/Go startup, not interpreter cost, see commit
`2223a72` for the raw numbers if you need the subprocess view. The in-process
runners above are the fair comparison for steady-state workloads.

### Historical: bashkit vs bash (runsc, 16 CPUs, 2026-04-13)

96 cases, 10 iterations, **107.2x faster** overall. 0 errors, 100% output match.
(Higher headline number than the vm run above because runsc + 16 CPUs makes
host `bash`'s per-process spawn much more expensive bashkit avoids spawn
host `bash`'s per-process spawn much more expensive, bashkit avoids spawn
entirely, so its lead widens.)

| Benchmark | bashkit | bash | Speedup | Description |
Expand Down Expand Up @@ -151,7 +151,7 @@ contract.
- Each benchmark: warmup iterations (not timed) → timed iterations → statistics (mean, stddev, min, max)
- Prewarm phase runs first 3 cases to warm up JIT/compilation before actual benchmarks
- Output compared against bash reference output; mismatches flagged but don't affect timing
- Benchmarks run sequentially no parallel execution competing for resources
- Benchmarks run sequentially, no parallel execution competing for resources
- Execution failures count as errors with 1000ms penalty time

## Output Files
Expand Down
Loading
Loading