From 8f8708680fdeedb09058e7fede5ea57e93c4d95a Mon Sep 17 00:00:00 2001 From: Erick Zhao Date: Thu, 6 May 2021 04:53:55 -0700 Subject: [PATCH] docs: rework sandbox guide (#28978) * docs: rework sandbox guide * update doc name * add missing comment to code sample * Update docs/tutorial/sandbox.md Co-authored-by: Samuel Attard * Update docs/tutorial/sandbox.md Co-authored-by: Samuel Attard * Update docs/tutorial/sandbox.md Co-authored-by: Biru Mohanathas * load https in the examples * change `process` docs to Electron's * remove bit on chrome://sandbox page * Update docs/tutorial/sandbox.md Co-authored-by: Jeremy Rose * Update docs/tutorial/sandbox.md Co-authored-by: Jeremy Rose * clarify sandbox default posture * clarify tasks sandboxed renderers need ipc for * clarify polyfilled preload environment * emphasize that --no-sandbox is bad * clarify preload polyfill `require` * format markdown references properly Co-authored-by: Samuel Attard Co-authored-by: Biru Mohanathas Co-authored-by: Jeremy Rose --- docs/README.md | 1 + docs/api/browser-window.md | 2 +- docs/api/command-line-switches.md | 3 +- docs/api/sandbox-option.md | 182 ------------------------------ docs/tutorial/sandbox.md | 169 +++++++++++++++++++++++++++ docs/tutorial/security.md | 2 +- filenames.auto.gni | 1 - 7 files changed, 174 insertions(+), 186 deletions(-) delete mode 100644 docs/api/sandbox-option.md create mode 100644 docs/tutorial/sandbox.md diff --git a/docs/README.md b/docs/README.md index c4e26f81b7df..692501a10a7d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -59,6 +59,7 @@ an issue: * [Using Native Node.js Modules](tutorial/using-native-node-modules.md) * [Performance Strategies](tutorial/performance.md) * [Security Strategies](tutorial/security.md) + * [Process Sandboxing](tutorial/sandbox.md) * [Accessibility](tutorial/accessibility.md) * [Manually Enabling Accessibility Features](tutorial/accessibility.md#manually-enabling-accessibility-features) * [Testing and Debugging](tutorial/application-debugging.md) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 010d6e66f96a..a1602e47ccf1 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -272,7 +272,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. 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). + are more limited. Read more about the option [here](../tutorial/sandbox.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/command-line-switches.md b/docs/api/command-line-switches.md index be9cac42b60d..b0239a8f46a9 100644 --- a/docs/api/command-line-switches.md +++ b/docs/api/command-line-switches.md @@ -137,7 +137,8 @@ proxy server flags that are passed. ### --no-sandbox -Disables Chromium sandbox, which is now enabled by default. +Disables the Chromium [sandbox](https://www.chromium.org/developers/design-documents/sandbox). +Forces renderer process and Chromium helper processes to run un-sandboxed. Should only be used for testing. ### --proxy-bypass-list=`hosts` diff --git a/docs/api/sandbox-option.md b/docs/api/sandbox-option.md deleted file mode 100644 index da8b530911fd..000000000000 --- a/docs/api/sandbox-option.md +++ /dev/null @@ -1,182 +0,0 @@ -# `sandbox` Option - -> Create a browser window with a sandboxed renderer. With this -option enabled, the renderer must communicate via IPC to the main process in order to access node APIs. - -One of the key security features of Chromium is that all blink rendering/JavaScript -code is executed within 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 web -technologies), the sandbox is disabled by electron. This is because -most Node.js APIs require system access. `require()` for example, is not -possible without file system permissions, which are not available 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 Node.js JavaScript APIs to client code. The only exception is the preload script, -which has access to a subset of the 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 (i.e. they do not return a [`BrowserWindowProxy`](browser-window-proxy.md)). - -## Example - -To create a sandboxed window, pass `sandbox: true` to `webPreferences`: - -```js -let win -app.whenReady().then(() => { - win = new BrowserWindow({ - webPreferences: { - sandbox: true - } - }) - win.loadURL('http://google.com') -}) -``` - -In the above code the [`BrowserWindow`](browser-window.md) that was created has Node.js disabled and can communicate -only via IPC. The use of this option stops Electron from creating a Node.js runtime in the renderer. Also, -within this new window `window.open` follows the native behavior (by default Electron creates a [`BrowserWindow`](browser-window.md) -and returns a proxy to this via `window.open`). - -[`app.enableSandbox`](app.md#appenablesandbox) can be used to force `sandbox: true` for all `BrowserWindow` instances. - -```js -let win -app.enableSandbox() -app.whenReady().then(() => { - // no need to pass `sandbox: true` since `app.enableSandbox()` was called. - win = new BrowserWindow() - win.loadURL('http://google.com') -}) -``` - -## Preload - -An app can make customizations to sandboxed renderers using a preload script. -Here's an example: - -```js -let win -app.whenReady().then(() => { - win = new BrowserWindow({ - webPreferences: { - sandbox: true, - preload: path.join(app.getAppPath(), 'preload.js') - } - }) - win.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. Without -// contextIsolation enabled, it's possible to accidentally leak privileged -// globals like ipcRenderer to web content. -const { ipcRenderer } = require('electron') - -const defaultWindowOpen = window.open - -window.open = function customWindowOpen (url, ...args) { - ipcRenderer.send('report-window-open', location.origin, url, args) - return defaultWindowOpen(url + '?from_electron=1', ...args) -} -``` - -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`, - `clearImmediate` and `require` are available. -- The preload script 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 - webpack or browserify. An example of using browserify is below. - -To create a browserify bundle and use it as a preload script, something like -the following should be used: - -```sh - browserify preload/index.js \ - -x electron \ - --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: - -- `electron` - - `crashReporter` - - `desktopCapturer` - - `ipcRenderer` - - `nativeImage` - - `webFrame` -- `events` -- `timers` -- `url` - -More may be added as needed to expose more Electron APIs in the sandbox. - -## Rendering untrusted content - -Rendering untrusted content in Electron is still somewhat uncharted territory, -though some apps are finding success (e.g. Beaker Browser). Our goal is to get -as close to Chrome as we can in terms of the security of sandboxed content, but -ultimately we will always be behind due to a few fundamental issues: - -1. We do not have the dedicated resources or expertise that Chromium has to - apply to the security of its product. We do our best to make use of what we - have, to inherit everything we can from Chromium, and to respond quickly to - security issues, but Electron cannot be as secure as Chromium without the - resources that Chromium is able to dedicate. -2. Some security features in Chrome (such as Safe Browsing and Certificate - Transparency) require a centralized authority and dedicated servers, both of - which run counter to the goals of the Electron project. As such, we disable - those features in Electron, at the cost of the associated security they - would otherwise bring. -3. There is only one Chromium, whereas there are many thousands of apps built - on Electron, all of which behave slightly differently. Accounting for those - differences can yield a huge possibility space, and make it challenging to - ensure the security of the platform in unusual use cases. -4. We can't push security updates to users directly, so we rely on app vendors - to upgrade the version of Electron underlying their app in order for - security updates to reach users. - -Here are some things to consider before rendering untrusted content: - -- A preload script can accidentally leak privileged APIs to untrusted code, - unless [`contextIsolation`](../tutorial/security.md#3-enable-context-isolation-for-remote-content) - is also enabled. -- Some bug in the V8 engine may allow malicious code to access the renderer - preload APIs, effectively granting full access to the system through the - `remote` module. Therefore, it is highly recommended to [disable the `remote` - module](../tutorial/security.md#15-disable-the-remote-module). - If disabling is not feasible, you should selectively [filter the `remote` - module](../tutorial/security.md#16-filter-the-remote-module). -- While we make our best effort to backport Chromium security fixes to older - versions of Electron, we do not make a guarantee that every fix will be - backported. Your best chance at staying secure is to be on the latest stable - version of Electron. diff --git a/docs/tutorial/sandbox.md b/docs/tutorial/sandbox.md new file mode 100644 index 000000000000..d69a7dd4d623 --- /dev/null +++ b/docs/tutorial/sandbox.md @@ -0,0 +1,169 @@ +# Process Sandboxing + +One key security feature in Chromium is that processes can be executed within a sandbox. +The sandbox limits the harm that malicious code can cause by limiting access to most +system resources — sandboxed processes can only freely use CPU cycles and memory. +In order to perform operations requiring additional privilege, sandboxed processes +use dedicated communication channels to delegate tasks to more privileged processes. + +In Chromium, sandboxing is applied to most processes other than the main process. +This includes renderer processes, as well as utility processes such as the audio service, +the GPU service and the network service. + +See Chromium's [Sandbox design document][sandbox] for more information. + +## Electron's sandboxing policies + +Electron comes with a mixed sandbox environment, meaning sandboxed processes can run +alongside privileged ones. By default, renderer processes are not sandboxed, but +utility processes are. Note that as in Chromium, the main (browser) process is +privileged and cannot be sandboxed. + +Historically, this mixed sandbox approach was established because having Node.js available +in the renderer is an extremely powerful tool for app developers. Unfortunately, this +feature is also an equally massive security vulnerability. + +Theoretically, unsandboxed renderers are not a problem for desktop applications that +only display trusted code, but they make Electron less secure than Chromium for +displaying untrusted web content. However, even purportedly trusted code may be +dangerous — there are countless attack vectors that malicious actors can use, from +cross-site scripting to content injection to man-in-the-middle attacks on remotely loaded +websites, just to name a few. For this reason, we recommend enabling renderer sandboxing +for the vast majority of cases under an abundance of caution. + + +Note that there is an active discussion in the issue tracker to enable renderer sandboxing +by default. See [#28466][issue-28466]) for details. + +## Sandbox behaviour in Electron + +Sandboxed processes in Electron behave _mostly_ in the same way as Chromium's do, but +Electron has a few additional concepts to consider because it interfaces with Node.js. + +### Renderer processes + +When renderer processes in Electron are sandboxed, they behave in the same way as a +regular Chrome renderer would. A sandboxed renderer won't have a Node.js +environment initialized. + + +Therefore, when the sandbox is enabled, renderer processes can only perform privileged +tasks (such as interacting with the filesystem, making changes to the system, or spawning +subprocesses) by delegating these tasks to the main process via inter-process +communication (IPC). + +### Preload scripts + +In order to allow renderer processes to communicate with the main process, preload +scripts attached to sandboxed renderers will still have a polyfilled subset of Node.js +APIs available. A `require` function similar to Node's `require` module is exposed, +but can only import a subset of Electron and Node's built-in modules: + +* `electron` (only renderer process modules) +* [`events`](https://nodejs.org/api/events.html) +* [`timers`](https://nodejs.org/api/timers.html) +* [`url`](https://nodejs.org/api/url.html) + +In addition, the preload script also polyfills certain Node.js primitives as globals: + +* [`Buffer`](https://nodejs.org/api/Buffer.html) +* [`process`](../api/process.md) +* [`clearImmediate`](https://nodejs.org/api/timers.html#timers_clearimmediate_immediate) +* [`setImmediate`](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) + +Because the `require` function is a polyfill with limited functionality, you will not be +able to use [CommonJS modules][commonjs] to separate your preload script into multiple +files. If you need to split your preload code, use a bundler such as [webpack][webpack] +or [Parcel][parcel]. + +Note that because the environment presented to the `preload` script is substantially +more privileged than that of a sandboxed renderer, it is still possible to leak +privileged APIs to untrusted code running in the renderer process unless +[`contextIsolation`][contextIsolation] is enabled. + +## Configuring the sandbox + +### Enabling the sandbox for a single process + +In Electron, renderer sandboxing can be enabled on a per-process basis with +the `sandbox: true` preference in the [`BrowserWindow`][browser-window] constructor. + +```js +// main.js +app.whenReady().then(() => { + const win = new BrowserWindow({ + webPreferences: { + sandbox: true + } + }) + win.loadURL('https://google.com') +}) +``` + +### Enabling the sandbox globally + +If you want to force sandboxing for all renderers, you can also use the +[`app.enableSandbox`][enable-sandbox] API. Note that this API has to be called before the +app's `ready` event. + +```js +// main.js +app.enableSandbox() +app.whenReady().then(() => { + // no need to pass `sandbox: true` since `app.enableSandbox()` was called. + const win = new BrowserWindow() + win.loadURL('https://google.com') +}) +``` + +### Disabling Chromium's sandbox (testing only) + +You can also disable Chromium's sandbox entirely with the [`--no-sandbox`][no-sandbox] +CLI flag, which will disable the sandbox for all processes (including utility processes). +We highly recommend that you only use this flag for testing purposes, and **never** +in production. + +Note that the `sandbox: true` option will still disable the renderer's Node.js +environment. + +## A note on rendering untrusted content + +Rendering untrusted content in Electron is still somewhat uncharted territory, +though some apps are finding success (e.g. [Beaker Browser][beaker]). +Our goal is to get as close to Chrome as we can in terms of the security of +sandboxed content, but ultimately we will always be behind due to a few fundamental +issues: + +1. We do not have the dedicated resources or expertise that Chromium has to + apply to the security of its product. We do our best to make use of what we + have, to inherit everything we can from Chromium, and to respond quickly to + security issues, but Electron cannot be as secure as Chromium without the + resources that Chromium is able to dedicate. +2. Some security features in Chrome (such as Safe Browsing and Certificate + Transparency) require a centralized authority and dedicated servers, both of + which run counter to the goals of the Electron project. As such, we disable + those features in Electron, at the cost of the associated security they + would otherwise bring. +3. There is only one Chromium, whereas there are many thousands of apps built + on Electron, all of which behave slightly differently. Accounting for those + differences can yield a huge possibility space, and make it challenging to + ensure the security of the platform in unusual use cases. +4. We can't push security updates to users directly, so we rely on app vendors + to upgrade the version of Electron underlying their app in order for + security updates to reach users. + +While we make our best effort to backport Chromium security fixes to older +versions of Electron, we do not make a guarantee that every fix will be +backported. Your best chance at staying secure is to be on the latest stable +version of Electron. + +[sandbox]: https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md +[issue-28466]: https://github.com/electron/electron/issues/28466 +[browser-window]: ../api/browser-window.md +[enable-sandbox]: ../api/app.md#appenablesandbox +[no-sandbox]: ../api/command-line-switches.md#--no-sandbox +[commonjs]: https://nodejs.org/api/modules.html#modules_modules_commonjs_modules +[webpack]: https://webpack.js.org/ +[parcel]: https://parceljs.org/ +[context-isolation]: ./context-isolation.md +[beaker]: https://github.com/beakerbrowser/beaker diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index f22de2a6befe..598e0242aa8f 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -692,5 +692,5 @@ which potential security issues are not as widely known. [window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandlerhandler [will-navigate]: ../api/web-contents.md#event-will-navigate [open-external]: ../api/shell.md#shellopenexternalurl-options -[sandbox]: ../api/sandbox-option.md +[sandbox]: ../tutorial/sandbox.md [responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure diff --git a/filenames.auto.gni b/filenames.auto.gni index 900a983eecbd..b88878ea1898 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -42,7 +42,6 @@ auto_filenames = { "docs/api/power-save-blocker.md", "docs/api/process.md", "docs/api/protocol.md", - "docs/api/sandbox-option.md", "docs/api/screen.md", "docs/api/service-workers.md", "docs/api/session.md",