diff --git a/ui/desktop/electron-app/src/handlers.js b/ui/desktop/electron-app/src/handlers.js index 2b6b1030a..6a251d742 100644 --- a/ui/desktop/electron-app/src/handlers.js +++ b/ui/desktop/electron-app/src/handlers.js @@ -26,10 +26,19 @@ handle('resetOrigin', async (requestOrigin) => { }); /** - * Opens the specified URL in an external browser. + * Opens the specified URL in an external browser. Only HTTPS URLs are allowed. */ handle('openExternal', async (href) => { - shell.openExternal(href); + if (href.startsWith('https://')) { + // openExternal is necessary in order to display documentation and to + // support arbitrary OIDC flows. The protocol is validated. + shell.openExternal(href); /* eng-disable OPEN_EXTERNAL_JS_CHECK */ + } else { + throw new Error( + `URLs may only be opened over HTTPS in an external browser. + The URL '${href}' could not be opened.` + ); + } }); /** diff --git a/ui/desktop/electron-app/src/index.js b/ui/desktop/electron-app/src/index.js index bbe22a1d8..4ed11956a 100644 --- a/ui/desktop/electron-app/src/index.js +++ b/ui/desktop/electron-app/src/index.js @@ -55,7 +55,9 @@ app.on('ready', async () => { const ses = session.fromPartition(partition); // Register custom protocol - ses.protocol.registerFileProtocol(emberAppProtocol, (request, callback) => { + // This file protocol is the exclusive protocol for this application. All + // other protocols are disabled. + ses.protocol.registerFileProtocol(emberAppProtocol, (request, callback) => { /* eng-disable PROTOCOL_HANDLER_JS_CHECK */ const isDir = request.url.endsWith('/'); const absolutePath = request.url.substr(emberAppURL.length); const normalizedPath = isDir @@ -67,17 +69,16 @@ app.on('ready', async () => { callback({ path: normalizedPath }); }); - // Disallow all permissions requests originating outside of serve://boundary, + // Disallow all permissions requests, // per Electronegativity PERMISSION_REQUEST_HANDLER_GLOBAL_CHECK - ses.setPermissionRequestHandler((webContents, permission, callback) => { - if (webContents.getURL().startsWith('serve://boundary')) - return callback(false); - return callback(true); + ses.setPermissionRequestHandler((webContents, permission, callback) => { /* eng-disable PERMISSION_REQUEST_HANDLER_JS_CHECK */ + return callback(false); }); // Setup content security policy ses.webRequest.onHeadersReceived((details, callback) => { callback({ + // See `content-security-policy.js` to learn how the CSP is generated. responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': [generateCSPHeader()], @@ -116,7 +117,9 @@ app.on('ready', async () => { nodeIntegration: false, enableRemoteModule: false, allowRunningInsecureContent: false, - preload: preloadPath, + // The preload script establishes the message-based IPC pathway without + // exposing new modules to the renderer. + preload: preloadPath, /* eng-disable PRELOAD_JS_CHECK */ disableBlinkFeatures: 'Auxclick', }, }); @@ -148,11 +151,10 @@ app.on('ready', async () => { // Prevent navigation outside of serve://boundary per // Electronegativity LIMIT_NAVIGATION_GLOBAL_CHECK - mainWindow.webContents.on('will-navigate', (event, url) => { + mainWindow.webContents.on('will-navigate', (event, url) => { /* eng-disable LIMIT_NAVIGATION_JS_CHECK */ if (!url.startsWith('serve://boundary')) event.preventDefault(); }); - - mainWindow.webContents.on('new-window', (event, url) => { + mainWindow.webContents.on('new-window', (event, url) => { /* eng-disable LIMIT_NAVIGATION_JS_CHECK */ if (!url.startsWith('serve://boundary')) event.preventDefault(); }); diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 21aa5f5e1..7efe8ade6 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -37,7 +37,7 @@ "posttest": "ember coverage-merge && yarn clean:coverage" }, "devDependencies": { - "@doyensec/electronegativity": "^1.7.0", + "@doyensec/electronegativity": "^1.8.1", "@ember/jquery": "^1.1.0", "@ember/optional-features": "^2.0.0", "@ember/render-modifiers": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 472e735d2..b56c1fba6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1264,10 +1264,10 @@ resolved "https://registry.yarnpkg.com/@doyensec/csp-evaluator/-/csp-evaluator-1.0.3.tgz#0d0405cbfb5bb814c24b8a73736a9700da2c681f" integrity sha512-Nz2QxdGiYh7nHgRU4R5/v6NDGW+IYv7j7dyQgpjtl4aJzrwHAdoZ0yxz7/YO8bgIOyDJJlF7jbUqjYpPClk9ZQ== -"@doyensec/electronegativity@^1.7.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@doyensec/electronegativity/-/electronegativity-1.8.0.tgz#b71f4836318010e3583a0c347d2660ef1e6bc6ef" - integrity sha512-TDil3r1l6mSZMXI8JsWLqY/B77PhoJQ9DeS4PoosL44R+uO+ZoclSg7HBFzGANU9SJ+19X2uwd4R1ufYEQJVfg== +"@doyensec/electronegativity@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@doyensec/electronegativity/-/electronegativity-1.8.1.tgz#401561cb822afb978d0ee89a97e64746a2a34680" + integrity sha512-3J1seE0TzGOUBhxopixy/DX88uvf1ho+D6NfF4w7VU/fhIK9zIWcHyrLco0nmrUlL0cOxul1dhx5c6ySI4JyQw== dependencies: "@babel/parser" "^7.1.2" "@babel/traverse" "^7.3.4"