From ebe54b52fbc7e7b140121e23f342abc06afe7006 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Sun, 26 Aug 2018 10:38:15 +0200 Subject: [PATCH] Add webPreferences.enableRemoteModule option --- atom/browser/api/atom_api_web_contents.cc | 8 ++++ atom/browser/api/atom_api_web_contents.h | 2 + atom/browser/web_contents_preferences.cc | 8 ++++ atom/browser/web_contents_preferences.h | 3 ++ atom/common/options_switches.cc | 4 ++ atom/common/options_switches.h | 2 + atom/renderer/renderer_client_base.cc | 20 ++++++-- docs/api/browser-window.md | 2 + docs/api/clipboard.md | 3 ++ docs/api/remote.md | 6 ++- docs/api/screen.md | 3 ++ docs/api/webview-tag.md | 12 +++++ filenames.gni | 1 + lib/browser/guest-view-manager.js | 2 + lib/browser/guest-window-manager.js | 3 +- lib/browser/rpc-server.js | 35 ++++++++++---- lib/common/api/clipboard.js | 20 ++++++-- lib/renderer/api/module-list.js | 11 +++-- lib/renderer/api/screen.js | 3 +- lib/renderer/remote.js | 10 ++++ lib/renderer/web-view/web-view-attributes.js | 15 ++++++ lib/renderer/web-view/web-view-constants.js | 1 + lib/renderer/web-view/web-view.js | 4 +- .../api/exports/child_process.js | 3 +- .../api/exports/electron.js | 2 + lib/sandboxed_renderer/api/exports/fs.js | 3 +- lib/sandboxed_renderer/api/exports/os.js | 3 +- lib/sandboxed_renderer/api/exports/path.js | 3 +- lib/sandboxed_renderer/api/module-list.js | 17 +++++-- lib/sandboxed_renderer/init.js | 14 +++++- spec/api-browser-window-spec.js | 40 ++++++++++++++++ spec/api-crash-reporter-spec.js | 5 ++ .../fixtures/module/preload-disable-remote.js | 8 ++++ spec/fixtures/module/preload-remote.js | 5 ++ spec/security-warnings-spec.js | 21 +++++---- spec/webview-spec.js | 46 +++++++++++++++++++ 36 files changed, 303 insertions(+), 45 deletions(-) create mode 100644 lib/renderer/remote.js create mode 100644 spec/fixtures/module/preload-disable-remote.js create mode 100644 spec/fixtures/module/preload-remote.js diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 8ebe556c76c05..e1500a84ffdbe 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -1896,6 +1896,13 @@ v8::Local WebContents::GetLastWebPreferences( return mate::ConvertToV8(isolate, *web_preferences->last_preference()); } +bool WebContents::IsRemoteModuleEnabled() const { + if (auto* web_preferences = WebContentsPreferences::From(web_contents())) { + return web_preferences->IsRemoteModuleEnabled(); + } + return true; +} + v8::Local WebContents::GetOwnerBrowserWindow() const { if (owner_window()) return BrowserWindow::From(isolate(), owner_window()); @@ -2067,6 +2074,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("_getPreloadPath", &WebContents::GetPreloadPath) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getLastWebPreferences", &WebContents::GetLastWebPreferences) + .SetMethod("_isRemoteModuleEnabled", &WebContents::IsRemoteModuleEnabled) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker) .SetMethod("unregisterServiceWorker", diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index fb930e8989738..6bbdd403bdb5a 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -249,6 +249,8 @@ class WebContents : public mate::TrackableObject, v8::Local GetWebPreferences(v8::Isolate* isolate) const; v8::Local GetLastWebPreferences(v8::Isolate* isolate) const; + bool IsRemoteModuleEnabled() const; + // Returns the owner window. v8::Local GetOwnerBrowserWindow() const; diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index b62023a0a8271..fc4bd0dc11a13 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -171,6 +171,10 @@ bool WebContentsPreferences::GetPreference(const base::StringPiece& name, return GetAsString(&preference_, name, value); } +bool WebContentsPreferences::IsRemoteModuleEnabled() const { + return IsEnabled(options::kEnableRemoteModule, true); +} + bool WebContentsPreferences::GetPreloadPath( base::FilePath::StringType* path) const { DCHECK(path); @@ -267,6 +271,10 @@ void WebContentsPreferences::AppendCommandLineSwitches( } } + // Whether to enable the remote module + if (!IsRemoteModuleEnabled()) + command_line->AppendSwitch(switches::kDisableRemoteModule); + // Run Electron APIs and preload script in isolated world if (IsEnabled(options::kContextIsolation)) command_line->AppendSwitch(switches::kContextIsolation); diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index d835347afcd25..2b6ebccbd5542 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -55,6 +55,9 @@ class WebContentsPreferences // Return true if the particular preference value exists. bool GetPreference(const base::StringPiece& name, std::string* value) const; + // Whether to enable the remote module + bool IsRemoteModuleEnabled() const; + // Returns the preload script path. bool GetPreloadPath(base::FilePath::StringType* path) const; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index ef9998a84d892..9108b310c2375 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -110,6 +110,9 @@ const char kPreloadURL[] = "preloadURL"; // Enable the node integration. const char kNodeIntegration[] = "nodeIntegration"; +// Enable the remote module +const char kEnableRemoteModule[] = "enableRemoteModule"; + // Enable context isolation of Electron APIs and preload script const char kContextIsolation[] = "contextIsolation"; @@ -193,6 +196,7 @@ const char kBackgroundColor[] = "background-color"; const char kPreloadScript[] = "preload"; const char kPreloadScripts[] = "preload-scripts"; const char kNodeIntegration[] = "node-integration"; +const char kDisableRemoteModule[] = "disable-remote-module"; const char kContextIsolation[] = "context-isolation"; const char kGuestInstanceID[] = "guest-instance-id"; const char kOpenerID[] = "opener-id"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 4880d9b0912eb..4a8a40fe1e828 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -58,6 +58,7 @@ extern const char kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; +extern const char kEnableRemoteModule[]; extern const char kContextIsolation[]; extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; @@ -97,6 +98,7 @@ extern const char kBackgroundColor[]; extern const char kPreloadScript[]; extern const char kPreloadScripts[]; extern const char kNodeIntegration[]; +extern const char kDisableRemoteModule[]; extern const char kContextIsolation[]; extern const char kGuestInstanceID[]; extern const char kOpenerID[]; diff --git a/atom/renderer/renderer_client_base.cc b/atom/renderer/renderer_client_base.cc index cd7d0b57f43a1..14f8fc490c13b 100644 --- a/atom/renderer/renderer_client_base.cc +++ b/atom/renderer/renderer_client_base.cc @@ -71,6 +71,15 @@ std::vector ParseSchemesCLISwitch(base::CommandLine* command_line, base::SPLIT_WANT_NONEMPTY); } +void SetHiddenValue(v8::Handle context, + const base::StringPiece& key, + v8::Local value) { + v8::Isolate* isolate = context->GetIsolate(); + v8::Local privateKey = + v8::Private::ForApi(isolate, mate::StringToV8(isolate, key)); + context->Global()->SetPrivate(context, privateKey, value); +} + } // namespace RendererClientBase::RendererClientBase() { @@ -100,10 +109,13 @@ void RendererClientBase::DidCreateScriptContext( auto context_id = base::StringPrintf( "%s-%" PRId64, renderer_client_id_.c_str(), ++next_context_id_); v8::Isolate* isolate = context->GetIsolate(); - v8::Local key = mate::StringToSymbol(isolate, "contextId"); - v8::Local private_key = v8::Private::ForApi(isolate, key); - v8::Local value = mate::ConvertToV8(isolate, context_id); - context->Global()->SetPrivate(context, private_key, value); + SetHiddenValue(context, "contextId", mate::ConvertToV8(isolate, context_id)); + + auto* command_line = base::CommandLine::ForCurrentProcess(); + bool enableRemoteModule = + !command_line->HasSwitch(switches::kDisableRemoteModule); + SetHiddenValue(context, "enableRemoteModule", + mate::ConvertToV8(isolate, enableRemoteModule)); } void RendererClientBase::AddRenderBindings( diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 673fc89e53ac5..d32fcc9925ba6 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -270,6 +270,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. 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. + * `enableRemoteModule` Boolean (optional) - Whether to enable the [`remote`](remote.md) module. + Default is `true`. * `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/clipboard.md b/docs/api/clipboard.md index 429395877af23..9dafd47c4d8eb 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -4,6 +4,9 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) +In the renderer process context it depends on the [`remote`](remote.md) module on Linux, +it is therefore not available when this module is disabled. + The following example shows how to write a string to the clipboard: ```javascript diff --git a/docs/api/remote.md b/docs/api/remote.md index 4bff092616215..bffe18db39dd6 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -24,6 +24,10 @@ win.loadURL('https://github.com') **Note:** For the reverse (access the renderer process from the main process), you can use [webContents.executeJavaScript](web-contents.md#contentsexecutejavascriptcode-usergesture-callback). +**Note:** The remote module can be disabled for security reasons in the following contexts: +- [`BrowserWindow`](browser-window.md) - by setting the `enableRemoteModule` option to `false`. +- [``](webview-tag.md) - by setting the `enableremotemodule` attribute to `false`. + ## Remote Objects Each object (including functions) returned by the `remote` module represents an @@ -180,7 +184,7 @@ belongs. **Note:** Do not use `removeAllListeners` on [`BrowserWindow`](browser-window.md). Use of this can remove all [`blur`](https://developer.mozilla.org/en-US/docs/Web/Events/blur) listeners, disable click events on touch bar buttons, and other unintended -consequences. +consequences. ### `remote.getCurrentWebContents()` diff --git a/docs/api/screen.md b/docs/api/screen.md index d847b55abdd9d..c45a22e0efe4b 100644 --- a/docs/api/screen.md +++ b/docs/api/screen.md @@ -7,6 +7,9 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer You cannot require or use this module until the `ready` event of the `app` module is emitted. +In the renderer process context it depends on the [`remote`](remote.md) module, +it is therefore not available when this module is disabled. + `screen` is an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). **Note:** In the renderer / DevTools, `window.screen` is a reserved DOM diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index de3bf24e9343b..d9f2647db9b51 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -126,6 +126,15 @@ integration and can use node APIs like `require` and `process` to access low level system resources. Node integration is disabled by default in the guest page. +### `enableremotemodule` + +```html + +``` + +When this attribute is `false` the guest page in `webview` will not have access +to the [`remote`](remote.md) module. The remote module is avaiable by default. + ### `plugins` ```html @@ -613,6 +622,9 @@ Shows pop-up dictionary that searches the selected word on the page. Returns [`WebContents`](web-contents.md) - The web contents associated with this `webview`. +It depends on the [`remote`](remote.md) module, +it is therefore not available when this module is disabled. + ## DOM events The following DOM events are available to the `webview` tag: diff --git a/filenames.gni b/filenames.gni index 6896cf557849c..e196c4789624c 100644 --- a/filenames.gni +++ b/filenames.gni @@ -68,6 +68,7 @@ filenames = { "lib/renderer/init.js", "lib/renderer/inspector.js", "lib/renderer/ipc-renderer-internal.js", + "lib/renderer/remote.js", "lib/renderer/override.js", "lib/renderer/security-warnings.js", "lib/renderer/web-frame-init.js", diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 05b02863d197d..b47d53501e333 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -209,6 +209,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn const webPreferences = { guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, + enableRemoteModule: params.enableremotemodule, plugins: params.plugins, zoomFactor: embedder._getZoomFactor(), webSecurity: !params.disablewebsecurity, @@ -243,6 +244,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn ['javascript', false], ['nativeWindowOpen', true], ['nodeIntegration', false], + ['enableRemoteModule', false], ['sandbox', true] ]) diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index 8663b2cb8fe1a..5bc5d26be6664 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -14,6 +14,7 @@ const inheritedWebPreferences = new Map([ ['javascript', false], ['nativeWindowOpen', true], ['nodeIntegration', false], + ['enableRemoteModule', false], ['sandbox', true], ['webviewTag', false] ]) @@ -195,7 +196,7 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, const options = {} const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] - const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload', 'javascript', 'contextIsolation', 'webviewTag'] + const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'] const disposition = 'new-window' // Used to store additional features diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 262d7e2ea9f45..f306ee0a69e1c 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -263,11 +263,17 @@ const callFunction = function (event, contextId, func, caller, args) { const handleRemoteCommand = function (channel, handler) { ipcMain.on(channel, (event, contextId, ...args) => { let returnValue + if (!event.sender._isRemoteModuleEnabled()) { + event.returnValue = null + return + } + try { returnValue = handler(event, contextId, ...args) } catch (error) { returnValue = exceptionToMeta(event.sender, contextId, error) } + if (returnValue !== undefined) { event.returnValue = returnValue } @@ -453,12 +459,28 @@ const crashReporterInit = function (options) { } } -ipcMain.on('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { +const setReturnValue = function (event, getValue) { try { - event.returnValue = [null, crashReporterInit(options)] + event.returnValue = [null, getValue()] } catch (error) { event.returnValue = [errorUtils.serialize(error)] } +} + +ipcMain.on('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { + setReturnValue(event, () => crashReporterInit(options)) +}) + +ipcMain.on('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { + setReturnValue(event, () => event.sender.getLastWebPreferences()) +}) + +ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_READ_FIND_TEXT', function (event) { + setReturnValue(event, () => electron.clipboard.readFindText()) +}) + +ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', function (event, text) { + setReturnValue(event, () => electron.clipboard.writeFindText(text)) }) ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) { @@ -475,6 +497,7 @@ ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) { event.returnValue = { preloadSrc, preloadError, + isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(), process: { arch: process.arch, platform: process.platform, @@ -484,11 +507,3 @@ ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) { } } }) - -ipcMain.on('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { - try { - event.returnValue = [null, event.sender.getLastWebPreferences()] - } catch (error) { - event.returnValue = [errorUtils.serialize(error)] - } -}) diff --git a/lib/common/api/clipboard.js b/lib/common/api/clipboard.js index 7e3ab510e08b6..e0c9921c15536 100644 --- a/lib/common/api/clipboard.js +++ b/lib/common/api/clipboard.js @@ -2,15 +2,29 @@ if (process.platform === 'linux' && process.type === 'renderer') { // On Linux we could not access clipboard in renderer process. - module.exports = require('electron').remote.clipboard + const { getRemoteForUsage } = require('@electron/internal/renderer/remote') + module.exports = getRemoteForUsage('clipboard').clipboard } else { const clipboard = process.atomBinding('clipboard') // Read/write to find pasteboard over IPC since only main process is notified // of changes if (process.platform === 'darwin' && process.type === 'renderer') { - clipboard.readFindText = require('electron').remote.clipboard.readFindText - clipboard.writeFindText = require('electron').remote.clipboard.writeFindText + const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') + const errorUtils = require('@electron/internal/common/error-utils') + + const invoke = function (command, ...args) { + const [ error, result ] = ipcRenderer.sendSync(command, ...args) + + if (error) { + throw errorUtils.deserialize(error) + } else { + return result + } + } + + clipboard.readFindText = (...args) => invoke('ELECTRON_BROWSER_CLIPBOARD_READ_FIND_TEXT', ...args) + clipboard.writeFindText = (...args) => invoke('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', ...args) } module.exports = clipboard diff --git a/lib/renderer/api/module-list.js b/lib/renderer/api/module-list.js index bc2c8b2d2b6cd..b9bba8a0eb394 100644 --- a/lib/renderer/api/module-list.js +++ b/lib/renderer/api/module-list.js @@ -1,6 +1,9 @@ 'use strict' const features = process.atomBinding('features') +const v8Util = process.atomBinding('v8_util') + +const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule') // Renderer side modules, please sort alphabetically. // A module is `enabled` if there is no explicit condition defined. @@ -11,8 +14,8 @@ module.exports = [ file: 'desktop-capturer', enabled: features.isDesktopCapturerEnabled() }, - { name: 'ipcRenderer', file: 'ipc-renderer', enabled: true }, - { name: 'remote', file: 'remote', enabled: true }, - { name: 'screen', file: 'screen', enabled: true }, - { name: 'webFrame', file: 'web-frame', enabled: true } + { name: 'ipcRenderer', file: 'ipc-renderer' }, + { name: 'remote', file: 'remote', enabled: enableRemoteModule }, + { name: 'screen', file: 'screen' }, + { name: 'webFrame', file: 'web-frame' } ] diff --git a/lib/renderer/api/screen.js b/lib/renderer/api/screen.js index bc1c33e34b568..6cae10d3bb041 100644 --- a/lib/renderer/api/screen.js +++ b/lib/renderer/api/screen.js @@ -1,3 +1,4 @@ 'use strict' -module.exports = require('electron').remote.screen +const { getRemoteForUsage } = require('@electron/internal/renderer/remote') +module.exports = getRemoteForUsage('screen').screen diff --git a/lib/renderer/remote.js b/lib/renderer/remote.js new file mode 100644 index 0000000000000..b807723f9178e --- /dev/null +++ b/lib/renderer/remote.js @@ -0,0 +1,10 @@ +'use strict' + +const { remote } = require('electron') + +exports.getRemoteForUsage = function (usage) { + if (!remote) { + throw new Error(`${usage} requires remote, which is not enabled`) + } + return remote +} diff --git a/lib/renderer/web-view/web-view-attributes.js b/lib/renderer/web-view/web-view-attributes.js index 9ed48f406a53e..7a7c84e382869 100644 --- a/lib/renderer/web-view/web-view-attributes.js +++ b/lib/renderer/web-view/web-view-attributes.js @@ -248,6 +248,20 @@ class WebPreferencesAttribute extends WebViewAttribute { } } +class EnableRemoteModuleAttribute extends WebViewAttribute { + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE, webViewImpl) + } + + getValue () { + return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false' + } + + setValue (value) { + this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false') + } +} + // Sets up all of the webview attributes. WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes = {} @@ -259,6 +273,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () { this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) + this.attributes[webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this) diff --git a/lib/renderer/web-view/web-view-constants.js b/lib/renderer/web-view/web-view-constants.js index 86268f15780d9..459aafaca28b1 100644 --- a/lib/renderer/web-view/web-view-constants.js +++ b/lib/renderer/web-view/web-view-constants.js @@ -7,6 +7,7 @@ module.exports = { ATTRIBUTE_SRC: 'src', ATTRIBUTE_HTTPREFERRER: 'httpreferrer', ATTRIBUTE_NODEINTEGRATION: 'nodeintegration', + ATTRIBUTE_ENABLEREMOTEMODULE: 'enableremotemodule', ATTRIBUTE_PLUGINS: 'plugins', ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity', ATTRIBUTE_ALLOWPOPUPS: 'allowpopups', diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index 8bab0685e806c..99c737a1448d0 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -1,6 +1,6 @@ 'use strict' -const { remote, webFrame } = require('electron') +const { webFrame } = require('electron') const v8Util = process.atomBinding('v8_util') const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal') @@ -338,6 +338,8 @@ const registerWebViewElement = function () { // WebContents associated with this webview. proto.getWebContents = function () { + const { getRemoteForUsage } = require('@electron/internal/renderer/remote') + const remote = getRemoteForUsage('getWebContents()') const internal = v8Util.getHiddenValue(this, 'internal') if (!internal.guestInstanceId) { internal.createGuestSync() diff --git a/lib/sandboxed_renderer/api/exports/child_process.js b/lib/sandboxed_renderer/api/exports/child_process.js index d1abecfa25e1a..21dfcee3af899 100644 --- a/lib/sandboxed_renderer/api/exports/child_process.js +++ b/lib/sandboxed_renderer/api/exports/child_process.js @@ -1,3 +1,4 @@ 'use strict' -module.exports = require('electron').remote.require('child_process') +const { getRemoteForUsage } = require('@electron/internal/renderer/remote') +module.exports = getRemoteForUsage('child_process').require('child_process') diff --git a/lib/sandboxed_renderer/api/exports/electron.js b/lib/sandboxed_renderer/api/exports/electron.js index 2992797b72499..4b69175c98bf5 100644 --- a/lib/sandboxed_renderer/api/exports/electron.js +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -6,6 +6,7 @@ for (const { name, load, enabled = true, + configurable = false, private: isPrivate = false } of moduleList) { if (!enabled) { @@ -13,6 +14,7 @@ for (const { } Object.defineProperty(exports, name, { + configurable, enumerable: !isPrivate, get: load }) diff --git a/lib/sandboxed_renderer/api/exports/fs.js b/lib/sandboxed_renderer/api/exports/fs.js index 93c46e0293da7..18afbfcb0f0d7 100644 --- a/lib/sandboxed_renderer/api/exports/fs.js +++ b/lib/sandboxed_renderer/api/exports/fs.js @@ -1,3 +1,4 @@ 'use strict' -module.exports = require('electron').remote.require('fs') +const { getRemoteForUsage } = require('@electron/internal/renderer/remote') +module.exports = getRemoteForUsage('fs').require('fs') diff --git a/lib/sandboxed_renderer/api/exports/os.js b/lib/sandboxed_renderer/api/exports/os.js index bbbbe589235d2..4e2fe34155fe9 100644 --- a/lib/sandboxed_renderer/api/exports/os.js +++ b/lib/sandboxed_renderer/api/exports/os.js @@ -1,3 +1,4 @@ 'use strict' -module.exports = require('electron').remote.require('os') +const { getRemoteForUsage } = require('@electron/internal/renderer/remote') +module.exports = getRemoteForUsage('os').require('os') diff --git a/lib/sandboxed_renderer/api/exports/path.js b/lib/sandboxed_renderer/api/exports/path.js index fbc7b18089063..f7903dc4de981 100644 --- a/lib/sandboxed_renderer/api/exports/path.js +++ b/lib/sandboxed_renderer/api/exports/path.js @@ -1,3 +1,4 @@ 'use strict' -module.exports = require('electron').remote.require('path') +const { getRemoteForUsage } = require('@electron/internal/renderer/remote') +module.exports = getRemoteForUsage('path').require('path') diff --git a/lib/sandboxed_renderer/api/module-list.js b/lib/sandboxed_renderer/api/module-list.js index b2fd9d8a4e848..9831fdfa2c1e3 100644 --- a/lib/sandboxed_renderer/api/module-list.js +++ b/lib/sandboxed_renderer/api/module-list.js @@ -16,21 +16,28 @@ module.exports = [ name: 'ipcRenderer', load: () => require('@electron/internal/renderer/api/ipc-renderer') }, - { - name: 'isPromise', - load: () => require('@electron/internal/common/api/is-promise'), - private: true - }, { name: 'nativeImage', load: () => require('@electron/internal/common/api/native-image') }, { name: 'remote', + configurable: true, // will be configured in init.js load: () => require('@electron/internal/renderer/api/remote') }, { name: 'webFrame', load: () => require('@electron/internal/renderer/api/web-frame') + }, + // The internal modules, invisible unless you know their names. + { + name: 'deprecate', + load: () => require('@electron/internal/common/api/deprecate'), + private: true + }, + { + name: 'isPromise', + load: () => require('@electron/internal/common/api/is-promise'), + private: true } ] diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 46271fadaf354..07216ab5221d7 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -63,9 +63,21 @@ ipcNative.onExit = function () { } const { - preloadSrc, preloadError, process: processProps + preloadSrc, preloadError, isRemoteModuleEnabled, process: processProps } = ipcRenderer.sendSync('ELECTRON_BROWSER_SANDBOX_LOAD') +const makePropertyNonConfigurable = function (object, name) { + const descriptor = Object.getOwnPropertyDescriptor(electron, name) + descriptor.configurable = false + Object.defineProperty(electron, name, descriptor) +} + +if (isRemoteModuleEnabled) { + makePropertyNonConfigurable(electron, 'remote') +} else { + delete electron.remote +} + require('@electron/internal/renderer/web-frame-init')() // Pass different process object to the preload script(which should not have diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 1db8239ec652a..96f0b1b3c05fc 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1396,6 +1396,46 @@ describe('BrowserWindow module', () => { }) }) + describe('"enableRemoteModule" option', () => { + const generateSpecs = (description, sandbox) => { + describe(description, () => { + const preload = path.join(fixtures, 'module', 'preload-remote.js') + + it('enables the remote module by default', async () => { + const w = await openTheWindow({ + show: false, + webPreferences: { + nodeIntegration: false, + preload, + sandbox + } + }) + w.loadFile(path.join(fixtures, 'api', 'blank.html')) + const [, remote] = await emittedOnce(ipcMain, 'remote') + expect(remote).to.equal('object') + }) + + it('disables the remote module when false', async () => { + const w = await openTheWindow({ + show: false, + webPreferences: { + nodeIntegration: false, + preload, + sandbox, + enableRemoteModule: false + } + }) + w.loadFile(path.join(fixtures, 'api', 'blank.html')) + const [, remote] = await emittedOnce(ipcMain, 'remote') + expect(remote).to.equal('undefined') + }) + }) + } + + generateSpecs('without sandbox', false) + generateSpecs('with sandbox', true) + }) + describe('"sandbox" option', () => { function waitForEvents (emitter, events, callback) { let count = events.length diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index e626fe025aef2..a4de55b84f343 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -195,6 +195,11 @@ describe('crashReporter module', () => { preload: path.join(fixtures, 'module', 'preload-sandbox.js') } }) + generateSpecs('with remote module disabled', { + webPreferences: { + enableRemoteModule: false + } + }) describe('getProductName', () => { it('returns the product name if one is specified', () => { diff --git a/spec/fixtures/module/preload-disable-remote.js b/spec/fixtures/module/preload-disable-remote.js new file mode 100644 index 0000000000000..008acec7b5d60 --- /dev/null +++ b/spec/fixtures/module/preload-disable-remote.js @@ -0,0 +1,8 @@ +setImmediate(function () { + try { + const { remote } = require('electron') + console.log(JSON.stringify(typeof remote)) + } catch (e) { + console.log(e.message) + } +}) diff --git a/spec/fixtures/module/preload-remote.js b/spec/fixtures/module/preload-remote.js new file mode 100644 index 0000000000000..a9014c726ff6c --- /dev/null +++ b/spec/fixtures/module/preload-remote.js @@ -0,0 +1,5 @@ +const { ipcRenderer, remote } = require('electron') + +window.onload = function () { + ipcRenderer.send('remote', typeof remote) +} diff --git a/spec/security-warnings-spec.js b/spec/security-warnings-spec.js index 17fcdeea6255b..ac53e0d3beea5 100644 --- a/spec/security-warnings-spec.js +++ b/spec/security-warnings-spec.js @@ -68,7 +68,7 @@ describe('security warnings', () => { w.loadURL(`http://127.0.0.1:8881/base-page-security.html`) }) - const generateSpecs = (description, sandbox) => { + const generateSpecs = (description, webPreferences) => { describe(description, () => { it('should warn about disabled webSecurity', (done) => { w = new BrowserWindow({ @@ -76,7 +76,7 @@ describe('security warnings', () => { webPreferences: { webSecurity: false, nodeIntegration: false, - sandbox + ...webPreferences } }) w.webContents.once('console-message', (e, level, message) => { @@ -92,7 +92,7 @@ describe('security warnings', () => { show: false, webPreferences: { nodeIntegration: false, - sandbox + ...webPreferences } }) @@ -111,7 +111,7 @@ describe('security warnings', () => { webPreferences: { allowRunningInsecureContent: true, nodeIntegration: false, - sandbox + ...webPreferences } }) w.webContents.once('console-message', (e, level, message) => { @@ -128,7 +128,7 @@ describe('security warnings', () => { webPreferences: { experimentalFeatures: true, nodeIntegration: false, - sandbox + ...webPreferences } }) w.webContents.once('console-message', (e, level, message) => { @@ -145,7 +145,7 @@ describe('security warnings', () => { webPreferences: { enableBlinkFeatures: ['my-cool-feature'], nodeIntegration: false, - sandbox + ...webPreferences } }) w.webContents.once('console-message', (e, level, message) => { @@ -161,7 +161,7 @@ describe('security warnings', () => { show: false, webPreferences: { nodeIntegration: false, - sandbox + ...webPreferences } }) w.webContents.once('console-message', (e, level, message) => { @@ -177,7 +177,7 @@ describe('security warnings', () => { show: false, webPreferences: { nodeIntegration: false, - sandbox + ...webPreferences } }) w.webContents.once('console-message', (e, level, message) => { @@ -191,6 +191,7 @@ describe('security warnings', () => { }) } - generateSpecs('without sandbox', false) - generateSpecs('with sandbox', true) + generateSpecs('without sandbox', {}) + generateSpecs('with sandbox', { sandbox: true }) + generateSpecs('with remote module disabled', { enableRemoteModule: false }) }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index c0a0891305891..2271c2cc58475 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -219,6 +219,41 @@ describe(' tag', function () { }) }) + describe('enableremotemodule attribute', () => { + const generateSpecs = (description, sandbox) => { + describe(description, () => { + const preload = `${fixtures}/module/preload-disable-remote.js` + const src = `file://${fixtures}/api/blank.html` + + it('enables the remote module by default', async () => { + const message = await startLoadingWebViewAndWaitForMessage(webview, { + preload, + src, + sandbox + }) + + const typeOfRemote = JSON.parse(message) + expect(typeOfRemote).to.equal('object') + }) + + it('disables the remote module when false', async () => { + const message = await startLoadingWebViewAndWaitForMessage(webview, { + preload, + src, + sandbox, + enableremotemodule: false + }) + + const typeOfRemote = JSON.parse(message) + expect(typeOfRemote).to.equal('undefined') + }) + }) + } + + generateSpecs('without sandbox', false) + generateSpecs('with sandbox', true) + }) + describe('preload attribute', () => { it('loads the script before other scripts in window', async () => { const message = await startLoadingWebViewAndWaitForMessage(webview, { @@ -506,6 +541,17 @@ describe(' tag', function () { }) }) + it('can disable the remote module', async () => { + const message = await startLoadingWebViewAndWaitForMessage(webview, { + preload: `${fixtures}/module/preload-disable-remote.js`, + src: `file://${fixtures}/api/blank.html`, + webpreferences: 'enableRemoteModule=no' + }) + + const typeOfRemote = JSON.parse(message) + expect(typeOfRemote).to.equal('undefined') + }) + it('can disables web security and enable nodeintegration', async () => { const jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') const src = ` `