2016-05-06 10:23:18 +00:00
|
|
|
|
# Security, Native Capabilities, and Your Responsibility
|
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
As web developers, we usually enjoy the strong security net of the browser - the
|
2018-01-12 15:24:48 +00:00
|
|
|
|
risks associated with the code we write are relatively small. Our websites are
|
2016-05-08 18:21:11 +00:00
|
|
|
|
granted limited powers in a sandbox, and we trust that our users enjoy a browser
|
|
|
|
|
built by a large team of engineers that is able to quickly respond to newly
|
|
|
|
|
discovered security threats.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
2016-05-08 18:21:11 +00:00
|
|
|
|
When working with Electron, it is important to understand that Electron is not
|
|
|
|
|
a web browser. It allows you to build feature-rich desktop applications with
|
|
|
|
|
familiar web technologies, but your code wields much greater power. JavaScript
|
2016-06-01 05:36:45 +00:00
|
|
|
|
can access the filesystem, user shell, and more. This allows you to build
|
2016-05-08 18:21:11 +00:00
|
|
|
|
high quality native applications, but the inherent security risks scale with the
|
|
|
|
|
additional powers granted to your code.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
With that in mind, be aware that displaying arbitrary content from untrusted
|
2016-05-08 18:21:11 +00:00
|
|
|
|
sources poses a severe security risk that Electron is not intended to handle.
|
2016-05-10 08:41:16 +00:00
|
|
|
|
In fact, the most popular Electron apps (Atom, Slack, Visual Studio Code, etc)
|
|
|
|
|
display primarily local content (or trusted, secure remote content without Node
|
|
|
|
|
integration) – if your application executes code from an online source, it is
|
|
|
|
|
your responsibility to ensure that the code is not malicious.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
2017-01-30 18:57:53 +00:00
|
|
|
|
## Reporting Security Issues
|
2017-01-30 18:49:17 +00:00
|
|
|
|
|
|
|
|
|
For information on how to properly disclose an Electron vulnerability,
|
|
|
|
|
see [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md)
|
|
|
|
|
|
2016-05-06 10:23:18 +00:00
|
|
|
|
## Chromium Security Issues and Upgrades
|
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
While Electron strives to support new versions of Chromium as soon as possible,
|
|
|
|
|
developers should be aware that upgrading is a serious undertaking - involving
|
|
|
|
|
hand-editing dozens or even hundreds of files. Given the resources and
|
|
|
|
|
contributions available today, Electron will often not be on the very latest
|
|
|
|
|
version of Chromium, lagging behind by either days or weeks.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
We feel that our current system of updating the Chromium component strikes an
|
|
|
|
|
appropriate balance between the resources we have available and the needs of the
|
|
|
|
|
majority of applications built on top of the framework. We definitely are
|
|
|
|
|
interested in hearing more about specific use cases from the people that build
|
|
|
|
|
things on top of Electron. Pull requests and contributions supporting this
|
|
|
|
|
effort are always very welcome.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
|
|
|
|
## Ignoring Above Advice
|
2016-09-08 17:39:29 +00:00
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
A security issue exists whenever you receive code from a remote destination and
|
|
|
|
|
execute it locally. As an example, consider a remote website being displayed
|
|
|
|
|
inside a browser window. If an attacker somehow manages to change said content
|
|
|
|
|
(either by attacking the source directly, or by sitting between your app and
|
|
|
|
|
the actual destination), they will be able to execute native code on the user's
|
|
|
|
|
machine.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
> :warning: Under no circumstances should you load and execute remote code with
|
2018-01-29 01:25:05 +00:00
|
|
|
|
Node.js integration enabled. Instead, use only local files (packaged together with
|
|
|
|
|
your application) to execute Node.js code. To display remote content, use the
|
2016-05-10 08:41:16 +00:00
|
|
|
|
`webview` tag and make sure to disable the `nodeIntegration`.
|
2016-05-06 10:23:18 +00:00
|
|
|
|
|
2018-01-29 00:55:11 +00:00
|
|
|
|
#### Checklist: Security Recommendations
|
2016-09-08 17:39:29 +00:00
|
|
|
|
|
2016-05-06 10:23:18 +00:00
|
|
|
|
This is not bulletproof, but at the least, you should attempt the following:
|
|
|
|
|
|
2018-01-29 00:55:11 +00:00
|
|
|
|
* [Only display secure (https) content](#only-display-secure-content)
|
2018-01-29 01:25:05 +00:00
|
|
|
|
* [Disable the Node integration in all renderers that display remote content](#disable-node-integration-for-remote-content)
|
2017-01-09 17:25:34 +00:00
|
|
|
|
(setting `nodeIntegration` to `false` in `webPreferences`)
|
2017-01-20 14:45:39 +00:00
|
|
|
|
* Enable context isolation in all renderers that display remote content
|
2017-01-09 17:25:34 +00:00
|
|
|
|
(setting `contextIsolation` to `true` in `webPreferences`)
|
2017-01-20 14:50:10 +00:00
|
|
|
|
* Use `ses.setPermissionRequestHandler()` in all sessions that load remote content
|
2016-05-10 08:41:16 +00:00
|
|
|
|
* Do not disable `webSecurity`. Disabling it will disable the same-origin policy.
|
2016-05-08 18:21:11 +00:00
|
|
|
|
* Define a [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/)
|
2016-06-16 22:19:38 +00:00
|
|
|
|
, and use restrictive rules (i.e. `script-src 'self'`)
|
2016-07-07 10:22:12 +00:00
|
|
|
|
* [Override and disable `eval`](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6-L8)
|
2016-05-08 18:21:11 +00:00
|
|
|
|
, which allows strings to be executed as code.
|
2016-05-10 08:41:16 +00:00
|
|
|
|
* Do not set `allowRunningInsecureContent` to true.
|
|
|
|
|
* Do not enable `experimentalFeatures` or `experimentalCanvasFeatures` unless
|
|
|
|
|
you know what you're doing.
|
|
|
|
|
* Do not use `blinkFeatures` unless you know what you're doing.
|
2016-10-19 08:08:46 +00:00
|
|
|
|
* WebViews: Do not add the `nodeintegration` attribute.
|
2016-05-10 08:41:16 +00:00
|
|
|
|
* WebViews: Do not use `disablewebsecurity`
|
|
|
|
|
* WebViews: Do not use `allowpopups`
|
|
|
|
|
* WebViews: Do not use `insertCSS` or `executeJavaScript` with remote CSS/JS.
|
2018-01-29 02:19:49 +00:00
|
|
|
|
* [WebViews: Verify the options and params of all `<webview>` tags](#verify-webview-options-before-creation)
|
2016-05-10 08:41:16 +00:00
|
|
|
|
|
2018-01-29 00:55:11 +00:00
|
|
|
|
## Only Display Secure Content
|
2018-01-29 01:25:05 +00:00
|
|
|
|
Any resources not included with your application should be loaded using a secure
|
|
|
|
|
protocol like `HTTPS`. Furthermore, avoid "mixed content", which occurs when the
|
|
|
|
|
initial HTML is loaded over an `HTTPS` connection, but additional resources
|
|
|
|
|
(scripts, stylesheets, etc) are loaded over an insecure connection.
|
2018-01-29 00:55:11 +00:00
|
|
|
|
|
2018-01-29 01:25:05 +00:00
|
|
|
|
### Why?
|
2018-01-29 00:55:11 +00:00
|
|
|
|
`HTTPS` has three main benefits:
|
|
|
|
|
|
2018-01-29 01:25:05 +00:00
|
|
|
|
1) It authenticates the remote server, ensuring that the host is actually who it
|
|
|
|
|
claims to be. When loading a resource from an `HTTPS` host, it prevents an
|
|
|
|
|
attacker from impersonating that host, thus ensuring that the computer your
|
|
|
|
|
app's users are connecting to is actually the host you wanted them to connect
|
|
|
|
|
to.
|
|
|
|
|
2) It ensures data integrity, asserting that the data was not modified while in
|
|
|
|
|
transit between your application and the host.
|
|
|
|
|
3) It encryps the traffic between your user and the destination host, making it
|
|
|
|
|
more difficult to eavesdropping on the information sent between your app and
|
|
|
|
|
the host.
|
|
|
|
|
|
|
|
|
|
### How?
|
2018-01-29 00:55:11 +00:00
|
|
|
|
```js
|
|
|
|
|
// Bad
|
|
|
|
|
browserWindow.loadURL('http://my-website.com')
|
|
|
|
|
|
|
|
|
|
// Good
|
|
|
|
|
browserWindow.loadURL('https://my-website.com')
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```html
|
|
|
|
|
<!-- Bad -->
|
|
|
|
|
<script crossorigin src="http://cdn.com/react.js"></script>
|
|
|
|
|
<link rel="stylesheet" href="http://cdn.com/style.css">
|
|
|
|
|
|
|
|
|
|
<!-- Good -->
|
|
|
|
|
<script crossorigin src="https://cdn.com/react.js"></script>
|
|
|
|
|
<link rel="stylesheet" href="https://cdn.com/style.css">
|
|
|
|
|
```
|
|
|
|
|
|
2018-01-29 01:25:05 +00:00
|
|
|
|
## Disable Node Integration for Remote Content
|
|
|
|
|
It is paramount that you disable Node integration in any renderer (`BrowserWindow`,
|
2018-01-29 02:19:49 +00:00
|
|
|
|
`BrowserView`, or `WebView`) that loads remote content. The goal of disabling Node
|
2018-01-29 01:25:05 +00:00
|
|
|
|
integration is to limit the powers you grant to remote content, thus making it
|
|
|
|
|
dramatically more difficult for an attacker to harm your users should they gain
|
2018-01-29 02:19:49 +00:00
|
|
|
|
the ability to execute JavaScript on your website.
|
2018-01-29 01:25:05 +00:00
|
|
|
|
|
|
|
|
|
Disabling Node integration does not mean that you cannot grant additional powers
|
2018-01-29 02:19:49 +00:00
|
|
|
|
to the website you are loading. If you are opening a `BrowserWindow` pointed at
|
|
|
|
|
`https://my-website.com`, the goal is to give that website exactly the abilities
|
|
|
|
|
it needs, but no more.
|
2018-01-29 01:25:05 +00:00
|
|
|
|
|
|
|
|
|
### Why?
|
2018-01-29 02:19:49 +00:00
|
|
|
|
A cross-site-scripting (XSS) attack becomes dramatically more dangerous if an
|
|
|
|
|
attacker can jump out of the renderer process and execute code on the user's
|
|
|
|
|
computer. Cross-site-scripting attacks are fairly common - and while an issue,
|
|
|
|
|
their power is usually limited to messing with the website that they are executed
|
|
|
|
|
on. However, in a renderer process with Node.js integration enabled, an XSS attack
|
|
|
|
|
becomes a whole different class of attack: A so-called "Remote Code Execution"
|
|
|
|
|
(RCE) attack. Disabling Node.js integration limits the power of successful XSS
|
|
|
|
|
attacks.
|
2018-01-29 01:25:05 +00:00
|
|
|
|
|
|
|
|
|
### How?
|
|
|
|
|
```js
|
|
|
|
|
// Bad
|
|
|
|
|
const mainWindow = new BrowserWindow()
|
|
|
|
|
mainWindow.loadURL('https://my-website.com')
|
|
|
|
|
|
|
|
|
|
// Good
|
|
|
|
|
const mainWindow = new BrowserWindow({
|
|
|
|
|
webPreferences: {
|
|
|
|
|
nodeIntegration: false,
|
|
|
|
|
preload: './preload.js'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
mainWindow.loadURL('https://my-website.com')
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
When disabling Node integration, you can still expose APIs to your
|
|
|
|
|
website that do consume Node.js modules or features. Preload scripts continue to
|
|
|
|
|
have access to `require` and other Node.js features, allowing developers to expose
|
|
|
|
|
a custom API to remotely loaded content.
|
|
|
|
|
|
|
|
|
|
In the following example preload script, the later loaded website will have access
|
|
|
|
|
to a `window.readConfig()` method, but no Node.js features.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const { readFileSync } = require('fs')
|
|
|
|
|
|
|
|
|
|
window.readConfig = function () {
|
|
|
|
|
const data = readFileSync('./config.json')
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2018-01-29 19:01:22 +00:00
|
|
|
|
## Enable Context Isolation for Remote Content
|
|
|
|
|
|
|
|
|
|
## Handle Session Permission Requests From Remote Content
|
|
|
|
|
|
|
|
|
|
You may have seen permission requests while using Chrome: They pop up whenever
|
|
|
|
|
the website attempts to use a feature that the user has to manually approve (
|
|
|
|
|
like notifications).
|
|
|
|
|
|
|
|
|
|
The API is based on the [Chromium permissions API](https://developer.chrome.com/extensions/permissions)
|
|
|
|
|
and implements the same types of permissions.
|
|
|
|
|
|
|
|
|
|
### Why?
|
|
|
|
|
|
|
|
|
|
By default, Electron will automatically approve all permission requests unless
|
|
|
|
|
the developer has manually configured a custom handler. While a solid default,
|
|
|
|
|
security-conscious developers might want to assume the very opposite.
|
|
|
|
|
|
|
|
|
|
### How?
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
const { session } = require('electron')
|
|
|
|
|
|
|
|
|
|
session
|
|
|
|
|
.fromPartition('some-partition')
|
|
|
|
|
.setPermissionRequestHandler((webContents, permission, callback) => {
|
|
|
|
|
const url = webContents.getURL()
|
|
|
|
|
|
|
|
|
|
if (permission === 'notifications') {
|
|
|
|
|
// Approves the permissions request
|
|
|
|
|
callback(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!url.startsWith('https://my-website.com')) {
|
|
|
|
|
// Denies the permissions request
|
|
|
|
|
return callback(false)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
2018-01-29 02:19:49 +00:00
|
|
|
|
## Verify WebView Options Before Creation
|
|
|
|
|
A WebView created in a renderer process that does not have Node.js integration
|
|
|
|
|
enabled will not be able to enable integration itself. However, a WebView will
|
|
|
|
|
always create an independent renderer process with its own `webPreferences`.
|
|
|
|
|
|
|
|
|
|
It is a good idea to control the creation of new `WebViews` from the main process
|
|
|
|
|
and to verify that their webPreferences do not disable security features.
|
|
|
|
|
|
|
|
|
|
### Why?
|
|
|
|
|
Since WebViews live in the DOM, they can be created by a script running on your
|
|
|
|
|
website even if Node integration is otherwise disabled.
|
|
|
|
|
|
|
|
|
|
Electron enables developers to disable various security features that control
|
|
|
|
|
a renderer process. In most cases, developers do not need to disable any of those
|
|
|
|
|
features - and you should therefore not allow different configurations for newly
|
|
|
|
|
created `<WebView>` tags.
|
|
|
|
|
|
|
|
|
|
### How?
|
|
|
|
|
Before a `<WebView>` tag is attached, Electron will fire the
|
|
|
|
|
`will-attach-webview` event on the hosting `webContents`. Use the event to
|
|
|
|
|
prevent the creation of WebViews with possibly insecure options.
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
app.on('web-contents-created', (event, contents) => {
|
|
|
|
|
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
|
|
|
|
// Strip away preload scripts if unused or verify their location is legitimate
|
|
|
|
|
delete webPreferences.preload
|
|
|
|
|
delete webPreferences.preloadURL
|
|
|
|
|
|
|
|
|
|
// Disable node integration
|
|
|
|
|
webPreferences.nodeIntegration = false
|
|
|
|
|
|
|
|
|
|
// Verify URL being loaded
|
|
|
|
|
if (!params.src.startsWith('https://yourapp.com/')) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
2016-05-10 08:41:16 +00:00
|
|
|
|
Again, this list merely minimizes the risk, it does not remove it. If your goal
|
|
|
|
|
is to display a website, a browser will be a more secure option.
|