flatpak / xdg-desktop-portal

Desktop integration portal
https://flatpak.github.io/xdg-desktop-portal/
GNU Lesser General Public License v2.1
563 stars 188 forks source link

NativeMessaging portal for sandboxed browsers #655

Open oSoMoN opened 2 years ago

oSoMoN commented 2 years ago

This is a proposal for a new portal to enable the native messaging feature of sandboxed browser apps, which was originally discussed on the snapcraft forum.

Native messaging is a mechanism implemented in several major browsers (Chromium, Firefox, Brave, Edge, others?) that allows web extensions to communicate with a native application installed on the system.

This mechanism is used by different types of applications to provide better integration between the user’s computer and their browser / web services, for instance password management applications, searching and managing GNOME shell extensions, KDE Plasma integration, …

This currently doesn’t work in browsers (such as Firefox) packaged as flatpaks or snaps, because confinement prevents the browser from executing random executables on the user’s host (for obvious security reasons).

The native messaging host is described by a JSON metadata blob that looks something like:

{
  "name": "org.gnome.chrome_gnome_shell",
  "description": "Native connector for extensions.gnome.org",
  "path": "/usr/bin/chrome-gnome-shell",
  "type": "stdio",
  "allowed_extensions": [
    "chrome-gnome-shell@gnome.org"
  ]
}

In particular, you have:

The proposal is to expose a new org.freedesktop.portal.NativeMessaging interface, with a Start(…) and a Kill(…) method (names up for debate):

<method name="Start">
  <arg type="s" name="name" direction="in"/>
  <arg type="s" name="extensionOrOrigin" direction="in"/>
  <arg type="h" name="stdin" direction="out"/>
  <arg type="h" name="stdout" direction="out"/>
</method>

When called, the service would look for a matching JSON metadata file and verify that it allows communication with the named extension. If so, it spawns an instance of the app and returns stdin and stdout file descriptors that are pipes to the equivalent descriptors of the app. The Kill(…) method would let the browser terminate the app.

This would prompt changes in browsers to use this new portal. Here in Firefox, and likely here in Chromium.

swick commented 2 years ago

How would the issues raised here be dealt with? https://bugzilla.mozilla.org/show_bug.cgi?id=1621763#c2

smcv commented 2 years ago

This proposal seems like it's intended to solve the "Interaction" point in that comment, but not the "Discovery" point: if the browser wants to enumerate all the native messaging modules on the host, it cannot.

Do browsers need to be able to do that? (I could see this going either way, depending whether communication with a native messaging module is initiated by the extension/webapp or by the browser.)

smcv commented 2 years ago

the list of allowed_extensions (for Firefox) or allowed_origins (for Chrome) that the server is willing to talk to

Are Firefox native messaging modules and Chrome native messaging modules interchangeable? Would there ever be a situation in which a user wants to expose only a subset of native messaging modules to one of the browsers?

Are extensions and origins interchangeable?

What's the security model for allowed extensions and origins? It seems to me that in this proposal, we're implicitly trusting the browser to tell us the correct extension/origin from which it got the request (components outside the browser have no way to know whether we can believe it) - which means that filtering could equally well be done by the browser itself, as it is in non-Flatpak, non-Snap browsers, as long as the browsers have a way to read the extension's list of allowed extensions and origins themselves.

smcv commented 2 years ago

Are Firefox native messaging modules and Chrome native messaging modules interchangeable?

From the documentation you linked, it seems the answer is no: each browser has its own browser-specific search path for manifests, and it's not obvious how we would correlate those with Flatpak and Snap app-IDs (except by hard-coding).

swick commented 2 years ago

This proposal seems like it's intended to solve the "Interaction" point in that comment, but not the "Discovery" point: if the browser wants to enumerate all the native messaging modules on the host, it cannot.

The discovery problem is a problem of the x-d-p. If an application provides a NativeMessaging app manifest on the host the portal will discover it (if it knows about all the well-known directories for each browser… urgh). If The application is in a sandbox itself and the app manifest has a path in that sandbox the portal won't be able to call it.

I don't think the browser needs to know about all existing app manifest but only react when an extension requests access to one.

