App sandbox
An app says what it needs from a sandbox in the [sandbox] table of Sabine.toml, using the
names Flatpak uses. A store or the system can show the declaration before the app is installed, and
enforce it when the app starts. On Linux, Sabine enforces it itself when nothing else does.
[sandbox]network = truefilesystems = ["xdg-documents", "xdg-download:ro"]devices = ["usb"]talk = ["org.mpris.MediaPlayer2.notes"]Declaring what the app needs
Section titled “Declaring what the app needs”| Key | Type | Default | Meaning |
|---|---|---|---|
network |
boolean | true |
Whether the app goes online |
filesystems |
array of strings | none | Folders the app reaches: home, xdg-desktop, xdg-documents, xdg-download, xdg-music, xdg-pictures or xdg-videos. Add :ro for read-only access, such as xdg-download:ro |
devices |
array of strings | none | usb, serial or all |
talk |
array of strings | none | Session bus names the app calls, such as org.mpris.MediaPlayer2.notes. A trailing .* covers every name below it |
unsandboxed |
boolean | false |
Declares that the app can’t run in a sandbox |
reason |
string | One line, shown to whoever decides, explaining why the app can’t run in a sandbox. Only with unsandboxed = true |
Files a person picks in a file dialog reach the app without any filesystems entry, and so do the
app’s own folders. A place or device may appear only once, and xdg-music and xdg-music:ro
together count as twice.
talk refuses names that would let an app leave its sandbox: the portal backends and permission
store (org.freedesktop.impl.portal.*), org.freedesktop.portal.Flatpak, org.freedesktop.Flatpak,
org.freedesktop.systemd1 and org.freedesktop.DBus, and wildcards that cover any of them, such as
org.freedesktop.*. Names must be valid D-Bus names with at least two elements.
Apps that can’t be sandboxed
Section titled “Apps that can’t be sandboxed”An app that needs more than a sandbox allows, such as one that manages containers or the system itself, says so instead, with a reason:
[sandbox]unsandboxed = truereason = "It manages the containers Podman runs for you."An unsandboxed declaration takes only unsandboxed and reason; any other key is an error, and so
is a reason without unsandboxed = true.
Where the declaration goes
Section titled “Where the declaration goes”sabine dev and sabine bundle check the table and stop on a mistake, and unknown keys are an
error. Without a [sandbox] table the app declares nothing.
Every bundle carries the declaration, normalized, in the production manifest that a store or the system reads without starting the app:
| Bundle | Manifest |
|---|---|
| AppImage, deb, rpm, Linux tarballs | usr/lib/sabine/<app id>/resources/Sabine.toml |
| macOS | Contents/Resources/Sabine.toml |
| Windows | resources/Sabine.toml next to the program |
On Linux, a system that runs the app in a sandbox of its own enforces the declaration; Luft’s Schelf does this for AppImages. Everywhere else, Sabine enforces it as the app launches, with Landlock and a system call filter that every process the app starts inherits, Chromium included. It needs neither root nor bubblewrap.
A confined app:
- reads the system’s programs, libraries and settings, and the person’s fonts, icons, themes and desktop settings
- writes the folders named after its app id in the XDG data, config, cache and state folders, its Chromium profile, and the temporary and runtime folders
- reads its own program, web assets, Sabine’s host and the CEF runtime, and writes Sabine’s logs
- reaches the declared
filesystemsanddevices, and nothing else in your home folder - can’t reach abstract sockets or signal processes outside the app
- with
network = false, can’t open or accept TCP connections, apart from its dev server and DevTools port in development
The system call filter also refuses the kernel keyrings and log, ptrace and other processes’
memory, and typing into or taking over the terminal the app was started from.
A browser or other program the app starts is confined with it, so a confined app opens links and
files through the desktop portal (shell.openLink and shell.showFolder do this already). Each
kernel applies what its Landlock version supports; a kernel without Landlock gets only the system
call filter, and the app records that once in its diagnostics.
The session bus isn’t filtered yet, so talk is declared but not enforced by Sabine. Apps with
unsandboxed = true, apps without a [sandbox] table, and apps started with
SABINE_FULL_ACCESS set run unconfined. A system sets SABINE_FULL_ACCESS for an app the person
gave full access.
Inside a Flatpak-style sandbox
Section titled “Inside a Flatpak-style sandbox”Inside a sandbox that marks its processes with /.flatpak-info, as Flatpak does, the app runs its
own Chromium in the sandbox and never uses the shared runtime. Chromium can’t create the namespaces
of its renderer sandbox there, so when the sandbox provides Zypak in ZYPAK_BIN, Sabine starts
Chromium through zypak-helper, which starts Chromium’s helpers through the Flatpak spawn portal,
each in a smaller sandbox of its own. Renderers keep their namespaces and system call filter. The
runtime and host have to be visible inside the helpers’ sandbox, so a sandboxed app uses a runtime
installed for every user or bundled with it.
Windows and macOS
Section titled “Windows and macOS”Sabine enforces [sandbox] as described above.
Bundles carry the declaration, but Sabine doesn’t enforce it yet; the app runs with your account’s access. Chromium’s renderers run in Chromium’s own sandbox.
Bundles carry the declaration, but Sabine doesn’t enforce it yet and writes no App Sandbox entitlements; the app runs with your account’s access. Chromium’s renderers run in Chromium’s own sandbox.
The browser sandbox
Section titled “The browser sandbox”[sandbox] confines your app’s own process and everything it starts. Chromium’s sandbox is a
separate layer that confines web content inside Chromium, and Sabine never turns it off. Pages of
apps in the shared runtime run in that runtime’s Chromium, which only Chromium’s own sandbox
confines.
Chromium uses its namespace and seccomp sandbox. Sabine doesn’t turn it off when the system’s policy keeps it from creating namespaces; see AppArmor.
Sabine’s installer gives Chromium’s restricted processes read and execute access to the host and CEF runtime files, so sandboxed renderers can load them.
Every Chromium helper starts the sandbox from the runtime’s libcef_sandbox.dylib before it
loads the Chromium framework, and stops if that fails.
AppArmor
Section titled “AppArmor”Ubuntu and other distributions that restrict unprivileged user namespaces with AppArmor stop
Chromium from creating its sandbox for hosts installed in your home folder. sabine runtime doctor
reports the host failing its launch check. Generate a profile that allows it as the user who runs
Sabine, then have an administrator install it:
-
Generate the profile. This prepares the runtime and host first if they’re missing.
Terminal window sabine runtime sandbox-profile > sabine-apparmor -
Review the paths in it, then install and load it:
Terminal window sudo install -m 644 sabine-apparmor /etc/apparmor.d/sabine-$USERsudo apparmor_parser -r /etc/apparmor.d/sabine-$USER -
Check that Chromium starts, and clean up:
Terminal window sabine runtime doctorrm sabine-apparmor
The profile allows user namespaces for every sabine-host under your Sabine data folder, so it
keeps working as the runtime updates, plus a separate entry for the host in use when it lives
elsewhere. It changes no global AppArmor policy. Anyone who can replace an executable at those paths
can also use this permission, which is why an administrator should review them.
If the kernel disables user namespaces entirely, an administrator has to enable them or provide
Chromium’s root-owned setuid helper through CHROME_DEVEL_SANDBOX. Running apps as root isn’t
supported.