Bringing XDG Desktop Portal support to Emacs

Sandboxes, like those of bwrap and systemd, are an effective way to secure your system from applications you don't or can't trust.

I may trust Emacs, but I don't trust myself to audit every single line of code (built-in or third-party) that the Emacs Lisp interpreter evaluates. Additionally, I may trust in the security of Chromium's provided sandbox, but I don't trust that it's infallible; no software is.

I do however put great effort to keep things secure which is why the most vulnerable parts of my system, the applications which are first and foremost a presentation layer over other people's code, are sandboxed either via bwrap if an executable or systemd if a daemon.

Sandboxing Emacs and reducing its attack vector to an acceptable level has been a very tedious and error-prone process for me, here's an excerpt from my emacs.service file constituting the unit's security policy:

[Service]
# ...
SystemCallArchitectures=native
LockPersonality=yes
ProtectSystem=strict
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
PrivateUsers=yes
PrivateDevices=yes
PrivateTmp=yes
RestrictNamespaces=yes
RestrictSUIDSGID=yes
NoNewPrivileges=yes
CapabilityBoundingSet=

Pairing this with a bwrap-sandboxed qutebrowser breaks the most ubiquitious functionality across Emacs: following links. When Emacs attempts to follow a link, it's not directly executing the qutebrowser binary with the URL as an argument, instead it calls a script that is silently shadowing it.

Shadowing a program is achieved by creating a script with an identical name as the original program, but storing it in a path preceeding that of the original program.

Emacs can't spawn a qutebrowser process because the unit's security policy prohibits it from spawning a nested sandbox. So, what are we supposed to do then?

It's quite easy actually, the Freedesktop/Flatpak team have already encountered this problem and worked out a thorough and well-architectured solution known as xdg-desktop-portal.

What we require is the OpenURI method from the org.freedesktop.portal.OpenURI interface which forwards a given URI to its associated application. Sounds good, Emacs has excellent D-Bus support, we first need a function that forwards the URI 1 to the portal:

(defun browse-url-xdg-desktop-portal (url &rest args)
  "Open URL via a portal backend."
  (dbus-call-method :session
                    "org.freedesktop.portal.Desktop"
                    "/org/freedesktop/portal/desktop"
                    "org.freedesktop.portal.OpenURI"
                    "OpenURI"
                    "" url '(:array :signature "{sv}")))

And to then set the default browser function to it:

(setopt browse-url-browser-function #'browse-url-xdg-desktop-portal)

Opening links should now work seamlessly across Emacs.

Footnotes

1

Despite there being a technical difference, I've used URI and URL interchangeably, but only to remain consistent with the naming conventions of the APIs in question.