by codedokode | 2025-11-25 05:57:13+00:00
I am using bwrap - it allows blocking network, creating a new process namespace (so that sandboxed program cannot mess with host processes) and creating a new filesystem, where you can mount host directories. I only pass through minimal set of directories like /usr and /lib, and only selected files from /etc, as they can contain valuable information. I also pass through several device files like /dev/null, but not the whole /dev.

I do not pass through /proc, as it contains not only information about processes (which is not a concern), but a lot of identifying information about a machine. However, lot of modern software due to poor coding and not following standards crashes or exhibits various bugs without /proc, so I wrote a minimal Python /proc emulator using FUSE: https://pastebin.com/s49V5nVL . /proc doesn't allow bind mounting its subdirectories somewhere else, so the only option was FUSE (or writing a kernel module). As you see, with Linux you learn something new every day, like it or not. The emulator is not perfect - it doesn't expose thread information, and, for example, Grand Central Dispatch library [1] terminates the application because of this. Telegram is using the library and crashes randomly because of this. I expected better quality from Apple and Swift.

Most importantly, I run the app as a different user (Android-style), because historically Linux was built around idea of protecting users from each other, and I don't trust namespaces as much. I clear the environment variables as well. Ideally, I should also be closing file descriptors to prevent leaks (for example, you can easily leak a file descriptor to your Wayland server, Dbus or something else).

To use Xorg, I grant access to the app's user. X server has no issues with sharing access to it. I am not sure if one can use Wayland with different users (is uses unix sockets). Pipewire doesn't allow its use by different users, so I have to add a pipewire-pulse plugin listening on localhost, and configure ALSA inside sandbox to connect to that pulseaudio server. Obviously, the app in sandbox has no direct access to audio devices in /dev.

Dbus is also necessary to display tray icons, but you should never grant direct access to it because it is almost like giving the root password. I use xdg-dbus-proxy [2] which I had to patch because originally it only supported running the sandboxed app under the same user. You can still find my patch in open pull requests.

I also did some experimentation with passing through a camera through a fake video4linux device, so that the app cannot know the real camera model, but it is not working great, for example, Gnome Cheese app cannot see it, because it needs not only a kernel device, but something more to detect cameras.

The sandbox cannot use hardware acceleration. It would be not easy to add because every GPU vendor uses its own directories and device files, /sys entries to interact with the hardware and I think you can get the full list only by reverse engineering. Furthermore, I am not sure if it is safe.

This setup is not perfect, and looks like a bunch of random utilities glued with shell scripts (which is not uncommon in Linux world). If I had more time, I would write something better, that can dynamically add and revoke privileges, can provide fake serial numbers and even emulate a working network which sadly loses 100% of sent packets.

I looked into using Flatpak, but they seem to grant full access to /sys, /proc and generally I don't feel that they care much about protection from malware and fingerprinting by legitimate software. Also, as I understand, they run the app as the same user which is not secure. Probably their primary goal is making installation easier but not making the use of software safer. I am not going to use Docker because it officially says that it is not a security tool.

[1] https://github.com/swiftlang/swift-corelibs-libdispatch

[2] https://github.com/flatpak/xdg-dbus-proxy