Combinators
jail.nix combinators are the building blocks to create Permissions,
which grant a program specific permissions at runtime.
These permissions can be passed into the third argument to jail
function, as well as
basePermissions.
A Permission is a State -> State function where State is an
internal type that is used to eventually build the final bubblewrap
flags. The type of State is not part of the public API, and may change
in the future.
add-cleanup
add-cleanup :: String -> Permission
Adds arbitrary logic to run when the jail exits.
This is designed to be an easy way to register cleanup actions for things
created in add-runtime. These scripts run in the same
scope as add-runtime so any shell variables defined there will be in
scope.
The cleanup actions may run even if the runtime doesn't— for example if a previous runtime exits non-zero the jail will exit prematurely, but the cleanup actions will still run.
Example:
compose [
(add-runtime ''
TMP_FILE=$(mktemp)
do-something "$TMP_FILE"
'')
(add-cleanup ''
if [ -e "''${TMP_FILE-}" ]; then
rm "$TMP_FILE"
fi
'')
]
add-path
add-path :: String -> Permission
Prepends the passed string to $PATH.
add-pkg-deps
add-pkg-deps :: [Package] -> Permission
Adds the packages' bin directory to $PATH.
add-runtime
add-runtime :: String -> Permission
Adds arbitrary logic to run at runtime, before the jail starts.
You can push additional bubblewrap arguments by appending the bash
array $RUNTIME_ARGS. This allows you to modify the bubblewrap flags
to be dependent on runtime conditions.
Note that anything added here is not run inside the jail. To run arbitrary things at runtime inside the jail see wrap-entry.
Example:
add-runtime ''
# binds /foo only if /bar exists on the host
if [ -e /bar ]; then
RUNTIME_ARGS+=(--bind /foo /foo)
fi
''
If you create any resources in add-runtime that you want to automatically clean up when the jail exits use add-cleanup.
bind-pkg
bind-pkg :: String -> Package -> Permission
Bind mounts the passed derivation at a specified location.
Example:
bind-pkg "/foo" (pkgs.writeText "foo" "bar")
camera
camera :: Permission
Allows access to webcams and other V4L2 video devices at /dev/video*.
compose
compose :: [Permission] -> Permission
Allows combinator composition.
compose [ a b c ] combines a, b, and c.
This is useful when writing your own combinators, for example when using
jail-nix.lib.extend:
jail-nix.lib.extend {
inherit pkgs;
additionalCombinators = combinators: with combinators; {
mycombinator = compose [
(readonly "/foo")
(readonly "/bar")
gpu
];
};
}
dbus
dbus :: { own? :: [String], talk? :: [String], see? :: [String], call? :: [String], broadcast? :: [String] } -> Permission
Grants access to dbus, using
xdg-dbus-proxy (inside a
jail itself) to filter messages that can be sent/received.
All of the args in the passed attrset turn into arguments for
xdg-dbus-proxy. They are all optional.
Example:
dbus {
talk = [
"ca.desrt.dconf"
"org.a11y.Bus"
"org.freedesktop.DBus"
"org.freedesktop.portal.*"
"org.gtk.vfs"
"org.gtk.vfs.*"
];
}
Multiple calls to dbus will only run a single xdg-dbus-proxy with a
union of all passed own/talk/see/call/broadcast permissions. This makes
it possible to write wrapper combinators for specific dbus permissions
and use them in conjunction with each other.
defer
defer :: Permission -> Permission
Runs the passed permission after all other permissions.
A Permission is just a State -> State function. For many
permissions order does not matter since they touch different parts of
state, or they add bubblewrap args that don't affect each other.
However, some more complicated permissions may need to operate on the
final state. By calling defer, this permission will be applied after
all other base and per-jail permissions.
This is useful when writing your own combinators, specifically ones
that operate on state modified by other permissions. This is especially
useful if your combinator is called in basePermissions since those
are applied before any of the per-jail permissions are.
Example:
let
print-hostname = state:
jail.combinators.add-runtime "echo 'Hostname is: ${state.hostname}'"
state;
in jail "test" pkgs.hello (combinators: with combinators; [
# This will print 'Hostname is: jail':
print-hostname
# This will print 'Hostname is: foo'
(defer print-hostname)
(set-hostname "foo")
])
escape
escape :: String -> String
Shell escapes the passed string.
Use noescape to prevent escaping.
escape and noescape don't return Permissions, but they are useful
helpers to expose when defining jails and writing custom combinators, so
they are exposed with the rest of the combinators for convenience.
Example:
jail-nix.lib.extend {
inherit pkgs;
additionalCombinators = combinators: with combinators; {
# a combinator that binds the passed path to /foo
my-combinator = path: unsafe-add-raw-args "--bind ${escape path} /foo"
};
}
fwd-env
fwd-env :: String -> Permission
Forwards the specified environment variable to the underlying process.
If the env var is not set when the jailed application is run, it will exit non-zero.
If you want to be tolerant of the environment being unset, use try-fwd-env instead.
gpu
gpu :: Permission
Exposes the gpu to jailed application.
gui
gui :: Permission
Exposes everything required to get graphical applications to work.
This composes pulse, pipewire, wayland, and forwards/binds a few other paths to get fonts and cursor to render correctly.
include-once
include-once :: String -> Permission -> Permission
Only run the passed permission if include-once hasn't been previously called with the specified key.
This is useful when writing your own combinators.
let
jail = jail-nix.lib.extend {
inherit pkgs;
additionalCombinators = combinators: with combinators; {
# foo isn't `include-once` so each call to it adds a new echo
foo = add-runtime "echo foo";
# bar will only be included once, no matter how many times it is called
bar = include-once "bar" (add-runtime "echo bar");
};
};
in
# Prints:
# foo
# foo
# foo
# bar
# Hello, world!
jail "test" pkgs.hello (c: with c; [
foo
foo
foo
bar
bar
bar
])
jail-to-host-channel
jail-to-host-channel :: String -> String -> Combinator
Allows programs in the jail to execute and pass messages to a specific handler that runs outside of the jail.
The first parameter specifies a name of a program that is exposed to the jail that, when called, sends its first argument to the script passed to the second parameter. The script runs outside of the jail. Any stdout generated by the script is relayed back as stdout from the program in the jail.
Current limitations:
- Only a single argument is supported. For more arguments you will need to use an encoding like JSON.
- Only stdout is relayed back to the jail, stderr will be visible in your terminal but the jail won't be able to read it.
- The first parameter must be a valid POSIX variable name.
Example:
jail-to-host-channel "getHostFileSize" ''
# This runs *outside* of the jail
if [ -f "$1" ]; then
wc -c < "$1"
else
echo "$1 is not a file on the host"
fi
''
This exposes a program inside the jail called getHostFileSize
that returns the size of the file passed in without needing to
give the jail read access to any files.
mount-cwd
mount-cwd :: Permission
Bind mounts the runtime working directory as read-write.
network
network :: Permission
Grants network access to the jail.
This also exposes everything required to allow TLS connections.
You can set your desired hostname with set-hostname. The
default is jail.
no-new-session
no-new-session :: Permission
Disables --new-session
By default, jail-nix includes the --new-session bwrap flag. Doing this
prevents a jailed application from being able to feed keyboard input to
the terminal, however this may break some TUI applications.
See BWRAP(1) for more information and security implications.
noescape
noescape :: String -> NoEscapedString
Prevent the passed string from being automatically shell escaped.
escape and noescape don't return Permissions, but they are useful
helpers to expose when defining jails and writing custom combinators, so
they are exposed with the rest of the combinators for convenience.
It is the caller's responsibility to ensure anything passed to this is correctly escaped.
# Probably doesn't do what you intended since "~/foo" is shell escaped:
(readonly "~/foo")
# This properly makes $HOME/foo readonly in the jail:
(readonly (noescape "~/foo"))
# Binds the path specified by the runtime $FOO variable as read only.
#
# Note that we must properly quote this to ensure bash correctly keeps it
# as a single argument, even if it contains spaces:
(readonly (noescape "\"$FOO\""))
notifications
notifications :: Permission
This adds the dbus combinator with talk permission to
org.freedesktop.Notifications which allows the jailed software to send
desktop
notifications.
open-urls-in-browser
open-urls-in-browser :: Permission
Allows access to open URLs in $BROWSER.
This works by creating a pipe that is mounted into the jail that forwards
all URLs to the $BROWSER outside of the jail. This way the jailed
program can launch your browser, even if it has a subset of the
permissions your browser has.
Only URLs beginning with http(s):// will be forwarded.
persist-home
persist-home :: String -> Permission
Persists the home directory across all jails with the specified name.
This is useful for a lot of software that may want to write arbitrary things into your home directory and expect to read them back in a future invocation.
The home directory is persisted in ~/.local/share/jail.nix/home/<name>.
pipewire
pipewire :: Permission
Exposes pipewire to the jailed application.
pulse
pulse :: Permission
Exposes pulseaudio to the jailed application.
readonly
readonly :: String -> Permission
Binds the specified path in the jail as read-only.
This will error if the file does not exist. If you want to be tolerant of missing files, see try-readonly.
readonly-paths-from-var
readonly-paths-from-var :: String -> String -> Permission
This binds multiple paths as read-only specified by a single runtime environment variable.
The first argument to this combinator is the runtime environment variable
that contains a list of paths to be bound. The second argument is a
deliminator to split the paths (Typically either " " or ":").
This is useful for variables like XDG_DATA_DIRS, GTK_PATH,
XCURSOR_PATH, etc.
Example:
compose [
(readonly-paths-from-var "XDG_DATA_DIRS" ":")
(readonly-paths-from-var "XCURSOR_PATH" " ")
]
readonly-runtime-args
readonly-runtime-args :: Permission
Binds any valid paths passed in as arguments to the jailed program at runtime as read-only.
readwrite
readwrite :: String -> Permission
Binds the specified path in the jail as read-write.
This will error if the file does not exist. If you want to be tolerant of missing files, see try-readwrite.
readwrite-runtime-args
readwrite-runtime-args :: Permission
Binds any valid paths passed in as arguments to the jailed program at runtime as read-write.
ro-bind
ro-bind :: String -> String -> Permission
Binds the specified path on the host to a path in the jail as read-only.
This will error if the host file does not exist. If you want to be tolerant of missing files, see try-ro-bind.
Example:
# Binds /foo on the host to /bar in the jail
ro-bind "/foo" "/bar"
rw-bind
rw-bind :: String -> String -> Permission
Binds the specified path on the host to a path in the jail as read-write.
This will error if the host file does not exist. If you want to be tolerant of missing files, see try-rw-bind.
Example:
# Binds /foo on the host to /bar in the jail
rw-bind "/foo" "/bar"
set-argv
set-argv :: [String] -> Permission
Overrides the current argv that is passed to the jailed executable.
By default argv is set to noescape "$@" which will forward whatever
arguments are provided to the wrapper script at runtime. Calling this
will override the current value.
set-env
set-env :: String -> String -> Permission
Sets the specified environment variable in the jail.
This will throw if the variable name is not a valid posix variable name.
set-hostname
set-hostname :: String -> Permission
Sets the hostname to use for the network combinator.
Must be specified before network.
Example:
[
(set-hostname "foo")
network
]
share-ns
share-ns :: String -> Permission
Removes the call to --unshare- for the provided namespace.
By default, jail-nix unshares all namespaces, this combinator allows you to remove the unshare, allowing the jailed app access to the specified host namespace.
For instance, calling share-ns "pid" will remove the --unshare-pid flag
from bwrap which will allow this process to share the same pid namespace as
the host.
The valid namespaces that can be passed to this combinator are: "cgroup"
"ipc" "net" "pid" "user", and "uts".
See wikipedia:Linux namespaces for more information.
time-zone
time-zone :: Permission
Exposes your timezone.
tmpfs
tmpfs :: String -> Permission
Mounts a new tmpfs at the specified location.
try-fwd-env
try-fwd-env :: String -> Permission
Forwards the specified environment variable to the underlying process (if set).
try-readonly
try-readonly :: String -> Permission
Binds the specified path in the jail as read-only if it exists.
try-readwrite
try-readwrite :: String -> Permission
Binds the specified path in the jail as read-write if it exists.
try-ro-bind
try-ro-bind :: String -> String -> Permission
Binds the specified path on the host to a path in the jail as read-only if it exists.
Example:
# Binds /foo on the host to /bar in the jail if /foo exists
try-ro-bind "/foo" "/bar"
try-rw-bind
try-rw-bind :: String -> String -> Permission
Binds the specified path on the host to a path in the jail as read-write if it exists.
Example:
# Binds /foo on the host to /bar in the jail if /foo exists
try-rw-bind "/foo" "/bar"
unsafe-add-raw-args
unsafe-add-raw-args :: String -> Permission
Adds the raw string passed into it into the call to bubblewrap.
Nothing is escaped, it is the caller's responsibility to ensure everything is properly escaped.
unsafe-dbus
unsafe-dbus :: Permission
Exposes D-Bus to the jailed program.
This does no message filtering so it is marked as unsafe. If you want more control over the messages that can be sent/received, consider using the dbus combinator instead.
unsafe-x11
unsafe-x11 :: Permission
Exposes X11 to the jailed application.
Note that applications may be able to break out of the jail because X11 is not designed to be a security boundary.
For a safer alternative, consider using the xwayland combinator inside of a wayland compositor.
wayland
wayland :: Permission
Exposes your wayland compositor to the jail.
wrap-entry
wrap-entry :: (String -> String) -> Permission
Wraps the binary to be jailed in a bash script that will be the new entrypoint to the jail.
This similar in spirit to the add-runtime combinator,
except that this runs inside the jail, while add-runtime runs before
the jail starts.
Example:
wrap-entry (entry: ''
echo 'Inside the jail!'
${entry}
echo 'Cleaning up...'
'')
write-text
write-text :: String -> String -> Permission
Bind mounts a read-only text file at a path.
Example:
# This will create a text file in the jail at `/hello.txt`
write-text "/hello.txt" "Hello, world!"
xwayland
xwayland :: Permission
Safely allow X11 apps to render to a wayland compositor.
This combinator runs xwayland-satellite inside the jail and only exposes wayland combinator.
This has the advantage of not allowing multiple jailed X11 applicaitons to see each other since each jailed applicaiton gets its own xwayland-satelite server.
However, doing it this way does mean that every jailed applicaiton you run with this combinator will spin up its own personal xwayland-satelite server, which will consume more resources than having a global one.
Default Included Combinators
The following combinators are enabled by default, and do not need to be
explicitly added to your jails unless you override
basePermissions.
base
base :: Permission
This sets up a basic minimal set of permissions that a lot of software will need.
This combinator:
- Sets up a fake proc at
/proc(Using--proc /procbwrap args) - Sets up a fake dev at
/dev(Using--dev /devbwrap args) - Sets up a tmpfs at
/tmp(Using--tmpfs /tmpbwrap args) - Sets up a tmpfs at your home (Using
--tmpfs ~bwrap args) - Ensures all processes in the jail are killed when the jail exits
(Using
--die-with-parentbwrap arg) - Adds coreutils to the package deps (
add-pkg-deps [ pkgs.coreutils ]) - Binds
/bin/shfrompkgs.bash(ro-bind "${pkgs.bash}/bin/sh" "/bin/sh") - Clears the environment except
LANG,HOMEandTERM
This is included in the base permissions by default so you shouldn't need
to include it unless you override base permissions. It is exposed as a
combinator (like the other default included combinators) so that you can
use it in a custom
basePermissions.
bind-nix-store-runtime-closure
bind-nix-store-runtime-closure :: Permission
Binds all /nix/store paths in the runtime closure of the jailed
application.
If you don't have any sensitive nix store paths, you may consider just
bind mounting /nix/store inside the jail instead.
For example, the jail defined by
# Note that this combinator is included in base permissions, so it does
# not need to be provided:
let listNixStore = pkgs.writeShellScriptBin "list-nix-store" "ls -l /nix/store";
in jail "list-nix-store" listNixStore [];
will print something like
total 52
dr-xr-xr-x 3 65534 65534 4096 Jan 1 1970 2dx846w0q80307z72r9jxai4xlj9ghb2-list-nix-store
dr-xr-xr-x 4 65534 65534 4096 Jan 1 1970 3mi59bgj22xx29dyss7jhmx3sgznd85m-acl-2.3.2
dr-xr-xr-x 3 65534 65534 4096 Jan 1 1970 6hqzbvz50bm87hcj4qfn51gh7arxj8a6-gcc-14.2.1.20250322-libgcc
dr-xr-xr-x 4 65534 65534 4096 Jan 1 1970 6nkqdqzpa75514lhglgnjs5k4dklw4sb-libidn2-2.3.8
dr-xr-xr-x 4 65534 65534 4096 Jan 1 1970 7c0v0kbrrdc2cqgisi78jdqxn73n3401-gcc-14.2.1.20250322-lib
dr-xr-xr-x 4 65534 65534 4096 Jan 1 1970 87fck6hm17chxjq7badb11mq036zbyv9-coreutils-9.7
dr-xr-xr-x 5 65534 65534 4096 Jan 1 1970 8syylmkvnn7lg2nar9fddpp5izb4gh56-attr-2.5.2
dr-xr-xr-x 6 65534 65534 4096 Jan 1 1970 cg9s562sa33k78m63njfn1rw47dp9z0i-glibc-2.40-66
dr-xr-xr-x 3 65534 65534 4096 Jan 1 1970 nzg6zqsijbv7yc95wlfcdswx6bg69srq-gmp-with-cxx-6.3.0
-r--r--r-- 1 65534 65534 145 Jan 1 1970 vylji4sibxm3mr3hync4zcmpmgbv09az-list-nix-store-runtime-closure
dr-xr-xr-x 4 65534 65534 4096 Jan 1 1970 xy4jjgw87sbgwylm5kn047d9gkbhsr9x-bash-5.2p37
dr-xr-xr-x 3 65534 65534 4096 Jan 1 1970 yypqcvqhnv8y4zpicgxdigp3giq81gzb-libunistring-1.3
dr-xr-xr-x 3 65534 65534 4096 Jan 1 1970 za53jjhjl1xajv3y1zpjvr9mh4w0c1ay-xgcc-14.2.1.20250322-libgcc
rather than your entire nix store.
fake-passwd
fake-passwd :: Permission
Generates and mounts fake /etc/passwd and /etc/group files in the jail.
The fake /etc/passwd and /etc/group files contains a root user, and
forward the calling user's user id, username, group id and group name.
If you do not want to hide the users and groups that exist on your system,
you may consider just bind mounting /etc/passwd and /etc/group inside
the jail instead.
Deprecated Combinators
The following combinators have been deprecated, and may be removed in the future.
dbus-unsafe
dbus-unsafe :: Permission
This was renamed to unsafe-dbus.
persisthome
persisthome :: Permission
This was reworked to store data under ~/.local/share/jail.nix and renamed to persist-home.