diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index f23f49f5ab13a..fae459703b7d4 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -355,6 +355,9 @@ WebContents::WebContents(v8::Isolate* isolate, // Whether to enable DevTools. options.Get("devTools", &enable_devtools_); + // Whether to disable remote. + options.Get("disableRemote", &disable_remote_); + // Obtain the session. std::string partition; mate::Handle session; @@ -1880,6 +1883,10 @@ v8::Local WebContents::GetLastWebPreferences( return mate::ConvertToV8(isolate, *web_preferences->last_preference()); } +bool WebContents::IsRemoteDisabled() const { + return disable_remote_; +} + v8::Local WebContents::GetOwnerBrowserWindow() const { if (owner_window()) return BrowserWindow::From(isolate(), owner_window()); @@ -2030,6 +2037,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("_getPreloadPath", &WebContents::GetPreloadPath) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getLastWebPreferences", &WebContents::GetLastWebPreferences) + .SetMethod("_isRemoteDisabled", &WebContents::IsRemoteDisabled) .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 fe19a5b249679..51b85ef2274dd 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -237,6 +237,8 @@ class WebContents : public mate::TrackableObject, v8::Local GetWebPreferences(v8::Isolate* isolate) const; v8::Local GetLastWebPreferences(v8::Isolate* isolate) const; + bool IsRemoteDisabled() const; + // Returns the owner window. v8::Local GetOwnerBrowserWindow() const; @@ -467,6 +469,9 @@ class WebContents : public mate::TrackableObject, // Whether to enable devtools. bool enable_devtools_ = true; + // Whether to disable remote. + bool disable_remote_ = false; + // Observers of this WebContents. base::ObserverList observers_; diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index c21c933831550..c83ae0e4fc785 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -265,6 +265,10 @@ void WebContentsPreferences::AppendCommandLineSwitches( } } + // Disable the remote module + if (IsEnabled(options::kDisableRemote)) + command_line->AppendSwitch(switches::kDisableRemote); + // Run Electron APIs and preload script in isolated world if (IsEnabled(options::kContextIsolation)) command_line->AppendSwitch(switches::kContextIsolation); diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 8b1836f80c583..e6d7f2ee1e3c4 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"; +// Disable the remote module +const char kDisableRemote[] = "disableRemote"; + // 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 kDisableRemote[] = "disable-remote"; 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 5f6d7e9d38120..959cbb85243c6 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 kDisableRemote[]; 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 kDisableRemote[]; 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 69490420ef5b8..17aecac6b906a 100644 --- a/atom/renderer/renderer_client_base.cc +++ b/atom/renderer/renderer_client_base.cc @@ -78,6 +78,15 @@ std::vector ParseSchemesCLISwitch(const char* switch_name) { 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() { @@ -99,10 +108,12 @@ void RendererClientBase::DidCreateScriptContext( std::string context_id = base::StringPrintf( "%" CrPRIdPid "-%d", base::GetCurrentProcId(), ++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(); + if (command_line->HasSwitch(switches::kDisableRemote)) { + SetHiddenValue(context, "disableRemote", mate::ConvertToV8(isolate, true)); + } } void RendererClientBase::AddRenderBindings( diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 1595e2c8635a6..da7594df59427 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -270,6 +270,7 @@ 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. + * `disableRemote` Boolean (optional) - If set, this will disable the [`remote`](remote.md) module. * `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/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index f8cfecf5c6243..be393de2f49ea 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -1,8 +1,11 @@ 'use strict' +const {spawn} = require('child_process') const electron = require('electron') const {EventEmitter} = require('events') const fs = require('fs') +const os = require('os') +const path = require('path') const v8Util = process.atomBinding('v8_util') const {ipcMain, isPromise} = electron @@ -260,10 +263,14 @@ const callFunction = function (event, contextId, func, caller, args) { const handleRemoteCommand = function (channel, handler) { ipcMain.on(channel, (event, contextId, ...args) => { let returnValue - try { - returnValue = handler(event, contextId, ...args) - } catch (error) { - returnValue = exceptionToMeta(event.sender, contextId, error) + if (event.sender._isRemoteDisabled()) { + returnValue = exceptionToMeta(event.sender, contextId, new Error('remote is disabled')) + } else { + try { + returnValue = handler(event, contextId, ...args) + } catch (error) { + returnValue = exceptionToMeta(event.sender, contextId, error) + } } if (returnValue !== undefined) { event.returnValue = returnValue @@ -396,8 +403,40 @@ ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { event.returnValue = null }) +const getTempDirectory = function () { + try { + return electron.app.getPath('temp') + } catch (error) { + return os.tmpdir() + } +} + +ipcMain.on('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { + let crashesDirectory = path.join(getTempDirectory(), `${options.productName} Crashes`) + + if (process.platform === 'win32') { + const env = { + ELECTRON_INTERNAL_CRASH_SERVICE: 1 + } + const args = [ + '--reporter-url=' + options.submitURL, + '--application-name=' + options.productName, + '--crashes-directory=' + crashesDirectory, + '--v=1' + ] + + spawn(process.helperExecPath, args, { + env: env, + detached: true + }) + } + + event.returnValue = crashesDirectory +}) + ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) { const preloadPath = event.sender._getPreloadPath() + const isRemoteDisabled = event.sender._isRemoteDisabled() let preloadSrc = null let preloadError = null if (preloadPath) { @@ -408,8 +447,9 @@ ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) { } } event.returnValue = { - preloadSrc: preloadSrc, - preloadError: preloadError, + preloadSrc, + preloadError, + isRemoteDisabled, process: { arch: process.arch, platform: process.platform, diff --git a/lib/common/api/clipboard.js b/lib/common/api/clipboard.js index b52524d759986..3742cea1a222a 100644 --- a/lib/common/api/clipboard.js +++ b/lib/common/api/clipboard.js @@ -1,14 +1,20 @@ +const {remote} = require('electron') + +if (!remote) { + throw new Error('clipboard requires remote, which is disabled') +} + if (process.platform === 'linux' && process.type === 'renderer') { // On Linux we could not access clipboard in renderer process. - module.exports = require('electron').remote.clipboard + module.exports = remote.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 + clipboard.readFindText = remote.clipboard.readFindText + clipboard.writeFindText = remote.clipboard.writeFindText } module.exports = clipboard diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index 1090d425e4c5c..de42b135ca874 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -1,18 +1,18 @@ 'use strict' -const {spawn} = require('child_process') -const os = require('os') -const path = require('path') const electron = require('electron') -const {app} = process.type === 'browser' ? electron : electron.remote +const {app} = process.type === 'browser' ? electron : electron.remote || {} const binding = process.atomBinding('crash_reporter') +const getAppName = () => app && app.getName() +const getAppVersion = () => app && app.getVersion() + class CrashReporter { start (options) { if (options == null) options = {} - this.productName = options.productName != null ? options.productName : app.getName() let { + productName, companyName, extra, ignoreSystemCrashHandler, @@ -20,6 +20,10 @@ class CrashReporter { uploadToServer } = options + if (productName == null) { + productName = getAppName() + } + if (uploadToServer == null) { uploadToServer = true } @@ -27,10 +31,13 @@ class CrashReporter { if (ignoreSystemCrashHandler == null) ignoreSystemCrashHandler = false if (extra == null) extra = {} - if (extra._productName == null) extra._productName = this.getProductName() + if (extra._productName == null) extra._productName = productName if (extra._companyName == null) extra._companyName = companyName - if (extra._version == null) extra._version = app.getVersion() + if (extra._version == null) extra._version = getAppVersion() + if (productName == null) { + throw new Error('productName is a required option to crashReporter.start') + } if (companyName == null) { throw new Error('companyName is a required option to crashReporter.start') } @@ -38,24 +45,12 @@ class CrashReporter { throw new Error('submitURL is a required option to crashReporter.start') } - if (process.platform === 'win32') { - const env = { - ELECTRON_INTERNAL_CRASH_SERVICE: 1 - } - const args = [ - '--reporter-url=' + submitURL, - '--application-name=' + this.getProductName(), - '--crashes-directory=' + this.getCrashesDirectory(), - '--v=1' - ] - - this._crashServiceProcess = spawn(process.execPath, args, { - env: env, - detached: true - }) - } + this.crashesDirectory = electron.ipcRenderer.sendSync('ELECTRON_CRASH_REPORTER_INIT', { + submitURL, + productName + }) - binding.start(this.getProductName(), companyName, submitURL, this.getCrashesDirectory(), uploadToServer, ignoreSystemCrashHandler, extra) + binding.start(productName, companyName, submitURL, this.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra) } getLastCrashReport () { @@ -70,30 +65,7 @@ class CrashReporter { } getUploadedReports () { - return binding.getUploadedReports(this.getCrashesDirectory()) - } - - getCrashesDirectory () { - const crashesDir = `${this.getProductName()} Crashes` - return path.join(this.getTempDirectory(), crashesDir) - } - - getProductName () { - if (this.productName == null) { - this.productName = app.getName() - } - return this.productName - } - - getTempDirectory () { - if (this.tempDirectory == null) { - try { - this.tempDirectory = app.getPath('temp') - } catch (error) { - this.tempDirectory = os.tmpdir() - } - } - return this.tempDirectory + return binding.getUploadedReports(this.crashesDirectory) } getUploadToServer () { diff --git a/lib/renderer/api/module-list.js b/lib/renderer/api/module-list.js index 0c1e74fc5dc68..c267697501e09 100644 --- a/lib/renderer/api/module-list.js +++ b/lib/renderer/api/module-list.js @@ -1,4 +1,5 @@ const features = process.atomBinding('features') +const v8Util = process.atomBinding('v8_util') // Renderer side modules, please sort alphabetically. // A module is `enabled` if there is no explicit condition defined. @@ -9,7 +10,11 @@ module.exports = [ enabled: features.isDesktopCapturerEnabled() }, {name: 'ipcRenderer', file: 'ipc-renderer', enabled: true}, - {name: 'remote', file: 'remote', enabled: true}, + { + name: 'remote', + file: 'remote', + enabled: v8Util.getHiddenValue(global, 'disableRemote') !== true + }, {name: 'screen', file: 'screen', enabled: true}, {name: 'webFrame', file: 'web-frame', enabled: true} ] diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 1f974e946acb4..387f0c0e3c2fd 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -269,8 +269,6 @@ function metaToPlainObject (meta) { // Construct an exception error from the meta. function metaToException (meta) { const error = new Error(`${meta.message}\n${meta.stack}`) - const remoteProcess = exports.process - error.from = remoteProcess ? remoteProcess.type : null error.cause = metaToValue(meta.cause) return error } diff --git a/lib/renderer/api/screen.js b/lib/renderer/api/screen.js index 9eecd49dc5bf2..5c2ab257673ec 100644 --- a/lib/renderer/api/screen.js +++ b/lib/renderer/api/screen.js @@ -1 +1,7 @@ -module.exports = require('electron').remote.screen +const {remote} = require('electron') + +if (!remote) { + throw new Error('screen requires remote, which is disabled') +} + +module.exports = remote.screen diff --git a/lib/sandboxed_renderer/api/exports/child_process.js b/lib/sandboxed_renderer/api/exports/child_process.js index ff39e96a120ec..98a77b97b5742 100644 --- a/lib/sandboxed_renderer/api/exports/child_process.js +++ b/lib/sandboxed_renderer/api/exports/child_process.js @@ -1 +1 @@ -module.exports = require('electron').remote.require('child_process') +module.exports = require('../remote-require')('child_process') diff --git a/lib/sandboxed_renderer/api/exports/electron.js b/lib/sandboxed_renderer/api/exports/electron.js index 3a2615152a65d..1bbf04cea1733 100644 --- a/lib/sandboxed_renderer/api/exports/electron.js +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -7,6 +7,7 @@ Object.defineProperties(exports, { }, remote: { enumerable: true, + configurable: true, // will be configured in init.js get: function () { return require('../../../renderer/api/remote') } diff --git a/lib/sandboxed_renderer/api/exports/fs.js b/lib/sandboxed_renderer/api/exports/fs.js index 7342908e59a93..e0dab9ef385ae 100644 --- a/lib/sandboxed_renderer/api/exports/fs.js +++ b/lib/sandboxed_renderer/api/exports/fs.js @@ -1 +1 @@ -module.exports = require('electron').remote.require('fs') +module.exports = require('../remote-require')('fs') diff --git a/lib/sandboxed_renderer/api/exports/os.js b/lib/sandboxed_renderer/api/exports/os.js index ecd0d38a63a6b..b1e3c25c109a0 100644 --- a/lib/sandboxed_renderer/api/exports/os.js +++ b/lib/sandboxed_renderer/api/exports/os.js @@ -1 +1 @@ -module.exports = require('electron').remote.require('os') +module.exports = require('../remote-require')('os') diff --git a/lib/sandboxed_renderer/api/exports/path.js b/lib/sandboxed_renderer/api/exports/path.js index f2b2f2a77fd4d..eb56f4bb35d3c 100644 --- a/lib/sandboxed_renderer/api/exports/path.js +++ b/lib/sandboxed_renderer/api/exports/path.js @@ -1 +1 @@ -module.exports = require('electron').remote.require('path') +module.exports = require('../remote-require')('path') diff --git a/lib/sandboxed_renderer/api/remote-require.js b/lib/sandboxed_renderer/api/remote-require.js new file mode 100644 index 0000000000000..edafea9274713 --- /dev/null +++ b/lib/sandboxed_renderer/api/remote-require.js @@ -0,0 +1,8 @@ +const {remote} = require('electron') +module.exports = function (name) { + if (remote) { + return remote.require(name) + } else { + throw new Error(`${name} module is not available`) + } +} diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 30a3fa55216f6..ba4cc729434d8 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -36,9 +36,17 @@ const loadedModules = new Map([ ]) const { - preloadSrc, preloadError, process: processProps + preloadSrc, preloadError, isRemoteDisabled, process: processProps } = electron.ipcRenderer.sendSync('ELECTRON_BROWSER_SANDBOX_LOAD') +if (isRemoteDisabled) { + delete electron.remote +} else { + let descriptor = Object.getOwnPropertyDescriptor(electron, 'remote') + descriptor.configurable = false + Object.defineProperty(electron, 'remote', descriptor) +} + require('../renderer/web-frame-init')() // Pass different process object to the preload script(which should not have