From dcc477acbc0735c04cb555c708b916a18e115d5a Mon Sep 17 00:00:00 2001 From: antelle Date: Sun, 11 Apr 2021 11:44:51 +0200 Subject: [PATCH] tightened content security policy to prevent browser extension from adding scripts to KeeWeb --- app/index.html | 2 +- app/scripts/plugins/plugin.js | 40 +++++++++++++++++---------------- build/tasks/grunt-csp-hashes.js | 4 ++-- release-notes.md | 1 + 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/index.html b/app/index.html index f556a310..a20ffa13 100644 --- a/app/index.html +++ b/app/index.html @@ -8,7 +8,7 @@ content=" default-src 'self'; font-src data:; - script-src 'self' blob: 'unsafe-eval'; + script-src 'self' 'unsafe-eval'; style-src 'self' blob:; connect-src 'self' ws: https:; child-src 'self' blob:; diff --git a/app/scripts/plugins/plugin.js b/app/scripts/plugins/plugin.js index 71ec2f28..9787e4a8 100644 --- a/app/scripts/plugins/plugin.js +++ b/app/scripts/plugins/plugin.js @@ -373,25 +373,27 @@ class Plugin extends Model { }; text = `(function(require, module){${text}})(window["${jsVar}"].require,window["${jsVar}"].module);`; const ts = this.logger.ts(); - const blob = new Blob([text], { type: 'text/javascript' }); - const objectUrl = URL.createObjectURL(blob); - const elId = 'plugin-js-' + name; - const el = this.createElementInHead('script', elId, { - src: objectUrl - }); - el.addEventListener('load', () => { - URL.revokeObjectURL(objectUrl); - setTimeout(() => { - delete global[jsVar]; - if (this.module.exports.uninstall) { - this.logger.debug('Plugin script installed', this.logger.ts(ts)); - this.loadPluginSettings(); - resolve(); - } else { - reject('Plugin script installation failed'); - } - }, 0); - }); + + // Note that here we're calling eval to run the plugin code, + // previously it was loaded as 'blob:' scheme (see the code below), however: + // 1. we need to have eval enabled in our CSP anyway for WASM, + // see https://github.com/WebAssembly/content-security-policy/issues/7 + // 2. we would like to prevent Chrome extensions from injecting scripts to our page, + // which is possible to do if we have 'blob:', but they can't call eval + // Previous implementation with 'blob:' can be found in git, if we ever need to restore it. + + // eslint-disable-next-line no-eval + eval(text); + setTimeout(() => { + delete global[jsVar]; + if (this.module.exports.uninstall) { + this.logger.debug('Plugin script installed', this.logger.ts(ts)); + this.loadPluginSettings(); + resolve(); + } else { + reject('Plugin script installation failed'); + } + }, 0); } catch (e) { this.logger.error('Error installing plugin script', e); reject(e); diff --git a/build/tasks/grunt-csp-hashes.js b/build/tasks/grunt-csp-hashes.js index 1e05982d..fa49889d 100644 --- a/build/tasks/grunt-csp-hashes.js +++ b/build/tasks/grunt-csp-hashes.js @@ -41,12 +41,12 @@ module.exports = function (grunt) { let htmlStr = html.toString('latin1'); for (const [type, digests] of Object.entries(hashes)) { - const cspIndex = htmlStr.indexOf(`${type}-src`); + const cspIndex = htmlStr.indexOf(`${type}-src 'self'`); if (cspIndex < 0) { grunt.warn(`Not found: ${type}-src`); } const digestsList = digests.map((digest) => `'${algo}-${digest}'`).join(' '); - htmlStr = htmlStr.replace(`${type}-src`, `${type}-src ${digestsList}`); + htmlStr = htmlStr.replace(`${type}-src 'self'`, `${type}-src ${digestsList}`); } grunt.log.writeln( diff --git a/release-notes.md b/release-notes.md index 2cc5f6dd..9c633fe0 100644 --- a/release-notes.md +++ b/release-notes.md @@ -8,6 +8,7 @@ Release notes `+` better Touch ID error messages `-` legacy auto-type removed `-` fixed a crash after disabling USB devices on Linux +`+` tightened content security policy ##### v1.17.6 (2021-04-09) `+` team drives support in Google Drive