[Unreleased]
0.1.3 - 2026-06-20
Added
- The
jqbuiltin (JSON processing) is now bundled — bashkit'sjqfeature (the pure-Rustjaqengine) is enabled in the precompiled NIF, so scripts can pipe throughjq(e.g.curl … | jq '.field'). Pure computation, no extra runtime gate.
0.1.2 - 2026-06-20
First release. An Elixir NIF wrapper around
bashkit 0.11.0 — a sandboxed, in-process
virtual bash interpreter written in Rust. Every execution semantic comes from
bashkit; ExBashkit only marshals data across the NIF boundary.
Added
- Stateless
ExBashkit.exec/1— runs a bash script in a fresh sandbox and returns an%ExBashkit.Result{}(stdout,stderr,exit_code). A non-zero exit is still{:ok, ...}— the script ran. ExBashkit.Session— persistent, stateful sandboxes. Unlikeexec/1, a session's environment variables, working directory, in-memory filesystem, shell functions and aliases persist acrossExBashkit.Session.exec/2calls.ExBashkit.Session.new/1seeds initial state via:env,:cwd,:username, and:hostname. Each session is an independent sandbox and serializes its own calls.- Virtual filesystem access from Elixir.
ExBashkit.Session.write_file/3andread_file/2place and retrieve files in a session's in-memory filesystem — shared with scripts, so the host can stage inputs and pull back results (round-tripping arbitrary binary content) without going through a script.Session.new/1gains a:filesoption to seed files (creating parent dirs) up front. - Host directory mounts.
ExBashkit.Session.new/1accepts:mounts—{vfs_path, host_path, mode}tuples (:read_only/:read_write) — mapping real host directories into a sandbox, plus:allowed_mount_pathsto opt into bashkit's sensitive-path default-deny. bashkit enforces canonicalization and symlink/..escape rejection; misconfigured mounts (unknown mode, missing or non-directory host path) raise fromnew/1. (:overlayis intentionally unsupported — bashkit has no real-FS overlay mode.) - Resource limits.
ExBashkit.Session.new/1accepts:limits(keyword list or map) to tighten bashkit's execution bounds for untrusted scripts::max_commands,:max_loop_iterations,:max_total_loop_iterations,:max_function_depth,:max_input_bytes, and:timeout_ms. Exceeding a limit returns{:error, message}; unknown keys or non-integer values raise. - Network access.
ExBashkit.Session.new/1accepts:allow_net— a list of URL patterns thecurl/wget/httpbuiltins may reach, or:allfor any host. The allowlist is default-deny (a session with no:allow_netcannot reach the network at all), matches scheme/host/port/path-prefix literally, and does not follow redirects. Requests to private/reserved IP ranges are blocked by default (SSRF protection);:block_private_ips(defaulttrue) controls this. The NIF bundles bashkit'shttp_clientfeature (reqwest + rustls), so network support ships in the precompiled binary. Invalid:allow_net/:block_private_ipsvalues raise fromnew/1. - Custom builtins.
ExBashkit.Session.new/1accepts:builtins— a map ofname => funregistering Elixir-defined virtual executables a script invokes asname args…. Each builtin is a 1-arity function receiving%{args, stdin, env, cwd}and returning{:ok, iodata}(stdout/exit 0),{:error, iodata}(stderr/exit 1), or a full%ExBashkit.Result{}. The call is a blocking Rust→Elixir round-trip serviced by a short-lived per-exec/2process; a handler that raises or exceeds:builtin_timeout_ms(default 30_000; exit 124) fails only that command, not the session. A builtin must not callexec/2on the same session (reentrancy deadlock); driving a different session is fine. - Elixir-backed virtual filesystems.
ExBashkit.Session.new/1accepts:virtual_fs— a map ofmount_path => backendmounting filesystems whose reads and writes a script performs under that path are serviced by your application (generate content on demand, proxy to a real store). A backend is a module implementing theExBashkit.VirtualFsbehaviour (asmoduleor{module, arg}), or a single dispatch function for inline use. Read and write are supported (read/write/append/mkdir/remove/list/stat, returning tagged results);existsis derived fromstat, the mount root is a directory,chmodis a no-op, andrename/copy/symlink/read_linkare not yet proxied. Composes with the in-memory FS,:files, and host:mounts. - Host filesystem primitives on
ExBashkit.Session:stat/2,list_dir/2,mkdir/3,remove/3,rename/3— lock-free introspection/mutation of a session's (shared) virtual filesystem, alongsideread_file/2/write_file/3. - Snapshot & resume.
ExBashkit.Session.snapshot/2captures a session's shell state (variables, env, cwd, aliases, functions) and in-memory filesystem contents as a binary;ExBashkit.Session.restore/3loads it back into a session, returning{:ok, session}or{:error, message}. A snapshot carries interpreter state, not session config (custom:builtins,:virtual_fsbackends, host:mounts,:limits), so to resume you rebuild a session with the same capabilities and restore into it; restore validates the whole snapshot before mutating, leaving the session usable on a bad/tampered/wrong-key load.snapshot/2options::key(a non-empty binary → HMAC-keyed snapshot for crossing trust boundaries; the matching key is required on restore, and a wrong key or tampered bytes are rejected — without a key the embedded digest detects accidental corruption only),:exclude_filesystem, and:exclude_functions. - Sandboxed
pythonbuiltin.ExBashkit.Session.new(python: true)registerspython/python3virtual executables that run sandboxed Python (via the optional:ex_montydependency) sharing the session's virtual filesystem — a file a bash step writes, the Python reads, and vice versa. Supportspython file.py,python -c "…", and a program piped on stdin; Python'spathlib/osfilesystem operations are routed to the session, while every other effect (network, clocks) is denied. A Python error or timeout fails only that command, not the session. Opt-in by adding:ex_montyto your deps; ExBashkit compiles and runs without it, andpython: trueraises a helpful error if it is absent. (Limitations: nosys.argv;pathlib.PathI/O, notopen().) SeeExBashkit.Python. - LLM tool recipe. A session can be used as an agent "bash" tool with a small
amount of plain data (a JSON schema, a system prompt, and a function that runs a
tool call and formats the result) — documented in the README and a runnable
examples/llm_tool.exs(with a ReqLLM wiring snippet). Deliberately not a module: the glue is framework-specific and tiny, so ExBashkit stays agnostic.