A simulated bash environment with an in-memory virtual filesystem, written in Elixir.
Designed for AI agents that need a secure, sandboxed bash environment.
Supports optional network access via curl with secure-by-default URL filtering.
Note: This is an Elixir port of just-bash by Vercel. The entire codebase was generated through conversational prompting with Claude Opus 4.5 via OpenCode.
- The shell only has access to the provided virtual filesystem
- No access to the real filesystem by default
- No network access by default
- Network access can be enabled with URL allowlists
def deps do
[{:just_bash, "~> 0.1.0"}]
endbash = JustBash.new()
{result, _} = JustBash.exec(bash, ~s(echo "Hello" > greeting.txt))
{result, _} = JustBash.exec(bash, "cat greeting.txt")
result.stdout #=> "Hello\n"
result.exit_code #=> 0bash = JustBash.new(
files: %{"/data/file.txt" => "content"}, # Initial files
env: %{"MY_VAR" => "value"}, # Environment variables
cwd: "/app" # Starting directory
)Network access is disabled by default. Enable it with allowlists:
# Allow specific hosts
bash = JustBash.new(
network: %{
enabled: true,
allow_list: ["api.github.com", "*.example.com"]
}
)
# Custom HTTP client for testing
bash = JustBash.new(
network: %{enabled: true},
http_client: MyMockHttpClient
)# Run a script from the real filesystem in the sandbox
{result, bash} = JustBash.exec_file("script.sh")
# With options
JustBash.exec_file("script.sh",
files: %{"/data/input.txt" => "hello"},
network: %{enabled: true}
)import JustBash.Sigil
result = ~b"echo hello"
result.stdout #=> "hello\n"
# Modifiers
~b"echo hello"t # trimmed output
~b"echo hello"s # stdout only
~b"exit 42"e # exit codecat, cp, file, find, ln, ls, mkdir, mv, readlink, rm, stat, touch, tree, du
awk, base64, comm, cut, diff, expand, fold, grep, head, md5sum, nl, paste, rev, sed, sort, tac, tail, tr, uniq, wc, xargs
jq (JSON), markdown (Markdown → HTML)
curl
echo, printf, cd, pwd, export, unset, set, test, [, [[, true, false, :, source, ., read, exit, return, local, declare, break, continue, shift, getopts, trap
basename, dirname, date, env, hostname, printenv, seq, sleep, tee, which
- Pipes:
cmd1 | cmd2 - Redirections:
>,>>,2>,&>,<,<<<, heredocs - Command chaining:
&&,||,; - Variables:
$VAR,${VAR},${VAR:-default},${VAR:=value},${#VAR},${VAR:start:len},${VAR#pattern},${VAR%pattern},${VAR/old/new},${VAR^^},${VAR,,} - Brace expansion:
{a,b,c},{1..10},{a..z} - Arithmetic:
$((expr))with full operators - Glob patterns:
*,?,[...] - Control flow:
if/elif/else/fi,for/while/until,case/esac - Functions:
function name { ... }orname() { ... } - Arrays:
arr=(...),${arr[0]},${arr[@]},${#arr[@]} - Subshells:
(cmd)and command groups{ cmd; }
When created without options, JustBash provides a Unix-like directory structure:
/home/user- Default working directory (and$HOME)/bin,/usr/bin- Binary directories/tmp- Temporary files
# Create environment
bash = JustBash.new(opts)
# Execute command
{result, bash} = JustBash.exec(bash, "command")
result.stdout # String
result.stderr # String
result.exit_code # Integer
result.env # Updated environment
# Parse without executing
{:ok, ast} = JustBash.parse("echo hello")
# Format script
{:ok, formatted} = JustBash.format("if true;then echo yes;fi")mix deps.get
mix test # 2400+ tests
mix dialyzer # Type checking
mix credo # LintingMIT