Security Model
jai is a casual sandbox. This page explains what it protects, what it does not, and where its boundaries are. Read this before deciding whether jai is sufficient for your use case.
Integrity vs confidentiality
jai is primarily concerned with protecting your file system outside of the directories where you run agents. Depending on the mode, it is concerned with one or two things:
- Integrity — Can the jailed process destroy or modify files it shouldn't? jai's overlay and mount isolation address this.
- Confidentiality — Can the jailed process read files it shouldn't? This depends entirely on the mode.
Casual mode primarily provides integrity. Strict mode provides both integrity and confidentiality. Bare mode provides integrity with weaker confidentiality.
Another area of concern is whether jailed processes can attack the system through other processes. To guard against that, jai hides your $XDG_RUNTIME_DIR (where dangerous sockets often live), and runs processes in an isolated PID namespace. However, there are plenty of other ways to attack the system, particularly if you grant jailed code access to your Xorg or other servers on the system. Truly malicious code may be able to escape jai indirectly, though so far most AI agent horror stories involve storage destruction.
Casual mode
Casual mode mounts your home directory as a copy-on-write overlay. Writes (except for the directory where you invoked jai and below) go to $HOME/.jai/<name>.changes and do not modify originals. The current working directory and any subdirectories are writable as usual. /tmp and /var/tmp are private. The rest of the filesystem is read-only.
What it protects against:
- Accidental or runaway deletion of files outside granted directories
- Rogue
rm -rf ~or similar destructive commands (they will destroy the directory in which you ran jai and any granted directories, but not the rest of your home directory). - Agents modifying config files, dotfiles, or data in your home directory
What it does not protect:
- Confidentiality. The process runs as your user and can read anything you can read — SSH keys, API tokens, browser data, credential files. The
--maskoption and default blacklists hide some sensitive dotfiles, but casual mode exposes everything readable by default. You cannot enumerate and mask every sensitive file. - Granted directories. The current working directory (and any
-ddirectories) have full read/write access. A jailed process can destroy everything in those directories. - Network access. jai does not restrict network access. A jailed process can exfiltrate data, make API calls, or connect to remote services.
- UI interactions. Jailed programs can interact with your terminal, and if you grant them access to your Xorg session (which happens by default in casual mode unless you mask or delete
.Xauthority), can see your cut buffer or even send keystroke events to other windows.
Strict mode
Strict mode runs the jailed process as the unprivileged jai system user with an empty private home directory. Granted directories are exposed through id-mapped mounts.
What it adds over casual mode:
- UID-based confidentiality. Because the process runs as a different user, it cannot read files that are only accessible to your UID. This blocks access to most of the sensitive data in your home directory.
- Empty home directory. No dotfiles, no cached credentials, no browser profiles — the jailed process starts clean.
What it still does not protect:
- World-readable files. Files with permissions like
644outside your home directory are still readable (e.g.,/etc/passwd, system configs). This is inherent to UID-based isolation without a full container. - Granted directories. As with casual mode, directories you grant via
-dare fully read/write. - Network access. No network restrictions.
Bare mode
Bare mode uses an empty private home directory but runs as your user. It exists primarily as a fallback for NFS home directories, where id-mapped mounts are not supported.
Compared to strict mode:
- Same empty home directory
- Same private
/tmpand/var/tmp - But the process runs as your UID, so it can read any file accessible to you outside the home directory
- No confidentiality benefit beyond hiding the home directory contents
Use bare mode only when strict mode is not available due to NFS constraints.
PID namespace
All three modes use a private PID namespace (via CLONE_NEWPID). This means:
- Jailed processes cannot
killor send signals to processes outside the jail - Jailed processes cannot
ptraceprocesses outside the jail psinside the jail only shows jailed processes
This is a meaningful defense against a rogue process trying to interfere with your other running programs.
File masking
The --mask option applies only to casual jails and provides a measure of confidentiality for your most sensitive files, if you remember to enumerate them. The option creates an overlay "whiteout" file to hide a specific path in your home directory. The default .defaults file masks common sensitive paths such as .ssh and .gnupg. You can only mask files relative to your home directory, because only your home directory is an overlay.
Limitations:
- Masks are only applied when the overlay mount is first created. If you add new
maskdirectives, runjai -ufirst. --unmaskonly reverses a previous--maskin the same config chain — it cannot unmask files already masked in an existing overlay. (To do that, you have to stop everything withjai -u, then go into$HOME/.jai/*.changesand delete the whiteout files hiding the underlying files you want to expose again.)- If you mask a parent directory,
--unmaskon a child path has no effect. - In casual mode, masking is defense-in-depth, not a guarantee. Because the process runs as your user, it may be able to read the underlying files through other paths if the overlay is misconfigured.
- Beware of backup files. E.g.,
.netrcmay be masked by default, but if your editor creates a backup file.netrc~, or a temporary file.#.netrc#, those will not be masked by default.
Overlayfs caveats
- The overlay work directory (
$HOME/.jai/*.work) occasionally accumulates root-owned files that the user cannot delete. Runjai -uto clean these up. - If extended attributes on the
changesdirectory get out of sync, the overlay may fail to mount. Creating a freshchangesdirectory can resolve this. - Overlayfs requires extended attribute support in the "upper" directory. NFS does not support extended attributes, so casual mode overlays do not work if your
.changesdirectories are on NFS. See below to get around this.
NFS limitations
NFS home directories cannot use:
Strict mode — id-mapped mounts are not supported on NFS. You have to fall back to bare mode.
Casual mode overlays — overlayfs requires extended attributes in the "upper" directory.
To get around this limitation you can make
$HOME/.jaia symbolic link to a local directory you own on a local disk, or you can set theJAI_CONFIG_DIRenvironment variable to relocate configuration files from$HOME/.jai. Finally, you can keep your configuration files in your home directory and move just the storage directories off NFS via the--storageconfiguration option.
Environment variable filtering
jai filters environment variables matching patterns like *_TOKEN, *_KEY, *_PASSWORD, etc. from the jailed environment. The default .defaults file sets these patterns. You can add more by editing the .defaults file or with the --unsetenv option. You can also selectively re-expose variables with --setenv.
This is a best-effort measure. Environment variable filtering cannot catch every secret — programs may store credentials in files, keyrings, or other locations. You could potentially filter everything with unsetenv * and then whitelist individual environment variables by putting the following in a configuration file:
unsetenv *
setenv HOME
setenv PATH
setenv SHELL
setenv TERM
setenv EDITOR
setenv LANG
setenv PAGER
(If you say setenv VAR, as opposed to setenv VAR=VALUE, it simply exposes the existing value of the environment variable, overriding a previous unsetenv.)
What jai is not
jai is not:
- A hardened container runtime (like Docker with seccomp and AppArmor profiles)
- A virtual machine
- A replacement for
gVisor,Kata Containers, or other strong isolation tools - A multi-tenant isolation boundary
- A defense against a determined adversary with local access
When to use a real container or VM
Use a proper container or VM when:
- You are running untrusted code from unknown sources (not just AI agents you chose to install)
- You need strong multi-tenant isolation between users or workloads
- You need network-level isolation (jai does not restrict network access)
- You need syscall filtering (seccomp) or mandatory access control (AppArmor, SELinux)
- The consequences of a sandbox escape are severe (production data, financial systems, medical records)
jai is designed for a different situation: you have a tool you broadly trust but want to limit the damage from bugs, mistakes, and overly eager file operations. It makes the common case safer without the overhead of a full container or VM.