$_ bashkit

bashkit-cli

Command-line interface for running bash scripts in a sandboxed virtual filesystem. One binary, three modes.

Modes

InvocationMode
bashkit -c '…'Execute command string, print stdout/stderr, exit
bashkit script.shExecute script file
bashkitInteractive shell (REPL)

Mode is detected from arguments — -c wins, then positional script, otherwise REPL.

Install

From crates.io:

cargo install bashkit-cli

Prebuilt binary via cargo-binstall:

cargo binstall bashkit-cli

Build features

FeatureDefaultEffect
interactiveonInteractive REPL (rustyline, signal-hook, terminal_size)
pythononpython/python3 builtin via Monty
realfsoff--mount-ro / --mount-rw host filesystem mounts
scripted_tooloffScripted tool orchestration

Build without interactive (library-only deps):

cargo build -p bashkit-cli --no-default-features

Defaults

Builtins enabled out of the box:

  • Git (git) — local VFS operations (init, add, commit, log, …)
  • Python (python, python3) — embedded via Monty (requires python feature)

Disabled by default (security):

  • HTTP (curl, wget) — network access stays blocked unless explicitly enabled

Disable per-run:

FlagEffect
--http-allow-allEnable curl/wget with unrestricted outbound access (trusted scripts only)
--no-httpForce-disable curl/wget builtins
--no-gitDisable git builtin
--no-pythonDisable python/python3 builtins

Execution limits

All modes start from ExecutionLimits::cli() — counting-based limits are effectively unlimited, timeout is off (user has Ctrl-C). Memory guards (function depth, AST depth, parser fuel) stay on.

Override with:

FlagMeaning
--max-commands NMax commands per run
--max-loop-iterations NMax iterations in a single loop
--max-total-loop-iterations NMax iterations across all loops
--timeout SECONDSWall-clock execution timeout

Host filesystem mounts (realfs feature)

By default the VFS is in-memory — scripts cannot reach the host. With realfs:

FlagEffect
--mount-ro HOST[:VFS]Overlay host dir as read-only
--mount-rw HOST[:VFS]Overlay host dir as read-write

Omit :VFS to overlay at VFS root. Both flags repeat.

bashkit --mount-ro /path/to/project -c 'ls /'
bashkit --mount-ro /data:/mnt/data -c 'wc -l /mnt/data/*.csv'
bashkit --mount-rw /tmp/out:/mnt/out script.sh

Warning. --mount-rw breaks the sandbox boundary — scripts can modify host files. Prefer --mount-ro unless writes are required.

Interactive shell

Run bashkit with no arguments. The REPL uses rustyline for line editing and reuses the same sandbox as -c. Lightweight deps, no SQLite, no crossterm.

Features

  • Emacs / vi line editing, in-memory history (1 000 entries)
  • Multiline input — unterminated quotes, if/for/while/case/functions reprompt with PS2 until closed
  • Ctrl-C cancels the running command (propagates via the cancellation token); at an empty prompt it clears the line
  • Ctrl-D exits the shell
  • exit [N] exits via an on_exit hook (works from pipelines and conditionals: echo bye; exit 1)
  • Streaming output — stdout/stderr flushed as produced
  • TTY detection: [ -t 0 ], [ -t 1 ], [ -t 2 ] all return true
  • Tab completion — builtins, aliases, $VAR, VFS paths (directories get trailing /)
  • Fish-style history hints inline (dim gray); accept with right arrow
  • COLUMNS, LINES exported from the real terminal size; SHLVL incremented from parent

Prompt (PS1 / PS2)

Default PS1: \u@bashkit:\w\$ (e.g. user@bashkit:~$ ). Override with export PS1='…'. PS2 (continuation) defaults to > .

Supported escapes:

EscapeMeaning
\uUsername ($USER)
\hShort hostname (up to first .)
\HFull hostname
\wWorking directory, ~ for $HOME
\WBasename of cwd
\$$ for non-root, # if EUID=0
\n \r \a \eNewline, CR, bell, ESC
\[ \]Non-printing sequence markers (ANSI codes)
\\Literal backslash

Startup file

Sources ~/.bashkitrc from the VFS on startup if present. Put it on the host and expose it via --mount-rw /path:/home/user (or --mount-ro for a read-only rc). Typical contents: aliases, PS1, environment.

Not implemented (by design)

  • Job control (bg/fg/jobs) — no real processes
  • History expansion (!!, !N) — complexity vs. value
  • Persistent history file — would leak across sessions, breaks isolation
  • exec — excluded for security

Examples

Text processing:

bashkit -c 'echo "hello world" | tr a-z A-Z'
# HELLO WORLD

Python (default):

bashkit -c 'python3 -c "print(2 + 2)"'
# 4

Git on the VFS:

bashkit -c '
git init /repo
cd /repo
echo "# readme" > README.md
git add README.md
git commit -m "init"
git log --oneline
'

Disable a builtin:

bashkit --no-python -c 'python --version'
# python: command not found

Run a script file:

bashkit script.sh arg1 arg2

Interactive shell:

bashkit
user@bashkit:~$ echo hi
hi
user@bashkit:~$ exit

Mount host workspace read-only and inspect:

bashkit --mount-ro "$PWD:/mnt/repo" -c 'wc -l /mnt/repo/**/*.rs'

Tighten limits for an untrusted script:

bashkit --max-commands 1000 --timeout 5 untrusted.sh

Error handling

Stack backtraces are suppressed. Panics emit a single sanitized line (bashkit: internal error: …) — no paths, line numbers, or dependency versions.

See also