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:
# ~/.config/systemd/user/emacs.service
[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 URI 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:
(require 'dbus)
(defun browse-url-xdg-desktop-portal (url &rest args)
"Open URI 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.