# nixpak turns a Nix package into a bubblewrap sandbox declaration

> nixpak is a declarative wrapper around bubblewrap that sandboxes Nix packaged applications, including graphical ones, with sloth values for runtime paths, D-Bus policies, a Flatpak shim and optional network isolation through pasta.

**nixpak/nixpak** — Runtime sandboxing for Nix

- Repository: https://github.com/nixpak/nixpak
- Stars: 672 · Forks: 39
- Language: Nix
- License: EUPL-1.2
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/nixpak-nixpak

## A bubblewrap command line generated from a module

nixpak describes itself as a fancy declarative wrapper around bubblewrap. That single sentence explains most of the design. The sandbox itself is bubblewrap, a well known tool for creating namespaces and bind mounts, and nixpak's job is to let you describe the sandbox as Nix configuration instead of hand-writing a bwrap invocation.

The configuration is a NixOS style module. The example in the README passes a `sloth` argument and then a `config` attribute set, and the pieces map closely onto what bubblewrap can do. The listed features are bind-mounting any host path read-write or read-only, bind-mounting devices, full network isolation, D-Bus access control, and the Flatpak shim. Under the hood the module turns those options into a launcher script that sets up the sandbox and then runs the wrapped application.

Two integrations are optional rather than built in, and they matter for how isolated the result is. pasta is used for highly customizable network isolation, xdg-dbus-proxy handles D-Bus service access control, and wayland-proxy-virtwl handles Wayland protocol access control. The last two are the reason a graphical application can run under a restricted D-Bus and Wayland policy without being handed the real session bus.

The module outputs two different things. The `script` output is just the wrapped binary, which suits a command line application. The `env` output is a symlinkJoin that resembles the original package, with the main binary replaced by the wrapper script and textual references to the binary rewritten as well, which is what makes it usable for GUI applications.

## The Flatpak shim and why the home directory stays out

The most interesting feature is the one that borrows from another ecosystem. The Flatpak shim is described as fooling xdg-desktop-portal into thinking your Nix application is a Flatpak. Its purpose is the Document Portal, which lets an application open and save files in arbitrary locations without those paths being bind-mounted into the sandbox in advance.

Without that trick, the only way an application can write to your documents is for you to bind-mount the directory in, which means enumerating paths ahead of time and invalidating the sandbox whenever you want a new one. With the portal in the way, the application asks the portal for access and the portal decides, using the same mechanism a Flatpak application uses.

That mechanism needs an application identifier. The configuration therefore has a `flatpak.appId` setting, which the example sets to a value of its own. The comment in the example notes that it has to be set for the Flatpak emulation, and that the default is generated from the derivation name, with a derivation called hello producing Hello and my-app producing MyApp.

The D-Bus side has a similar structure. Access control is expressed as a policy map whose values use the same verbs as the busctl tool, namely see, talk and own. The example grants talk to org.freedesktop.DBus and to ca.desrt.dconf, the latter being the settings daemon most desktop applications write their preferences to.

## Writing a nixpak flake

A nixpak configuration lives in a flake. The inputs section pulls nixpkgs and nixpak itself, and the nixpak input is told to follow the nixpkgs input so that both use the same package set:

```nix
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    nixpak = {
      url = "github:nixpak/nixpak";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };
```

Inside the outputs, the helper that does the work is `nixpak.lib.nixpak`, called with the library and the package set inherited from nixpkgs:

```nix
      mkNixPak = nixpak.lib.nixpak {
        inherit (pkgs) lib;
        inherit pkgs;
      };
```

What follows is the per-application configuration, where the package to isolate is named and the path to its executable is given. The example notes that the binary path is usually autodetected but can be set explicitly anyway, and it shows the D-Bus block that grants the two talk permissions:

```nix
          dbus.policies = {
            "org.freedesktop.DBus" = "talk";
            "ca.desrt.dconf" = "talk";
          };
```

The bubblewrap section then decides what the sandbox can reach. Setting network to false disables all network access, and the bind lists control which host paths appear inside:

```nix
            bind.rw = [
              (sloth.concat' sloth.homeDir "/Documents")
              (sloth.env "XDG_RUNTIME_DIR")
              # a nested list represents a src -> dest mapping
              # where src != dest
              [
                (sloth.concat' sloth.homeDir "/.local/state/nixpak/hello/config")
                (sloth.concat' sloth.homeDir "/.config")
              ]
            ];
```

Two shapes appear there. A plain string is mounted at the same path inside the sandbox, while a nested list is a source and destination pair, which is how the example maps its state directory onto the .config path that the application actually looks for. Device mounts are declared separately, and the example asks for /dev/dri so that graphics acceleration is available.

## Sloth values postpone path decisions until launch

Most of the awkwardness in sandbox configuration comes from paths that differ per user and per machine. A configuration that hardcodes /home/alice is useless to anyone else, so nixpak introduced what it calls sloth values: values whose evaluation is delayed until runtime, named for their extraordinary laziness.

They are constructed from the `sloth` attribute set, which arrives as a module argument, so the module signature is where the dependency is declared:

```nix
{ sloth, ... }:

{
  bubblewrap.bind.ro = [ sloth.homeDir ];
}
```

