Permissions
How Linux (bubblewrap / Landlock), macOS Seatbelt and the Windows ACL runner differ, and the read-only / workspace-write / danger-full-access modes.
qialike runs the confined bash (or pwsh on Windows) through the harness’s
ctx.sandbox.confine(). All three platforms get it out of the box: Linux prefers the host’s
bubblewrap and falls back to qialike’s own Landlock (which needs the LSM enabled in the
kernel, ≥5.13); macOS uses the system Seatbelt; Windows uses the harness’s ACL
restricted-token runner. danger-full-access runs unconfined.
The sandbox’s boundary is “a child process sharing the host’s kernel and filesystem”. When you need to isolate the whole environment, use a container, a microVM or a remote executor.
Three modes
Press Tab in the composer to cycle them; the status line shows the current one:
| Mode | Status-line label | Effect |
|---|---|---|
read-only | Read Only | Writes are denied, except required sinks such as /dev/null. |
workspace-write | Workspace Write | Writes are allowed inside the workspace root and in the temporary areas the backend defines. |
danger-full-access | Full access | Bypasses confinement; the raw argv is spawned directly. |
A few semantics:
- The default is
workspace-write. When a session is resumed, the mode comes from the session’s last persistedsandbox/modeevent — that is your last choice, not a fresh default. - The mode and the approval policy are decoupled. Switching to
danger-full-accessraises the file boundary alone; it does not quietly turn the approval policy into “never ask”. Turning approvals off is always an explicit human action (choosing “Allow always” for a tool in the approval dialog). - A refused call carries the
[sandbox: file access denied under <mode> mode]marker; the model may retry the identical call once withsandbox_permissions(the narrowest wider mode that would suffice) plus ajustification, and a human approves it. An escalation has to be strictly wider than the mode in effect, and it applies to that one call only.
Linux: bubblewrap → Landlock, two rungs
Confined bash runs through bwrap (bubblewrap) first, and needs a kernel with
unprivileged user namespaces enabled. Where bubblewrap is installed this is the rung taken, and
it is the stronger one:
# Debian / Ubuntusudo apt install bubblewrap
# Fedora / RHELsudo dnf install bubblewrap
# Archsudo pacman -S bubblewrapNo bubblewrap does not mean no sandbox: qialike carries its own Landlock rung (its launcher
is implemented in apps/tui-bin/src/landlock-shim.ts), taken automatically whenever the kernel has
the Landlock LSM enabled (≥5.13) — still a kernel-level constraint.
Only when both rungs are unavailable does the following happen (that, and not a missing bwrap,
is what triggers SANDBOX_UNAVAILABLE):
- The app boots as usual.
- Every non-shell tool works (the pure-JS
fs-sandboxfence is still there). - But under
workspace-write/read-onlyevery bash call fails closed withSANDBOX_UNAVAILABLErather than running unconfined. - Only
danger-full-access(no confinement) runs. - Where the executor offers no kernel confinement, per-call shell approval takes over — the prompt states that the command has your full user authority.
This is deliberate fail-closed design: when the requested mode cannot be enforced, the call
fails with SANDBOX_UNAVAILABLE instead of being silently let through unconfined. The error text
names the missing platform runner.
A kernel with unprivileged user namespaces disabled can produce the same outcome:
# checkcat /proc/sys/kernel/unprivileged_userns_clone # Debian/Ubuntu familycat /proc/sys/user/max_user_namespacesmacOS: Seatbelt
macOS uses the system’s built-in Seatbelt (sandbox-exec). The workspace-write writable scope
is derived from one shared writable root used by both the Seatbelt profile and the in-process fs
fence, so the two grant exactly the same roots.
There is nothing to install. If sandbox-exec is unavailable the handling matches Linux: a mode
that cannot be enforced fails with SANDBOX_UNAVAILABLE.
Windows: the ACL restricted-token runner
Windows gets OS-level isolation from the harness’s ACL restricted-token runner, out of the box
like the other two platforms. Two things have to be carried for it to run inside the single-file
build: the native koffi it depends on (replaced by apps/tui-bin/stub/koffi.js), and the runner
process itself — the harness locates it by module specifier, which a compiled binary cannot
answer, so qialike carries the harness’s own runner and acts as its launcher (see
apps/tui-bin/src/windows-acl-shim.ts).
The permission (presets) row is enabled on every platform, Windows included.
What that means in practice:
- The pure-JS
fs-sandboxfence plus theworkspace-write + askapproval boundary remain the gate on file effects. - The ACL runner provides kernel-level process isolation.
- You can still use qialike inside WSL2 (which gets the Linux bubblewrap / Landlock rungs), but that is optional, not required. See Windows and WSL.
Design boundaries
- Enforcement completeness is reported per call:
fullmeans every file operation the backend’s governing mode promises,partialmeans only a subset is governed. A consumer that needs an absolute boundary can refuse or surfacepartial. - Policy travels with the call, not fixed on the provider: two consumers can confine under different policies at the same time; an approved escalation retry is simply a new call made with a wider policy.
- Secret files cannot be read: the
.envfamily,.gitinternals and harness credential documents are refused outright (.env.exampleexcepted). This holds in every sandbox mode, anddanger-full-accessdoes not lift it. It fences the read tools only: a shell command that names those files still reads them — a gap recorded honestly here, and one Gemini CLI shares. - A repository cannot loosen your sandbox: rows that change (or disable)
sandbox/sandbox-policy/fs-sandbox/bash-sandbox/pwsh-sandbox/approval/permission/fs-observation-policycan only be written in your own overlay; such rows in a repository-level overlay are always refused. Use--no-project-overlay(QIALIKE_NO_PROJECT_OVERLAY=1) to ignore that layer for one run. See Config.
Next steps
- Windows and WSL — use Windows natively, or optionally through WSL2
- Troubleshooting — handling
SANDBOX_UNAVAILABLE - Commands —
qialike --dump-configshows the composed layers