Skip to content

Configuration reference

TOML. Unknown keys are rejected, so a typo is an error rather than a silently ignored rule.

Discovery order

Lowest precedence first:

  1. The implicit grant of the target executable, read and execute.
  2. Profile: the untrusted floor, plus the profile named by --profile, or one that claims this target with applies_to. A profile is either bundled or a file in $XDG_CONFIG_HOME/bailey/profiles/.
  3. $XDG_CONFIG_HOME/bailey/config.toml, or ~/.config/bailey/config.toml.
  4. Every bailey.toml found walking up from the target's directory and from the working directory, ordered by path depth, shallowest first. A file found by both walks contributes once; at equal depth the working directory wins.
  5. The file given to --config.

[filesystem]

KeyTypeDescription
readlist of pathsGrant read on each path hierarchy
writelist of pathsGrant write on each path hierarchy
executelist of pathsGrant execute on each path hierarchy
denylist of pathsRetract a grant of the same path and record the path as denied
read_onlylist of pathsMake a path read-only inside a writable grant. Needs the isolation layer
resetboolClear all filesystem grants from lower layers before applying this one

Rights for the same path combine across layers. A grant on a directory covers everything beneath it.

toml
[filesystem]
read = ["~/.config/app", "./assets"]
write = ["./saves"]
execute = ["./program"]
deny = ["~/.config/app/token"]

Granting and denying the same path within one layer is an error.

A read-only island

read_only is how you keep part of a writable hierarchy from being written:

toml
[filesystem]
write = ["~/.local/share/thing"]
read_only = ["~/.local/share/thing/versions"]

Unlike deny, the contents stay readable; only writes are refused. It is enforced by mounting the path over itself read-only, which the VFS honours whatever Landlock says, so it needs the isolation layer. That is the default; under --no-isolate the run warns and the path stays writable.

A nested deny needs the isolation layer

A denial of a path inside a granted directory is enforced by covering the path with an empty read-only filesystem, which only the isolation layer can do. That layer is on by default; under --no-isolate the denial is reported as unenforced before the run starts.

[network]

KeyTypeDescription
egress"deny" or "allow"Outbound policy. Default "deny"
egress_allowlist of tablesPermitted destinations, { host, port }
bind_portslist of integersTCP ports the target may bind
toml
[network]
egress_allow = [{ host = "*", port = 443 }]
bind_ports = [27015]

egress_allow takes precedence over egress when both are present. bind_ports accumulate across layers; egress is replaced by the nearest layer that sets it.

  • host is advisory. Landlock matches on TCP port only, and bailey warns when host is anything other than *.
  • An entry without a port is a resolution error, because the resulting policy would mean the opposite of what it says.
  • Only TCP is restricted. See known limitations.

[[device]]

An array of tables, one per device.

KeyTypeDescription
pathpathDevice node or directory
accessstringAny combination of r, w, x
toml
[[device]]
path = "/dev/dri"
access = "rw"

Device grants use filesystem semantics and accumulate across layers like filesystem grants.

[resources]

KeyTypeDescription
memorystringMaximum memory. Bytes, or a suffix
pids_maxintegerMaximum processes and threads
cpu_percentintegerCPU quota as a percentage of one core
tmp_sizestringSize of the private /tmp. Default 64MiB
shm_sizestringSize of the private /dev/shm. Default 256MiB

Accepted size suffixes, case-insensitive: b, k/kb (1000), kib (1024), m/mb, mib, g/gb, gib, t/tb, tib.

toml
[resources]
memory = "2GiB"
pids_max = 512
cpu_percent = 150

Each key is replaced by the nearest layer that sets it. Applied through cgroup v2, best-effort.

[env]

KeyTypeDescription
passlist of namesForward these caller variables. A trailing * matches by prefix
settableDefine variables outright
denylist of namesRemove these from the final environment, including the base set
resetboolDiscard everything lower layers passed or set
toml
[env]
pass = ["MANGOHUD*"]
set = { RUST_LOG = "debug" }
deny = ["TERM"]

The target's environment is built, not inherited: it starts empty, gets a base set of PATH, HOME, TMPDIR, TERM, LANG, LC_*, USER, LOGNAME, SHELL, and TZ, and takes nothing else from the caller unless named here. See environment and storage.

applies_to

A top-level key, meaningful only in a profile under $XDG_CONFIG_HOME/bailey/profiles/. It lists the programs the profile is for, so that bailey selects it without --profile.

toml
applies_to = ["claude", "/opt/thing/bin/thing"]

An entry with no / matches the target's file name; one with a / must match the resolved path. An explicit --profile overrides it, and two profiles claiming one target is an error.

home

A top-level key giving the host directory to use as the target's private home, overriding the derived $XDG_DATA_HOME/bailey/<target>/home.

toml
home = "~/games/thegame/home"

Granting your real home in [filesystem] turns the private home off entirely.

[hooks]

KeyTypeWhen
pre_launchlist of commandsBefore the target starts. Non-zero exit aborts the run
post_exitlist of commandsAfter the target terminates. Gets BAILEY_EXIT_CODE

on_violation was removed: nothing could trigger it. A config that still sets it is accepted with a warning rather than rejected. See lifecycle hooks.

Commands run through sh -c and accumulate across layers, running in layer order.

Path handling

  • Relative paths resolve against the directory of the config file containing them.
  • ~ and ~/... expand using $HOME.
  • ${VAR} expands from the environment, which is how a shared profile can name something like ${XDG_RUNTIME_DIR}. An unset variable expands to nothing, leaving a path that matches nothing rather than one that matches something unintended.
  • ${PWD} is the directory the run was launched from, asked of the kernel when the shell has not exported it. A profile uses it to grant "wherever I am", which is what lets a per-program policy work without a config file in every project. Launch from a directory you actually want granted: from your home, it grants your home.
  • . and .. are collapsed lexically, so different spellings of one path merge into a single grant.

Errors

ErrorCause
failed to parse configNot valid TOML, or an unknown key
invalid configA bad value, such as an unknown access character or size suffix
conflicting directivesOne layer both granted and denied the same path
failed to read configThe file named by --config does not exist or is unreadable

Released under the MIT or Apache-2.0 license.