How the "Interaction" problem is solved is not really clear to me. If the NativeMessaging app is in flatpak the path in the manifest is still a host path.

What's the security model for allowed extensions and origins? It seems to me that in this proposal, we're implicitly trusting the browser to tell us the correct extension/origin from which it got the request (components outside the browser have no way to know whether we can believe it) - which means that filtering could equally well be done by the browser itself, as it is in non-Flatpak, non-Snap browsers, as long as the browsers have a way to read the extension's list of allowed extensions and origins themselves.

Since the manifests are somewhere on the host and the browsers are in a sandbox they don't see the manifest. We could just as well return the allowed extensions and origins instead of checking them for the browser.

Deciding if an app should be able to access a specific or all NativeMessaging endpoints on the other hand is a really hard problem. Should we hardcore firefox and chrome? Let the user decide? What data can the user make a decision on? E.g. if a keyring application can be talked to by any application through NativeMessaging that would be really bad.

oSoMoN commented 2 years ago

If an application provides a NativeMessaging app manifest on the host the portal will discover it (if it knows about all the well-known directories for each browser… urgh).

Being a dedicated portal for native messaging, it doesn't sound too bad that it has to know of a list of well-known directories where to search for manifests.

I haven't given much thought to endpoints packaged as flatpaks/snaps themselves, but that's an interesting prospect indeed. It sounds like the portal would need some additional logic to know where to look for them, and to invoke the right executable.

I don't think the browser needs to know about all existing app manifest but only react when an extension requests access to one.

Agreed, the browser doesn't need to discover all manifests, only to delegate to the portal when an extension requires talking to a given origin.

What's the security model for allowed extensions and origins? It seems to me that in this proposal, we're implicitly trusting the browser to tell us the correct extension/origin from which it got the request.

Yes, that's the point: the browser requests talking to a given extension/origin, and the portal handles locating the corresponding endpoint and initiating communication with it.

E.g. if a keyring application can be talked to by any application through NativeMessaging that would be really bad.

Supposedly, the portal would mediate that by informing the user which application (browser or not) is trying to launch which endpoint, and requesting permission (which might then be cached to avoid subsequent blocking dialogs). I am not familiar with the internals of x-d-p (yet), hopefully this makes sense?

smcv commented 2 years ago

Supposedly, the portal would mediate that by informing the user which application (browser or not) is trying to launch which endpoint, and requesting permission (which might then be cached to avoid subsequent blocking dialogs).

How would this look to a user?

We should try to avoid situations where we pop up a dialog that, to a non-technical user, might as well say

The application "Firefox" wants to do something.
This is probably fine, except maybe it isn't.
[ Allow ] [ Deny ]
smcv commented 2 years ago

This seems like something where it might be a lesser evil to have static permissions (in the Flatpak metadata or Snap's equivalent, similar to how access to e.g. X11 is handled), so that Firefox can contact native messaging providers that were found in Firefox directories, Chromium (and Chrome and ungoogled-Chromium) can contact native messaging providers that were found in Chromium directories, and random non-browser apps can't contact any native messaging providers.

oSoMoN commented 2 years ago

Yes, that sounds reasonable.

swick commented 2 years ago

That would work but it's far from ideal. Taking a step back, what we really want is for applications to communicate with each other freely through dbus. The application providing a bus should then have a policy, possibly even ask the user what to do, to decide what another app is allowed to do. That only works if apps can authenticate each other and requires something like o.fd.DBus.GetConnectionCredentials. With such a system in place there could be a new NativeMessaging scheme which has a strong security model which works with sandboxed and host apps on either side.

As a stop-gap solution the static permissions might be reasonable though.

jhenstridge commented 2 years ago

This seems like something where it might be a lesser evil to have static permissions

We could certainly work with a static permission, similar to how the network connectivity portal decides whether to answer requests from the client. On the snapd side, we've already got a "browser-support" permission that might be an appropriate proxy for this permission (although it is also used by some Electron apps), which we could expose to x-d-p.

As far as separating out Chrome vs. Firefox native messaging hosts, I'd originally hoped we could mostly rely on the different format for extension IDs to pick the right executables. As a quick example, here are the two json files for the native messaging host for gnome-shell's browser extension:

For Firefox:

{
  "name": "org.gnome.chrome_gnome_shell",
  "description": "Native connector for extensions.gnome.org",
  "path": "/usr/bin/chrome-gnome-shell",
  "type": "stdio",
  "allowed_extensions": [
    "chrome-gnome-shell@gnome.org"
  ]
}

and for Chrome:

{
  "name": "org.gnome.chrome_gnome_shell",
  "description": "Native connector for extensions.gnome.org",
  "path": "/usr/bin/chrome-gnome-shell",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://gphhapmejobijbbhgpjhcjognlahblep/",
    "chrome-extension://olkooankbfblcebocnkjganpdmflbnbk/"
  ]
}

