[Bug] [Firefox] The Kee Browser Extension stalls Websocket connections to localhost

Hey there, I’m pretty sure I found a bug.

Today when working I was having trouble to get my browser to connect to a WebSocket server on my local machine.

After a long time of trial and error I found out that the issue vanishes as soon as the Kee Extension is disabled and appears again as soon as it’s back online.

It is fairly easy to reproduce:

  1. Open a WS server locally. I used node and the ws package:
const WebSocket = require('ws');
const ws = new WebSocket.Server({ port: 80 });
ws.on('connection', socket =>
{
  console.log('New connection!');
  socket.close();
});
  1. Open Firefox with Kee installed and enabled (not connected to Keepass, not sure if it makes a difference but I guess that it does)
  2. Go to about:blank and open the DevTools (Ctrl + Shift + I)
  3. Use the console to run
const ws = new WebSocket('ws://127.0.0.1/'); 
ws.onopen = () => console.log('Connected!');
  1. Refresh the page
  2. Goto step 4

When I do this on my machine and Kee is installed, the time between calling new Websocket and actually starting a connection gets increasingly slower until it is the slowest at roughly 70 seconds. This might match the reconnect timeout used in WebsocketSession.ts of the addon. I can’t tell if they are related though, I haven’t looked thorugh the code very long.

I have to do a full restart of the browser to fix the issue. It seems to only affect connections via localhost or 127.0.0.1. I have not tested this out in Chromium.

Have a nice day!

Firefox continuously increases the time required for a websocket connection to succeed each time that it fails.

Perhaps it additionally treats all localhost ports as part of the same timeout pool?

Kee is very conservative because of this and therefore only attempts to connect once every 70 seconds, unless an exploratory HTTP request fails in a way that suggests KeePassRPC is waiting for an incoming connection.

In step 5, which page are you refreshing? I see no way to do this on “about:blank”

Thanks for the reply!

You can hit F5 while the devtools are open to refresh the page. You don’t have to try it on the about:blank page either, I just figured it would be easiest to try there but looks like I was mistaken ^^

When I disable the Kee extension I can do it 100 times in quick succession without noticing any difference, when Kee is active, it takes about 7-10 tries to start stalling and about 15-20 tries to become basically unusable.

Were you able to reproduce it?

Yeah, more or less. For me the successful connections occur at the point at which Firefox gives up trying to connect to the KeePassRPC websocket port - that’s every 60 seconds at a time related to when Kee first started up. So there’s no increasing time delay but instead always a delay of somewhere up to 60 seconds.

I know that Linux and Windows behave differently with respect to websocket connections though so perhaps that explains our slightly different results (I’m on Linux).

So, at least on Linux, there’s nothing that Kee (or any Websocket client) can do about this. Whether via a Firefox bug or WebSocket protocol requirement, it seems that attempting two connections to different ports on the same IP address concurrently is just not possible. Again, this may even be platform dependant behaviour.

It would be interesting to see if the behaviour in Chrome is the same (suggesting a protocol requirement) and whether using an alternative IP address for localhost (or even a different host or domain name aliased to it) allows you a way to work around the limitation. And/or you can always keep KeePass open (but locked) so that Kee has no need to try to connect to it using the only available connection to localhost.

I also ran into this bug. I found it is resolved by

  • disabling Kee
  • opening my KeePass Database and waiting for Kee to pick up on it
  • in about:config, set network.websocket.delay-failed-reconnects to false

My Environment:

  • Ubuntu 20.10 64-bit
  • Firefox 88.0.1
  • Kee 3.9.5
As for finding a solution, I tried to get the same issue with [Jupyter Lab](https://jupyter.org/) but couldn't. It is a notebook programming environment, popular in the data science space. It is served from localhost and connects via WebSocket to the server that you start in a terminal. I would reload my HTML test page (see below) until it needs 10s of seconds to connect, but reloading Jupyter Lab will still establish a connection in normal time. So clearly there is a way to make this work.

Edit: Unfortunately, Jupyter Lab doesn’t have a way around it. They seem to serve a lot of features via HTTP so I didn’t notice it. But when looking at the Network tab in dev tools, you can see they can’t connect their WebSocket any earlier than my test page. In fact, both connect at pretty much the same time (once the rate limiter allows it).

As an idea though, what about using HTTP to check whether the KeePass server is available? The server will accept TCP connections before resetting the HTTP connection, so it is possible to tell whether it is up. For example, when I tested it quickly with a Node client, I got an ERRCONRESET when KeePass was running and an ERRCONNREFUSED when it wasn’t.

Not sure what kind of limit the browser puts on HTTP requests though…

My Test Setup

  • websocat. Run with websocat -s 8080
  • test file:
<!doctype html>
<head>
  <meta charset="utf-8">
</head>
<body>
    <div id="status_field">connecting</div>
    <script>
        const ws = new WebSocket("ws://localhost:8080")
        ws.onopen = () => {
          document.getElementById("status_field").innerHTML = "connected"
        }
    </script>
</body>
</html>

Thanks for the additional information. I had forgotten all about that about:config override setting because it’s not practical to expect most users to reconfigure their browser in that way. Given the scenarios in this topic though, it’s a potentially viable workaround.

However, be aware that enabling this setting will expose your local network to external port scanners and also increases the risk of malicious connections to local websockets which rely upon random port numbers for protection against compromise from external networks (KeePassRPC is not affected by this but see the previous security vulnerability for a good example of how multiple vulnerabilities in a local WebSocket server could be chained with the ability for an attacker to rapidly attempt connecting to every local port).

Thus, if you decide to keep this setting false for the purposes of a specific development project, I’d recommend setting yourself a reminder to change it back to the default when the project is complete.

We already check if we can connect with HTTP before trying the WebSocket connection but because different operating systems and security software can alter the type of connection failure that Firefox sees, we must also make regular attempts to the WebSocket independently of the result of that test. These environment differences are likely to explain some of the different behaviours we’re all seeing.

I also think there are two separate issues confusing the matter here:

  1. Firefox (and maybe other browsers) forces an increasing delay to failed WebSocket connections and it appears to consider all ports on localhost as equivalent for that calculation
  2. Firefox (and maybe other browsers) don’t allow more than one concurrent attempt to a WebSocket on localhost

Focussing on the latter for a moment, I think the problem would be at least minimised if we could ensure that Firefox aborts the attempt to connect to KeePassRPC after a shorter period of time, perhaps user configurable if this is something that can occasionally take many seconds on some slower machines.

We have a 750ms timeout for the HTTP requests every 2 seconds but I don’t think the WebExtensions or Web APIs offer a way to do this for WebSockets. Feel free to take a look to confirm if you have time. However, bear in mind that the upcoming Chrome manifest v3 migration (likely in 2022) might force us to make wider changes to the way we connect to KeePassRPC anyway so this probably isn’t something we should spend a lot of time on until then.