On Mon, 23 Jun 2025 at 20:59:46 +0900, grape mingijung wrote:
During discussions with several Linux distro security teams, the following
suggestions were raised:
1. Introduce an "untrusted" mode or flag in browser CLI tools for
opening external URLs
2. Extend xdg-open to support passing this "untrusted" flag or context
to the browser
3. Modify desktop environments or applications to invoke xdg-open with
the "untrusted" option when appropriate
Please bear in mind that there is nothing special or magic about
xdg-open. xdg-open is just one of many possible tools that open a
caller-specified URL, and it's considered equally valid to use it or
not use it. If a solution to this is specific to xdg-open, then it can
only ever be a partial solution.
In an application that opens a URL (like the email client that you
described as your example), a simple implementation of opening a URL
would be to delegate it to xdg-open, but many applications will do
something simpler or more direct instead, like calling
g_app_info_launch_default_for_uri() or gtk_uri_launcher_launch() in the
GLib/GTK ecosystem, or the equivalent in Qt or SDL or any similar toolkit
library. These often don't go through the xdg-open shell script:
instead, they launch the URL handler directly.
Sometimes these functions have a place to put arbitrary "options" (for
example g_app_info_launch_default_for_uri() takes an optional
GAppLaunchContext object containing parameters to modify behaviour) but
sometimes they don't (for example SDL_OpenURL() takes exactly one
parameter, the URL to open as a string), and in any case their default
behaviour needs to be something reasonable (for example
g_app_info_launch_default_for_uri() is normally given a NULL
GAppLaunchContext pointer if its caller doesn't have any specific
requirements).
Even if the xdg-open shell script is used, it often delegates the actual
launching to a desktop-environment-specific program. For example, in the
GLib/GTK ecosystem (GNOME, MATE or similar), xdg-open just wraps `gio
open`, which is a simple command-line wrapper for the
g_app_info_launch_default_for_uri() function that does the actual work.
xdg-open and `gio open` are primarily designed to be something that a
user can run interactively at a command-line, and are only secondarily
something that an application would launch non-interactively to handle a
URL; the preferred way for an application to handle a URL is whatever is
the equivalent of g_app_info_launch_default_for_uri() in the libraries
that it uses, which will often involve fewer layers of indirection.
The URL handler registration protocol on Linux/Unix/freedesktop.org
platforms looks like this:
- the URL handler's .desktop file (e.g. firefox.desktop) lists
x-scheme-handler/http (or https or ftp or any other scheme) as a
pseudo-MIME-type in its MimeType field
- there is a mechanism for choosing one of several suitable URL handlers
to be the default, which is out-of-scope here; assume that we have
been able to choose one specific URL handler
- the URL handler's Exec field includes %u (placeholder for 0 or 1 URLs)
or %U (0 or more URLs) as described in
https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html,
and optionally the URL handler also implements the
org.freedesktop.Application D-Bus interface described in
https://specifications.freedesktop.org/desktop-entry-spec/latest/dbus.html
- the caller (for example g_app_info_launch_default_for_uri() called by
the email client) execs the program specified by the Exec field,
replacing %u or %U with the desired URL, or calls the Open() D-Bus
method with a list containing the desired URL as first argument
The major desktop environments will often prefer to use the D-Bus
interface where possible, because that results in the URL handler being
exec'd in a predictable environment by a piece of centralized
infrastructure like dbus-daemon or systemd, rather than inheriting
various aspects of the execution environment such as environment
variables and resource limits from whatever program happens to have
launched the URL handler.
There is currently no defined place in that specification to put a
boolean flag for whether the URL is or isn't "trusted" in some way. To
pass an extra command-line argument to Exec, the .desktop file
specification would need to define a way for the caller to discover
whether the program supports it (perhaps a new % variable, or a new
field alongside Exec). To pass extra information to the Open() D-Bus
method (for URL handlers that support it), the .desktop file
specification would need to define a new key in the `platform_data`
dict alongside the desktop-startup-id and/or activation-token.
How does this work on other platforms like Windows and macOS? On
Windows, the implementation details are different, but the general
"shape" of the API seems like it's the same: the URL handler registers
itself with the system by saying "I can handle http URLs" and storing a
command-line with some placeholders (on Windows I think this is done via
the registry), the caller (e.g. email client) passes the URL to an API
function like ShellExecute() or a command-line tool like `start`, and OS
libraries are responsible for figuring out which URL handler is the
correct one and launching it with suitable options. On Windows, does the
URL handler (e.g. browser) treat the URLs it receives from the OS as
though they had been typed into the address bar, or as though a link had
been followed?
I'm inclined to agree with Solar Designer that if there is a "safe" and
an "unsafe" way to open URLs, then browsers should make the "safe" way be
the default, with the more-powerful but less-safe way being an opt-in.
That would make the overall system "fail safe", especially if it is not
straightforward to add more information to the whole route from caller
to URL handler (for example SDL_OpenURL() has nowhere that it could put
this extra information).
smcv