We could do separate portal APIs for Firefox style hosts (match on allowed_extensions) and Chrome style hosts (match on allowed_origins). But due to the different ID formats, we could essentially collapse them: searching both browsers' configuration directories and check the extension ID against both allowed_origins and allowed_extensions should reliably pick the right json file.

jhenstridge commented 2 years ago

Based on the discussion here, there are two main deficiencies in the initial Start() API I proposed that is included at the top of this issue:

  1. It doesn't provide a way to stop the native messaging server (@oSoMoN suggests a Kill method for this).
  2. If we wanted to have the option of presenting an access control dialog, we'd have an active D-Bus method call waiting for the interaction to complete. We've tried to avoid that in most other cases.

For (1), the existing Session interface seems like a good fit. That would give the client a way to request the server be killed (through the Close) method, and notification if it dies for some other reason (the Closed signal). This also lets us reuse code to provide appropriate access control to the session object (i.e. ensure one confined app doesn't kill the session of another app).

For (2), while we might not show one if we go for the static permission route, it might still be worth while designing the API to allow it. So we'd probably want an API that returns a Request. We could include the stdin/stdout file descriptors in the request's Response signal broadcast, but we haven't done that anywhere else. We could alternatively provide a separate method call to get the file descriptors.

This would give an API something like:

<!-- Create a session object representing an instance of a native messaging host. -->
<method name="CreateSession">
  <arg type="a{sv}" name="options" direction="in"/>
  <arg type="o" name="handle" direction="out"/>
</method>

<!-- Start the named native messaging host. Returns a Request object that will signal success if the native messaging server could be started -->
<method name="Start">
  <arg type="o" name="session_handle" direction="in"/>
  <arg type="s" name="name" direction="in"/>
  <arg type="s" name="extensionOrOrigin" direction="in"/>
  <arg type="a{sv}" name="options" direction="in"/>
  <arg type="o" name="handle" direction="out"/>
</method>

<!-- Get the stdin/stdout file descriptors for the session. Will fail if session has not been successfully started -->
<method name="GetPipes">
  <arg type="o" name="session_handle" direction="in"/>
  <arg type="h" name="stdin" direction="out"/>
  <arg type="h" name="stdout" direction="out"/>
</method>

As with other session based APIs, sessions will automatically get killed if the D-Bus peer that created them disconnects from the bus.

ramcq commented 2 years ago

Thanks for working on this @jhenstridge - I was looking at it earlier this month to see if we could get the Chrome GNOME extension working on Endless OS and someone pointed me towards this ticket.

Is the intention that the static permission is basically all or nothing? Ie with the permission, a browsers are able to access any native message host, and the browser is trusted to whatever internal sandboxing/ACLs to restrict that to the right sites, and the system vendor/admin/user outside the sandbox is in control of which native extensions they want to take that chance with?

(The reason I ask is that I was about to point out that a static permission that enabled access per native messaging host would be relatively useless, as it would require the packaging for each browser to know ahead of time which extension + host combinations the browser user might want to use. Or, a mechanism for the OS to tell Flatpak to give access all of these available hosts to all of the browsers, but that becomes essentially equivalent to "let any browser access any messaging host".)

I think a request to the user is not as bad as first thought, because the OS can take care of illustrating to the user what is being accessed, ideally making use of metadata about the messaging hosts and which package/whatever they come from. The information we gain here, which is not otherwise accessible, is confirmation from the user that the access is happening when they are trying to use it, rather than maliciously/unexpectedly in the background when the user thinks something else is going on. So you could have a dialog like:

A web browser or other application is requesting additional access to your computer:
 * Chromium
Is trying to launch:
 * Native connector for extensions.gnome.org

If you are not trying to access this functionality at this time,
please press Stop and seek assistance from $DEITY.

[ Stop ] [ Allow Always ] [ Allow Once ]

A further complication which I think is worth considering, even if we decide to implement/solve it later, is that we have >0 examples of apps themselves which would ideally be able to offer native messaging hosts. KeePassX has a Flatpak and its native messaging stuff doesn't really work unless you do some weird gymnastics at the moment. https://github.com/flathub/org.keepassxc.KeePassXC/issues/29

Perhaps this becomes, at least for Flatpaks, an export mechanism similar to search providers where the export is present but requires additional user consent/action to have the export activated. This might argue in favour of the user consent mechanism because the portal could see the message host exported from the Flatpak, and check whether the user wants them to be patched together in this way.

jhenstridge commented 2 years ago

The suggestion about static permissions was from https://github.com/flatpak/xdg-desktop-portal/issues/655#issuecomment-966517356. If we can come up with an understandable wording for the access control dialog, then we could certainly include that. With the API proposal above, I wanted to make sure we didn't back ourselves into the corner of never being able to show such a dialog.

As far as sandboxed native messaging servers go, I see that as a separate problem that may be best solved in the individual confinement systems. If flatpak or snapd learned how to write out a native messaging JSON metadata file in a way that a traditionally packaged browser can find, then this proposed portal would also make that server available to confined web browsers too.

The important part here would be to ensure that the command in the JSON file causes the server to be invoked with the appropriate sandboxing. I don't think KeepassXC's current approach of writing out the JSON metadata file at runtime is something we'd really want to support, since it would effectively be a sandbox escape vulnerability. But if the package maintainer could ask flatpak/snapd to create the metadata file on its behalf, then it wouldn't matter that the one KeepassXC writes out is ignored.

ramcq commented 2 years ago

Sounds good - in Flatpak land exported files such as .desktop, .service, etc files are rewritten so that the Exec= line includes the appropriate flatpak run ... invocation so that the entry points from the outside world are appropriately sandboxed.

jhenstridge commented 2 years ago

That's essentially how snaps are exposed to the system too. One wrinkle though is that the specs for Firefox/Chromium only let you specify the executable for the native messaging server: not its arguments. So you'd probably need to also write out a shell script that does exec flatpak run ....

swick commented 2 years ago

IMO the suggested prompt is exactly what we try to avoid: there isn't any way for a normal user to judge if it should be allowed or not. If we go for static permission I don't see the point of this.

It would also be good if we didn't have to modify flatpak. Finding all NativeMessaging connectors could probably be done by searching a specific directory on the host and a specific directory for every flatpak (e.g. ~/.var/app/$APPID/config/.NativeMessaging) and snap which has the additional requirement that the name in the NativeMessaging metadata is a prefix of the $APPID.

NativeMessaging hosts from a flatpak would then be started with flatpak run --command=$COMMAND $APPID.

This already is a hack and in the long term I would love to replace this with just dbus but until then it would be neat if we can contain it in the portal as much as possible.

teohhanhui commented 2 years ago

there isn't any way for a normal user to judge if it should be allowed or not

If it's possible to be specific about what's being accessed, then the user can decide, e.g.:

Firefox is trying to access KeePassXC

swick commented 2 years ago

If the connector is a flatpak we could get reliable names but what does accessing KeePassXC mean? How can I judge that Firefox is not doing anything horrible with KeePass? What information do I gain by that message when I already installed KeePass and the Firefox extension?

teohhanhui commented 2 years ago

What information do I gain by that message when I already installed KeePass and the Firefox extension?

The user is even prompted on choosing / allowing an app / system handler to open their supported file types / links. This kind of prompts is not unprecedented (even if some of the prompts are from Firefox itself).

As a user you wouldn't expect any app to be able to access any other app, especially with sandboxing on mobile OS'es nowadays. Of course, reality vs expectation, but my point is the user can understand these prompts if they're worded correctly.

vasyugan commented 2 years ago

Hi, just pinging this. I just noticed that the respective Firefox bug report was changed from "unconfirmed" to open today, after two years. I'd love to use the flatpak instead of manually downloading the binary package from Getmozilla.org and manually integrating it into the system. Flatpak has the distinct advantage of being both well integrated into the system and up-to-date at the same time. But of course, since native messaging doesn't work in flatpak, some of the extensions I absolutely need, such as keepassxc-browser are broken, and that's why I am forced to using the tar.bz2 instead of the flatpak anyway. Since I am not a programmer, I have nothing to contribute here, just nagging. But it seems, that indeed, this should be addressed on the flatpak side. I guess there is little that packagers of Firefox etc can do to work around this.

jhenstridge commented 1 year ago

From discussion at the Ubuntu Summit:

  1. Remove the static permission check stub
  2. Rename webextensions.[ch] to web-extensions.[ch]
  3. Make hard coded manifest JSON locations a little configurable. E.g. chrome-gnome-shell on Fedora 36 installs to /usr/lib64/mozilla/native-messaging, which wouldn't be found. Need to check whether this actually worked with Firefox.
  4. Add @libdir@ and @sysconfdir@ versions to manifest search path.

Post merge:

  1. Implement client bindings in libportal
  2. Port WebExtensions test to use libportal client API instead of the bindings in the test suite.

These changes were not included in the PR because we had not previously locked down the D-Bus API.

raniesantos commented 1 year ago

Hi, sorry to bother, but I just want to ask if this issue is related to why I can't get this extension to work with Flatpaks.

freundTech commented 1 year ago

Hi, sorry to bother, but I just want to ask if this issue is related to why I can't get this extension to work with Flatpaks.

Yes. That extension uses NativeMessaging, which currently doesn't work with flatpak.

Mannshoch commented 1 year ago

I use Ubuntu with SNAP. My SNAP Firefox work with the addon for Gnome Extension, and KDE Connect since a while.

The strange part is, for KeePassXC I had to install flatpak and used Flatpak to activate Native Messaging to my SNAP Firefox.

sudo apt-get install flatpak
flatpak permission-set webextensions org.keepassxc.keepassxc_browser snap.firefox yes
tazihad commented 1 year ago

any news when native messaging may work in flatpak browsers?

iihmsunn commented 1 year ago

@tazihad this has some interesting stuff

cheako commented 1 year ago

Here is a partial solution: https://github.com/mi-g/vdhcoapp/issues/91#issuecomment-1247940392

bayazidbh commented 1 year ago

@cheako the workaround have been known for a while -- I've commented something similar in https://github.com/flathub/org.freedownloadmanager.Manager/issues/1#issuecomment-1186395787 though not using a flatpak-spawn --host (either of which is not ideal from both security and user-friendliness perspective).

This issue is for a proper solution that doesn't rely on manually poking holes in the sandbox and is handled dynamically, automatically, and securely through a portal. You can track the progress in PR #705.

omega3 commented 4 months ago

Any progress in this issue?

I've just started using Firefox flatpak on Wayland and discovered that it is impossible to use keepassxc-browser (with xorg disabled in Flatseal).

No. It's up to Flatpak to solve problems with Native Messaging in their browser packages. For now, it seems there's no easy solutions for it.

https://github.com/keepassxreboot/keepassxc-browser/issues/1875#issuecomment-1677013577

I also found this workaround but as I am basic-skills Linux user I have no idea how to compile keepassxc-proxy-rust.

[HOW TO] Run Firefox and KeePassXC in a flatpak and get the KeePassXC-Browser add-on to work - General - Flathub Discourse

Anyway, looks like there is no (easy) way to use any offline password manager with flatpak at present. This kinf of limits the joy of using flatpaks.

iihmsunn commented 4 months ago

@omega3 one "easy" "way" I use is to give up on browser integration and use a hotkey to open keepassxc. You still have to manually find and copy the password but realistically it only takes a few seconds at most if you don't use mouse except to focus the password field in the end. Works best with "hide keepassxc to system tray on close" setting. Also it works with other apps too

p1gp1g commented 4 months ago

@omega3 Here is a guide how to set it up: https://github.com/keepassxreboot/keepassxc-browser/issues/1631#issuecomment-1153736766