The set of available constructors is small and each does one thing. `sloth.env` takes an environment variable name and resolves it at runtime, so sloth.env "HOME" resolves to the user's home directory at launch rather than at build time. `sloth.mkdir` ensures a directory exists, creating it and any missing parents with permissions 0700, and returns the resolved value. `sloth.concat` joins a list of sloth values and `sloth.concat'` joins two without the list, which is the form used throughout the example.

There are also purely runtime values that carry no path. `sloth.instanceId` is a unique alphanumeric string derived from the launcher's process id, and it exists to tell simultaneous sandbox instances apart, which matters when the instance identifier is used as part of a socket path under the runtime directory. `sloth.uid` and `sloth.gid` resolve to the user and group id at runtime.

The point of the whole mechanism is that a nixpak configuration becomes portable. The same declaration can be built once and run by any user on any machine, which is exactly what a configuration committed to a repository needs.

## What the sandbox does not decide for you

nixpak gives you the mechanism, not a policy, and the gap shows in a few places.

The first is the D-Bus policy. The values in the policy map are busctl style verbs, so the choices are see, talk and own, and each service has to be listed by name. The example grants talk to two services and says nothing about the rest, so a user who wants an application to read a third-party service has to know the bus name and add the rule. Nothing in the configuration warns about a service that is used but not permitted, so the failure surfaces as an application that misbehaves rather than as a sandbox error.

The second is the optional dependencies. The D-Bus proxy is enabled by default, and the example comment says it can be turned off to remove the dependency on xdg-dbus-proxy. Likewise network isolation is off by default in the example, and the pasta integration is a separate opt-in with its own example file. The security posture of a nixpak is therefore decided by which of these you switch on, and the defaults are the permissive ones.

The third is scope. A nixpak sandbox is a bwrap sandbox: namespaces, bind mounts and a filtered bus. The README does not claim more than that, and it does not document seccomp filters, capabilities handling or user namespace remapping, so anyone who needs a stronger boundary against a hostile binary should treat nixpak as packaging hygiene rather than as a security boundary. It also does not help with an application you did not package, because there is no configuration for it.

## The examples folder, the NixOS module and the licence

The repository layout supports the claim that nixpak is more than a single example. At the top level there are flake.nix and flake.lock, a launcher directory, a modules directory, a nixos directory, dev and contrib directories, a jobs directory, and an examples folder. The nixos directory in particular suggests the module can be consumed from a NixOS configuration rather than only from a standalone flake.

The examples are the fastest way to read the project. There is a busybox example, an example named for multiple executables, a network isolation demo, and a pasta example that shows the pasta integration in use. A user deciding whether the D-Bus rules or the network options can express what they need can read those files rather than the module interface.

The licence is EUPL-1.2, the European Union Public Licence, which is a copyleft licence with a source availability requirement for modified versions. The README says contributions are welcome and points at a contributing guide and a code of conduct, and it lists community channels including a discussion area and an issue tracker. For an organisation that already ships GPL or EUPL components, the licence is not an obstacle; for one that avoids reciprocal licences, it is a decision to make deliberately rather than a detail to skim.

On release cadence there is nothing to track. The repository publishes no GitHub releases, so there is no version to pin in a flake lock beyond the commit you chose, and the last push was on 2026-10-14. nixpak is best consumed as a library from a pinned commit with your own configuration on top, rather than as a binary you upgrade.

## Conclusion

nixpak suits a user who wants the Nix store contents of an application without the rest of the host, and who is willing to write a module-style declaration covering binds, D-Bus rules and network access for each application. It is the wrong tool when you need isolation from an application you did not package yourself, since the sandbox rules are only as good as the config you write, and it is the wrong tool on a host without bubblewrap. Before adopting it, check that the D-Bus rules you need are expressible as talk, see and own entries, decide whether pasta or the default network handling fits, and read the nixos directory and the examples folder, since the repository publishes no releases and the last push was on 2026-10-14.

## FAQ

### What is nixpak and what does it use under the hood?

nixpak is a declarative wrapper around bubblewrap for sandboxing Nix packaged applications, including graphical ones. It optionally integrates with pasta for network isolation, xdg-dbus-proxy for D-Bus access control and wayland-proxy-virtwl for Wayland protocol access control.

### How do I hardcode a path in a nixpak configuration?

You should use sloth values instead. Sloth values delay evaluation until runtime, so sloth.env "HOME" resolves the home directory at launch, sloth.concat' joins two sloth values, and sloth.mkdir creates a directory with permissions 0700 before it is used.

### What is the Flatpak shim in nixpak for?

The shim makes xdg-desktop-portal treat your Nix application as a Flatpak, which enables the Document Portal. That lets the application open and save files in arbitrary locations without bind-mounting your entire home directory into the sandbox.

### How do I give a sandboxed application D-Bus access in nixpak?

Set the dbus.policies option, whose entries use the same verbs as busctl, namely see, talk and own. The example grants talk to org.freedesktop.DBus and ca.desrt.dconf, and dbus.enable is on by default unless you turn it off.

## Sources

- [Issues](https://github.com/nixpak/nixpak/issues)
- [License: EUPL-1.2](https://github.com/nixpak/nixpak/blob/master/LICENSE)
- [nixpak/nixpak on GitHub](https://github.com/nixpak/nixpak)
- [README](https://github.com/nixpak/nixpak/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nixpak-nixpak
