Virtual filesystem
Every Bashkit script runs against an in-memory virtual filesystem (VFS), not
the host disk. cat, ls, cp, redirections, mkdir — they all work exactly
as a script expects, but the bytes live in memory and disappear when the
interpreter is dropped. Path traversal like ../../../etc/passwd is normalised
away, and symlinks are stored but never followed. The host is invisible by
default; you grant access deliberately, never by accident.
This is the foundation of the security model: there is no real filesystem to escape to unless you mount one.
The layering stack
A Bash instance composes its filesystem from layers. Each layer wraps the one
below it, so you can stack read-only enforcement, text mounts, and host mounts
over an in-memory base — and swap mounts at runtime.
<g font-size="11" fill="#404040">
<text x="338" y="42">Bash::mount() / unmount()</text>
<text x="338" y="94">readonly_filesystem()</text>
<text x="338" y="146">mount_text()</text>
<text x="338" y="198">mount_real_*_at()</text>
<text x="338" y="250">Bash::new()</text>
</g>
<g stroke="#0a1636" stroke-opacity="0.25">
<line x1="180" y1="60" x2="180" y2="68"/>
<line x1="180" y1="112" x2="180" y2="120"/>
<line x1="180" y1="164" x2="180" y2="172"/>
<line x1="180" y1="216" x2="180" y2="224"/>
</g>
Two-layer trait model
Internally the VFS splits raw storage from POSIX semantics:
| Layer | Trait | Responsibility |
|---|---|---|
| Backend | FsBackend | Raw storage operations (minimal contract) |
| POSIX | FileSystem / PosixFs | POSIX-like semantics (no duplicate names, type-safe ops, parent-dir rules) |
If you want a custom backend (a database, object store, key-value store),
implement the small FsBackend and wrap it in PosixFs — the POSIX checks come
for free. Implement FileSystem directly only when you need full control over
semantics.
Built-in implementations
| Implementation | Purpose |
|---|---|
| InMemoryFs | Default (Bash::new()). HashMap-backed, thread-safe, no persistence. Seeds /, /tmp, /home, /home/user, /dev. |
| OverlayFs | Copy-on-write over another filesystem, with whiteout tracking for deletes. |
| MountableFs | Mount multiple filesystems at different paths (longest-prefix match). Always the outermost layer, enabling live mounts. |
| ReadOnlyFs | Delegates reads, denies every mutation with PermissionDenied — even writes to /tmp, cp, mv, rm, chmod. For inspection-only sessions. |
RealFs (realfs feature) | Direct access to a host directory. Read-only (safe) or read-write (dangerous); path traversal blocked by canonicalisation + root-prefix checks. |
Mounting host directories
Real host access is opt-in and read-only by default. The realfs feature
adds builder and CLI entry points:
use bashkit::Bash;
let mut bash = Bash::builder()
.mount_real_readonly("/host/data") // visible read-only inside the VFS
.build();
bashkit --mount-ro /host/data:/data -c 'ls /data' # read-only
bashkit --mount-rw /host/out:/out -c 'echo hi > /out/f' # writable (dangerous)
To freeze a session — including in-memory writes — wrap it with
readonly_filesystem().
Special files and symlinks
/dev/nullis handled at the interpreter level (not the filesystem), so a custom backend can’t intercept it./dev/urandom//dev/randomreturn bounded random data.- Symlinks are stored but never followed — this closes symlink-escape (TM-ESC-002) and symlink-loop DoS (TM-DOS-011).
Binding parity
Every language binding exposes the same concepts, so the model is identical from Rust, Python, and Node:
files: { "/path": "content" } # writable in-memory text files
mounts: [{ host_path, vfs_path?, writable? }] # real FS (read-only by default)
readonly_filesystem: bool # deny all VFS mutations after setup
See also
- Security — the boundaries built on top of the VFS.
- Live mounts — attach and detach filesystems at runtime.
- Snapshotting — serialise and restore VFS + shell state.
- Spec:
specs/vfs.md.