From 437f1192d9e570d98ff016d729eda21533ffb8ed Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Thu, 9 Mar 2017 09:23:03 -0300 Subject: [PATCH 1/2] Add initial documentation for `sandbox` option. --- docs/api/browser-window.md | 5 + docs/api/sandbox-option.md | 186 +++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 docs/api/sandbox-option.md diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2456c73e8033..13d823c231c3 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -225,6 +225,11 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope. See example [here](process.md#event-loaded). + * `sandbox` Boolean (optional) - If set, this will sandbox the renderer + associated with the window, making it compatible with chromium sandbox and + disabling the node.js engine. This is not the same as the + `nodeIntegration` option and the APIs available to the preload script are + more limited. Read more about the option [here](sandbox-option.md). * `session` [Session](session.md#class-session) (optional) - Sets the session used by the page. Instead of passing the Session object directly, you can also choose to use the `partition` option instead, which accepts a partition string. When diff --git a/docs/api/sandbox-option.md b/docs/api/sandbox-option.md new file mode 100644 index 000000000000..454a8ad05cca --- /dev/null +++ b/docs/api/sandbox-option.md @@ -0,0 +1,186 @@ +# `sandbox` Option + +> Create a browser window with renderer that can run inside chromium OS sandbox. + +One of chromium key security features is that all blink rendering/javascript +code is confined in a sandbox. This sandbox uses OS-specific features to ensure +that exploits in the renderer process cannot harm the system. + +In other words, when the sandbox is enabled, the renderers can only make changes +to the system by delegating tasks to the main process via IPC. +[Here's](https://www.chromium.org/developers/design-documents/sandbox) more +information about the sandbox. + +Since a major feature in electron is the ability to run node.js in the +renderer process(making it easier to develop desktop applications using only web +technologies), the sandbox has to disabled by electron. One of the reasons is +that most node.js APIs require system access. `require()` for example, is not +possible without file system permissions, which are unavailable in a sandboxed +environment. + +Usually this is not a problem for desktop applications since the code is always +trusted, but it makes electron less secure than chromium for displaying +untrusted web content. For applications that require more security, the +`sandbox` flag will force electron to spawn a classic chromium renderer that is +compatible with the sandbox. + +A sandboxed renderer doesn't have a node.js environment running and doesn't +expose javascript APIs to client code. The only exception is the preload script, +which has access to a subset of electron renderer API. + +Another difference is that sandboxed renderers don't modify any of the default +javascript APIs. Consequently, some APIs such as `window.open` will work as they +do in chromium(no `BrowserWindowProxy`). + +## Example + +Create a sandboxed window, simply pass `sandbox: true` to `webPreferences`: + +```js +let win +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + sandbox: true + } + }) + w.loadURL('http://google.com') +}) +``` + +This alone won't enable the OS-enforced sandbox. To use it, the +`--enable-sandbox` command-line argument must be passed to electron, which will +force `sandbox: true` to all BrowserWindow instances. + +```js +let win +app.on('ready', () => { + // no need to pass `sandbox: true` since `--enable-sandbox` was enabled. + win = new BrowserWindow() + w.loadURL('http://google.com') +}) +``` + +Note that it is not enough to call +`app.commandLine.appendSwitch('--enable-sandbox')`, as electron/node startup +code runs after it is possible to make changes to chromium sandbox settings. The +switch must be passed to electron command-line: + +``` +electron --enable-sandbox app.js +``` + +It is not possible to have the OS sandbox active only for some renderers, if +`--enable-sandbox` is enabled, normal electron windows cannot be created. + +If you need to mix sandboxed and non-sandboxed renderers in one application, +simply omit the `--enable-sandbox` argument. Without this argument, windows +created with `sandbox: true` will still have node.js disabled and communicate +only via IPC, which by itself is already a gain from security POV. + +## Preload + +An app can make customizations to sandboxed renderers using a preload script. +Here's an example: + +```js +let win +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + sandbox: true, + preload: 'preload.js' + } + }) + w.loadURL('http://google.com') +}) +``` + +and preload.js: + +```js +// This file is loaded whenever a javascript context is created. It runs in a +// private scope that can access a subset of electron renderer APIs. We must be +// careful to not leak any objects into the global scope! +const fs = require('fs') +const {ipcRenderer} = require('electron') + +// read a configuration file using the `fs` module +const buf = fs.readFileSync('allowed-popup-urls.json') +const allowedUrls = JSON.parse(buf.toString('utf8')) + +const defaultWindowOpen = window.open + +function customWindowOpen (url, ...args) { + if (allowedUrls.indexOf(url) === -1) { + ipcRenderer.sendSync('blocked-popup-notification', location.origin, url) + return null + } + return defaultWindowOpen(url, ...args) +} + +window.open = customWindowOpen +``` + +Important things to notice in the preload script: + +- Even though the sandboxed renderer doesn't have node.js running, it still has + access to a limited node-like environment:`Buffer`, `process`, `setImmediate` + and `require` are available. +- The preload can indirectly access all APIs from the main process through the + `remote` and `ipcRenderer` modules. This is how `fs`(used above) and other + modules are implemented: They are proxies to remote counterparts in the main + process. +- The preload must be contained in a single script, but it is possible to have + complex preload code composed with multiple modules by using a tool like + browserify, as explained below. In fact, browserify is already used by + electron to provide a node-like environment to the preload script. + +To create a browserify bundle and use it as a preload script, something like +the following should be used: + + browserify preload/index.js \ + -x electron \ + -x fs \ + --insert-global-vars=__filename,__dirname -o preload.js + +The `-x` flag should be used with any required module that is already exposed in +the preload scope, and tells browserify to use the enclosing `require` function +for it. `--insert-global-vars` will ensure that `process`,`Buffer` and +`setImmediate` are also taken from the enclosing scope(normally browserify +injects code for those). + +Currently the `require` function provided in the preload scope exposes the +following modules: + +- `child_process` +- `electron`(crashReporter, remote and ipcRenderer) +- `fs` +- `os` +- `timers` +- `url` + +More may be added as needed to expose more electron APIs in the sandbox, but any +module in the main process can already be used through +`electron.remote.require`. + +## Status + +Please use the `sandbox` option with care, as it still is an experimental +feature. We are still not aware of the security implications of exposing some +electron renderer APIs to the preload script, but here are some things to +consider before rendering untrusted content: + +- A preload script can accidentaly leak privileged APIs to untrusted code. +- Some bug in V8 engine may allow malicious code to access the renderer preload + APIs, effectively granting full access to the system through the `remote` + module. + +Since renderering untrusted content in electron is still uncharted territory, +the APIs exposed to the sandbox preload script should be considered more +unstable than the rest of electron APIs, and may have breaking changes to fix +security issues. + +One planned enhancement that should greatly increase security is to block IPC +messages from sandboxed renderers by default, allowing the main process +explicitly define a set of messages the renderer is allowed to send. From 870dcb9071fe9f5a883dfd2c0f04e77d1d7bb90b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 27 Mar 2017 10:14:38 -0700 Subject: [PATCH 2/2] Remove old sandbox option in list and mark as experimental --- docs/api/browser-window.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 13d823c231c3..182e798158b1 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -226,10 +226,12 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. Node global symbols back to the global scope. See example [here](process.md#event-loaded). * `sandbox` Boolean (optional) - If set, this will sandbox the renderer - associated with the window, making it compatible with chromium sandbox and - disabling the node.js engine. This is not the same as the - `nodeIntegration` option and the APIs available to the preload script are - more limited. Read more about the option [here](sandbox-option.md). + associated with the window, making it compatible with the Chromium + OS-level sandbox and disabling the Node.js engine. This is not the same as + the `nodeIntegration` option and the APIs available to the preload script + are more limited. Read more about the option [here](sandbox-option.md). + **Note:** This option is currently experimental and may change or be + removed in future Electron releases. * `session` [Session](session.md#class-session) (optional) - Sets the session used by the page. Instead of passing the Session object directly, you can also choose to use the `partition` option instead, which accepts a partition string. When @@ -287,7 +289,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. window. Defaults to `false`. See the [offscreen rendering tutorial](../tutorial/offscreen-rendering.md) for more details. - * `sandbox` Boolean (optional) - Whether to enable Chromium OS-level sandbox. * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and the specified `preload` script in a separate JavaScript context. Defaults to `false`. The context that the `preload` script runs in will still