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

Source

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

Source

Prepends the passed string to $PATH.


add-pkg-deps

add-pkg-deps :: [Package] -> Permission

Source

Adds the packages' bin directory to $PATH.


add-runtime

add-runtime :: String -> Permission

Source

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

Source

Bind mounts the passed derivation at a specified location.

Example:

bind-pkg "/foo" (pkgs.writeText "foo" "bar")

camera

camera :: Permission

Source

Allows access to webcams and other V4L2 video devices at /dev/video*.


compose

compose :: [Permission] -> Permission

Source

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

Source

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

Source

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

Source

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

Source

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

Source

Exposes the gpu to jailed application.


gui

gui :: Permission

Source

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

Source

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

Source

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

Source

Bind mounts the runtime working directory as read-write.


network

network :: Permission

Source

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

Source

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

Source

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\""))

open-urls-in-browser

open-urls-in-browser :: Combinator

Source

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

Source

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

Source

Exposes pipewire to the jailed application.


pulse

pulse :: Permission

Source

Exposes pulseaudio to the jailed application.


readonly

readonly :: String -> Permission

Source

Binds the specified path in the jail as read-only.


readonly-paths-from-var

readonly-paths-from-var :: String -> String -> Permission

Source

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

Source

Binds any valid paths passed in as arguments to the jailed program at runtime as read-only.


readwrite

readwrite :: String -> Permission

Source

Binds the specified path in the jail as read-write.


readwrite-runtime-args

readwrite-runtime-args :: Permission

Source

Binds any valid paths passed in as arguments to the jailed program at runtime as read-write.


ro-bind

ro-bind :: String -> String -> Permission

Source

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

Source

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

Source

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

Source

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

Source

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

Source

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

Source

Exposes your timezone.


tmpfs

tmpfs :: String -> Permission

Source

Mounts a new tmpfs at the specified location.


try-fwd-env

try-fwd-env :: String -> Permission

Source

Forwards the specified environment variable to the underlying process (if set).


unsafe-add-raw-args

unsafe-add-raw-args :: String -> Permission

Source

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

Source

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

Source

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

Source

Exposes your wayland compositor to the jail.


wrap-entry

wrap-entry :: (String -> String) -> Permission

Source

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

Source

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

Source

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

Source

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

Source

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

Source

This was renamed to unsafe-dbus.


persisthome

persisthome :: Permission

Source

This was reworked to store data under ~/.local/share/jail.nix and renamed to persist-home.