Combinators
jail.nix combinators are the building blocks to create Permission
s,
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.*"
];
}
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 Permission
s, 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 Permission
s, 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\""))
open-urls-in-browser
open-urls-in-browser :: Combinator
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.
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.
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.
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.
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.
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, 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.
See BWRAP(1) 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).
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
.
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.