At Wavebox, security is at the centre of everything we do. It's our number one priority to ensure that everyone who uses Wavebox remains safe and secure.
Although security isn't a shiny feature to sing and dance about, we've been quietly adding more layers to our model since the beginning. So if you're interested to find out how we keep your stuff safe in Wavebox, then read on.
Friendly warning: this article does get a little bit techy, so please get in touch if you need more explanation on anything covered here.
Wavebox, the Onion
To use a common analogy, our security is like an onion. There are lots of layers, and you need to peel them back before you reach the eye-wateringly solid core. Wavebox is Open-Source so our code is out there for everyone to see, and we are immensely proud of this. With us, there's no security through obscurity, everything is on show, and this flows all the way up the Wavebox stack. Wavebox is built atop of Electron which itself is built atop the Chromium Project - these are both hugely successful platforms which are also Open-Source.
When using Chromium and Electron as a platform, it's important that good security measures are taken, especially when displaying untrusted content (e.g. content from the web). In this blog post, we'll go through some of the do's and don'ts when using Electron, and take a deeper look at some of the security implications and protections that each gives you.
Some Background on Electron and Threads
When you write an Electron app you normally start off with two threads. The browser thread and the renderer thread. You could think of the browser thread as the coordinator who makes sure everything works correctly in the background, and the renderer thread as something that's displayed on-screen (i.e. some content in a window). When you write a more complex app you may end up with lots of renderer threads, all showing different things and running in their own process. This is how Wavebox works.
You have the browser thread that runs in the background, which you never see. Then you have lots of renderer threads. Some are responsible for displaying the content of a window, and others are responsible for displaying the content of a guest webpage, and so forth.
It's important to give each of these threads its own security model, especially those that display remote, potentially untrusted content. Failing to do this, could for example give google.com access to privileged APIs. To prevent this from happening, Wavebox has high privilege threads and low privilege threads, with those displaying remote content always being low privilege. Here are 5 things we do to ensure the low privilege threads aren't allowed to do things they shouldn't be able to do:
1. Don't leave the door open
2. Don't expose high privilege APIs
3. Don't accidentally leak high privilege APIs
5. Keep your doors locked
So let's go through each of these in a bit more detail:
1. Don't leave the door open
Electron allows you to customise the way that pages are displayed. This goes all the way through to relaxing some of the security rules that a web browser normally enforces. This can be helpful when building desktop apps, but not so when displaying untrusted content. These are basic configuration options that provide the baseline to web security and although configured securely by default, changing the settings is akin to just leaving your front door open.
Don't disable webSecurity, disabling this will allow web pages to do things they shouldn't be able to, like talk between frames from different domains. This can expose to cross-site scripting (XSS) attacks.
Don't enable allowRunningInsecureContent, doing so will allow https pages to load insecure content from http pages. This breaks one of the core principles of displaying secure pages and can lead to vulnerabilities such as man-in-the-middle attacks.
2. Don't Expose High Privilege APIs
As mentioned above, Electron gives you some really great tooling to build desktop apps. To accommodate this, it integrates nodeJS into the runtime giving you tools to write to the filesystem and do other things that are considered high privilege. Giving this to some untrusted content, is like giving a prisoner a key to get out. It's a giant no-no.
- Don't enable nodeIntegration or nodeIntegrationInWorker, this can lead to things like remote code execution vulnerabilities. It's also worth noting that when sandboxing is enabled (see later) the node environment is never exposed to the thread.
- Disable webviewTag. The Webview tag is a tool that allows a page to embed remote content. A little bit like an iframe, but running in its own security context, thread and isolation. Allowing an untrusted domain to create webview tags effectively allows it create a page with a higher privilege level. In addition to disabling the tag, if any page creates a webview tag the preferences used to setup the security model should be sanitised and checked.
3. Don't Accidentally Leak High Privilege APIs
When an untrusted renderer is setup there's a small time-window before it starts loading content where the app can configure the renderer. This is done through a preload script which runs as a high privilege script. Normally this is fine, however, it's relatively easy to leak some functions to the untrusted renderer that might do privileged things. For example, storing a privileged function in the main window object would allow some untrusted page to call it and act as a trusted party.
JSONpreventing it from sniffing any information that's passed to built-in functions.
When Electron sets up a new renderer it adds a set of APIs to it and then once setup, removes some of those depending on the security setup. This enables the renderer to be setup, but effectively still exposes an untrusted render to privileged tools & APIs for some time.
- Enable sandboxing, to use a security model that's compatible with Chromium OS-level sandboxing. This uses OS-specific features to ensure that exploits in the renderer process cannot harm the system and removes all high privilege APIs from the untrusted renderer allowing only plain text messages to be passed to more trusted threads. This means that aside from all the work that the Electron team has done to secure untrusted renderers, the Chromium sandbox ensures that the renderer runs in a low-privilege mode at runtime and does not have a nodeJS environment ever. It's unable to write to disk or read system state or do anything that it should be allowed to.
5. Keep Your Doors Locked
Web browsers give users great tools to do things quickly. Dragging and dropping files is one of those tools. However, when you have a high privilege window, this can be dangerous. Imagine dragging a malicious script into a high privilege window, it could now access all those high privilege APIs.
- Limiting Navigation, can prevent this. By disabling tools such as drag and drop for certain windows and checking before the page is allowed to navigate, it's not as simple as just dragging some code into a window.
And so, back to the Wavebox onion...
Keeping Wavebox safe and secure is baked into everything we do - it's always top of our list. We work hard to implement all the available security features and follow the latest guidelines and best practices. This involves a fair bit of effort, which is probably why many others don't take the same precautions.
We initially built Wavebox to make life easier in the office. Now we, like thousands of others worldwide, use it daily to keep track of the cloud and create new workflows. So it's fundamentally important to us that the app is as stable and secure as we can make it.
We're always adding new layers of security to our onion, and to tell the truth, it can be a real pain! Sometimes our security precautions can hold back new feature releases until we figure out a way of doing things safely, and without breaking security. It nearly always results in late nights and extra coffee, but in the end, we know it's essential and totally worth it.