Bash sandbox¶
The Bash tool in biu wraps every command in an OS-level sandbox by default, so commands generated by the LLM (and commands you approve) cannot escape the project directory and damage the host. The sandbox and the permissions system (permissions.md) are independent of each other: permissions decide "may this call run at all", while the sandbox decides "what it may touch once it runs".
Platform strategy¶
| Platform | Mechanism | Notes |
|---|---|---|
| macOS | sandbox-exec (SBPL profile) |
Ships with the system; nothing to install |
| Linux | Bubblewrap (bwrap) |
Must be installed (Debian/Ubuntu: apt install bubblewrap). At startup it actually probes once whether user namespaces are available; if not, it falls back to running unsandboxed (when bwrap exists but userns is disabled by a hardened kernel / container, it does not pretend to be sandboxed) |
| Other platforms | No sandbox | Bash runs directly as a subprocess |
When the sandbox tooling is missing, biu doctor reports a ! warning (e.g. bwrap not in PATH — Bash will run unsandboxed).
Default policy¶
Without any sandbox rules configured, every Bash command runs under these constraints:
- Read: the whole filesystem is readable (the model often needs to look at
/tmp,/etc, system frameworks). - Write: only the current working directory (plus
/tmp,/var/folderson macOS,/dev/null, standard streams, and other necessary devices). - Network: off by default (on macOS, only loopback binds are allowed). The model can explicitly request network access for a single call via the
allow_networkparameter, which then shows up in the permission prompt. - Extra writable directories: working directories registered via
/add-dir,--add-dir, orpermissions.additionalDirectoriesin settings.json are automatically added to the writable roots.
Custom rules: the sandbox block in settings.json¶
Four list fields form a "read / write × deny / exempt" matrix:
{
"sandbox": {
"fsReadDeny": [
"~/.ssh",
"~/.aws",
"~/.config/gcloud"
],
"fsReadAllowWithinDeny": [
"~/.aws/config"
],
"fsWriteAllowExtra": [
"${PROJECT_ROOT}/../build-output"
],
"fsWriteDenyWithinAllow": [
"${PROJECT_ROOT}/../build-output/keep-out"
]
}
}
| Field | Purpose |
|---|---|
fsReadDeny |
Absolute paths that may not be read — typically used to shield credential directories (~/.ssh, ~/.aws, cloud CLI configs, etc.) |
fsReadAllowWithinDeny |
Re-allows individual paths inside denied directories (e.g. deny ~/.aws but allow reading ~/.aws/config) |
fsWriteAllowExtra |
Extra writable roots beyond the current directory (for example a build-output directory in a sibling directory) |
fsWriteDenyWithinAllow |
Punches holes inside writable roots — even if the outer directory allows writes, these subpaths remain read-only |
Path syntax¶
- Paths must be absolute (relative paths are silently dropped, preventing
../escapes); - Three expansions are supported:
~(home directory),${VAR}(environment variable),${PROJECT_ROOT}(project root).
Three-layer merging: tighten only, never loosen¶
Like permission rules, the sandbox block spans the three settings.json layers (~/.biumind/settings.json → project .biumind/settings.json → .biumind/settings.local.json), and the merge rule is a pure union: the project and local layers can only append entries to the four lists — they can never remove or empty entries from the user layer.
[!WARNING] This is a deliberate security baseline: a malicious repository's
.biumind/settings.jsoncannot quietly cancel your global "deny reads of~/.ssh" rule with"fsReadDeny": []. For the same reason, the plugin disable list (plugins.disabled) is likewise an append-only union.
You can verify the merged result at any time with biu doctor — the sandbox check prints the entry counts of the four lists:
[!TIP] Keys in settings.json are camelCase (
fsReadDeny). Writingfs_read_denyproduces no error — you just silently get an empty rule set.biu doctoris where you catch this kind of typo.
Implementation semantics per platform¶
macOS (SBPL, the last matching rule wins):
- baseline allows all reads;
fsReadDenydenies each entry;fsReadAllowWithinDenyre-allows each entry;- deny all writes;
- allow the current directory +
/tmp+/var/folders+fsWriteAllowExtra; fsWriteDenyWithinAllowdenies each entry (so a path appearing in both allow and deny ends up denied — the "hole in the writable root" semantics).
Linux (bwrap, mount arguments applied in order, later ones overriding earlier ones):
fsReadDeny→ the directory is covered by an empty tmpfs (the command sees an empty directory instead of the contents);fsReadAllowWithinDeny→ re-mounted read-only on top of the masked area;- the current directory and
fsWriteAllowExtra→ mounted read-write; fsWriteDenyWithinAllow→ downgraded to a read-only mount;- network off →
--unshare-net.
Skipping the sandbox¶
- The model can pass
dangerously_disable_sandbox: trueon a single call — it skips all sandbox layers (equivalent to running unsandboxed). Bash is treated as a destructive tool by the permissions system and usually needs your confirmation, but the sandbox lists themselves cannot be loosened per call — the four lists are assembled from configuration only at startup; the model's parameter can only tighten or skip entirely; - Background task exception: commands run with
run_in_background: true(dev servers, log tails, etc.) are not sandboxed — these scenarios need network and access outside the project, and they already went through your confirmation when launched; - On platforms where the sandbox tooling is missing / unavailable, commands run unsandboxed and the Bash result notes the actual sandbox mode, so both the model and you can see whether the command was constrained.
Related command cheat sheet¶
| Command | Purpose |
|---|---|
biu doctor |
Check sandbox tool availability and print merged rule counts |
/permissions |
View permission rules and modes |
/add-dir <path> --remember |
Append a writable working directory (persisted) |
biu config validate |
Load all configuration layers and report problems |