sec: deprecate some webPreference defaults to be secure-by-default (#14284)

* feat: deprecate default value of nodeIntegration

* Use DeprecationStatus::Stable as the default instead of shadowing

* change wording of deprecations

* chore: also deprecate kWebviewTag and kContextIsolation

* chore: do as we preach, lets be secure-by-default in the default app
This commit is contained in:
Samuel Attard 2018-08-30 06:14:04 +12:00 коммит произвёл Charles Kerr
Родитель 9b2c14a745
Коммит 66d6ba8689
9 изменённых файлов: 100 добавлений и 43 удалений

Просмотреть файл

@ -8,6 +8,7 @@
#include <string>
#include <vector>
#include "atom/browser/api/atom_api_web_contents.h"
#include "atom/browser/native_window.h"
#include "atom/browser/web_view_manager.h"
#include "atom/common/native_mate_converters/value_converter.h"
@ -99,12 +100,14 @@ WebContentsPreferences::WebContentsPreferences(
// Set WebPreferences defaults onto the JS object
SetDefaultBoolIfUndefined(options::kPlugins, false);
SetDefaultBoolIfUndefined(options::kExperimentalFeatures, false);
bool node = SetDefaultBoolIfUndefined(options::kNodeIntegration, true);
bool node = SetDefaultBoolIfUndefined(options::kNodeIntegration, true,
Status::Deprecated);
SetDefaultBoolIfUndefined(options::kNodeIntegrationInWorker, false);
SetDefaultBoolIfUndefined(options::kWebviewTag, node);
SetDefaultBoolIfUndefined(options::kWebviewTag, node, Status::Deprecated);
SetDefaultBoolIfUndefined(options::kSandbox, false);
SetDefaultBoolIfUndefined(options::kNativeWindowOpen, false);
SetDefaultBoolIfUndefined(options::kContextIsolation, false);
SetDefaultBoolIfUndefined(options::kContextIsolation, false,
Status::Deprecated);
SetDefaultBoolIfUndefined("javascript", true);
SetDefaultBoolIfUndefined("images", true);
SetDefaultBoolIfUndefined("textAreasAreResizable", true);
@ -134,15 +137,24 @@ WebContentsPreferences::~WebContentsPreferences() {
bool WebContentsPreferences::SetDefaultBoolIfUndefined(
const base::StringPiece& key,
bool val) {
bool val,
Status status) {
auto* current_value =
preference_.FindKeyOfType(key, base::Value::Type::BOOLEAN);
if (current_value) {
return current_value->GetBool();
} else {
preference_.SetKey(key, base::Value(val));
return val;
if (status == Status::Deprecated && web_contents_) {
auto internal_contents = atom::api::WebContents::CreateFrom(
v8::Isolate::GetCurrent(), web_contents_);
internal_contents->Emit("-deprecated-default",
std::string("webPreferences.") + key.data(),
/* oldDefault */ val, /* newDefault */ !val);
}
}
return val;
}
bool WebContentsPreferences::IsEnabled(const base::StringPiece& name,

Просмотреть файл

@ -66,11 +66,15 @@ class WebContentsPreferences
friend class content::WebContentsUserData<WebContentsPreferences>;
friend class AtomBrowserClient;
enum class Status { Deprecated, Stable };
// Get WebContents according to process ID.
static content::WebContents* GetWebContentsFromProcessID(int process_id);
// Set preference value to given bool if user did not provide value
bool SetDefaultBoolIfUndefined(const base::StringPiece& key, bool val);
bool SetDefaultBoolIfUndefined(const base::StringPiece& key,
bool val,
Status status = Status::Stable);
static std::vector<WebContentsPreferences*> instances_;

Просмотреть файл

@ -16,7 +16,10 @@ exports.load = (appUrl) => {
autoHideMenuBar: true,
backgroundColor: '#FFFFFF',
webPreferences: {
nodeIntegrationInWorker: true
nodeIntegration: false,
webviewTag: false,
contextIsolation: true,
preload: path.resolve(__dirname, 'renderer.js')
},
useContentSize: true,
show: false

Просмотреть файл

@ -83,8 +83,6 @@
</div>
</div>
</nav>
<script src="./renderer.js"></script>
</body>
</html>

Просмотреть файл

@ -4,49 +4,60 @@ const path = require('path')
const URL = require('url')
const electronPath = path.relative(process.cwd(), remote.process.execPath)
Array.from(document.querySelectorAll('a[href]')).forEach(link => {
// safely add `?utm_source=default_app
let url = URL.parse(link.getAttribute('href'), true)
url.query = Object.assign(url.query, {utm_source: 'default_app'})
url = URL.format(url)
function initialize () {
Array.from(document.querySelectorAll('a[href]')).forEach(link => {
// safely add `?utm_source=default_app
let url = URL.parse(link.getAttribute('href'), true)
url.query = Object.assign(url.query, {utm_source: 'default_app'})
url = URL.format(url)
link.addEventListener('click', (e) => {
e.preventDefault()
shell.openExternal(url)
link.addEventListener('click', (e) => {
e.preventDefault()
shell.openExternal(url)
})
})
})
document.querySelector('.electron-version').innerText = `Electron v${process.versions.electron}`
document.querySelector('.chrome-version').innerText = `Chromium v${process.versions.chrome}`
document.querySelector('.node-version').innerText = `Node v${process.versions.node}`
document.querySelector('.v8-version').innerText = `v8 v${process.versions.v8}`
document.querySelector('.command-example').innerText = `${electronPath} path-to-app`
document.querySelector('.electron-version').innerText = `Electron v${process.versions.electron}`
document.querySelector('.chrome-version').innerText = `Chromium v${process.versions.chrome}`
document.querySelector('.node-version').innerText = `Node v${process.versions.node}`
document.querySelector('.v8-version').innerText = `v8 v${process.versions.v8}`
document.querySelector('.command-example').innerText = `${electronPath} path-to-app`
function getOcticonSvg (name) {
const octiconPath = path.resolve(__dirname, 'node_modules', 'octicons', 'build', 'svg', `${name}.svg`)
if (fs.existsSync(octiconPath)) {
const content = fs.readFileSync(octiconPath, 'utf8')
const div = document.createElement('div')
div.innerHTML = content
return div
function getOcticonSvg (name) {
const octiconPath = path.resolve(__dirname, 'node_modules', 'octicons', 'build', 'svg', `${name}.svg`)
if (fs.existsSync(octiconPath)) {
const content = fs.readFileSync(octiconPath, 'utf8')
const div = document.createElement('div')
div.innerHTML = content
return div
}
return null
}
return null
}
function loadSVG (element) {
for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) {
const icon = getOcticonSvg(cssClass.substr(8))
if (icon) {
icon.classList = element.classList
element.parentNode.insertBefore(icon, element)
element.remove()
break
function loadSVG (element) {
for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) {
const icon = getOcticonSvg(cssClass.substr(8))
if (icon) {
icon.classList = element.classList
element.parentNode.insertBefore(icon, element)
element.remove()
break
}
}
}
}
for (const element of document.querySelectorAll('.octicon')) {
loadSVG(element)
}
}
for (const element of document.querySelectorAll('.octicon')) {
loadSVG(element)
function onReadyStateChange () {
if (document.readyState === 'complete') {
document.removeEventListener('readystatechange', onReadyStateChange)
initialize()
}
}
document.addEventListener('readystatechange', onReadyStateChange)

Просмотреть файл

@ -34,6 +34,18 @@ app.releaseSingleInstance()
app.releaseSingleInstanceLock()
```
## `new BrowserWindow({ webPreferences: { ... }})`
```js
// Deprecated defaults
const webPreferences = {}
new BrowserWindow({ webPreferences })
// webPreferences.contextIsolation - Default was false, will be true
// webPreferences.nodeIntegration - Default was true, will be false
// webPreferences.webviewTag - Default was true, will be false
```
# Breaking API Changes (3.0)

Просмотреть файл

@ -293,6 +293,10 @@ WebContents.prototype._init = function () {
ipcMain.emit(channel, event, ...args)
})
this.on('-deprecated-default', function (event, key, oldDefault, newDefault) {
deprecate.warnDefault(key, oldDefault, newDefault)
})
// Handle context menu action request from pepper plugin.
this.on('pepper-context-menu', function (event, params, callback) {
// Access Menu via electron.Menu to prevent circular require.

Просмотреть файл

@ -31,6 +31,12 @@ deprecate.warn = (oldName, newName) => {
return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
}
deprecate.warnDefault = (propName, oldDefault, newDefault) => {
return deprecate.log(`The default value of '${propName}' is changing from \
'${oldDefault}' to '${newDefault}' in a future release. If you want to keep \
the current value, please explicitly declare the property.`)
}
let deprecationHandler = null
// Print deprecation message.

Просмотреть файл

@ -61,6 +61,13 @@ const getIsRemoteProtocol = function () {
* @returns {boolean} Is a CSP with `unsafe-eval` set?
*/
const isUnsafeEvalEnabled = function () {
// FIXME(MarshallOfSound): Although not exactly true, this warning is incorrect
// when contextIsolation is enabled
// FIXME(MarshallOfSound): Once remote issues have gone away we can remove
// the falsey check
const prefs = getWebPreferences()
if (prefs && prefs.contextIsolation) return false
try {
//eslint-disable-next-line
new Function('');