asarfuckery/electronasar/canary/renderer/security-warnings.js

340 lines
11 KiB
JavaScript
Raw Normal View History

2019-06-20 21:19:08 +00:00
'use strict'
let shouldLog = null
2019-06-16 20:19:06 +00:00
/**
* This method checks if a security message should be logged.
* It does so by determining whether we're running as Electron,
* which indicates that a developer is currently looking at the
* app.
*
* @returns {boolean} - Should we log?
*/
const shouldLogSecurityWarnings = function () {
2019-06-20 21:19:08 +00:00
if (shouldLog !== null) {
return shouldLog
}
const { platform, execPath, env } = process
switch (platform) {
case 'darwin':
shouldLog = execPath.endsWith('MacOS/Electron') ||
execPath.includes('Electron.app/Contents/Frameworks/')
break
case 'freebsd':
case 'linux':
shouldLog = execPath.endsWith('/electron')
break
case 'win32':
shouldLog = execPath.endsWith('\\electron.exe')
break
default:
shouldLog = false
}
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
shouldLog = false
}
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
shouldLog = true
}
return shouldLog
}
2019-06-16 20:19:06 +00:00
/**
* Checks if the current window is remote.
*
* @returns {boolean} - Is this a remote protocol?
*/
const getIsRemoteProtocol = function () {
2019-06-20 21:19:08 +00:00
if (window && window.location && window.location.protocol) {
return /^(http|ftp)s?/gi.test(window.location.protocol)
}
}
2019-06-16 20:19:06 +00:00
/**
* Tries to determine whether a CSP without `unsafe-eval` is set.
*
* @returns {boolean} Is a CSP with `unsafe-eval` set?
*/
const isUnsafeEvalEnabled = function () {
2019-06-20 21:19:08 +00:00
const { webFrame } = require('electron')
return new Promise((resolve) => {
webFrame.executeJavaScript(`(${(() => {
try {
new Function('') // eslint-disable-line no-new,no-new-func
} catch (err) {
return false
}
return true
}).toString()})()`, resolve)
})
}
2019-06-16 20:19:06 +00:00
const moreInformation = `\nFor more information and help, consult
https://electronjs.org/docs/tutorial/security.\n This warning will not show up
2019-06-20 21:19:08 +00:00
once the app is packaged.`
2019-06-16 20:19:06 +00:00
/**
* #1 Only load secure content
*
* Checks the loaded resources on the current page and logs a
* message about all resources loaded over HTTP or FTP.
*/
const warnAboutInsecureResources = function () {
2019-06-20 21:19:08 +00:00
if (!window || !window.performance || !window.performance.getEntriesByType) {
return
}
const resources = window.performance
.getEntriesByType('resource')
.filter(({ name }) => /^(http|ftp):/gi.test(name || ''))
.map(({ name }) => `- ${name}`)
.join('\n')
if (!resources || resources.length === 0) {
return
}
const warning = `This renderer process loads resources using insecure
2019-06-16 20:19:06 +00:00
protocols.This exposes users of this app to unnecessary security risks.
Consider loading the following resources over HTTPS or FTPS. \n ${resources}
2019-06-20 21:19:08 +00:00
\n ${moreInformation}`
console.warn('%cElectron Security Warning (Insecure Resources)',
'font-weight: bold;', warning)
}
2019-06-16 20:19:06 +00:00
/**
* #2 on the checklist: Disable the Node.js integration in all renderers that
* display remote content
*
* Logs a warning message about Node integration.
*/
const warnAboutNodeWithRemoteContent = function (nodeIntegration) {
2019-06-20 21:19:08 +00:00
if (!nodeIntegration) return
if (getIsRemoteProtocol()) {
const warning = `This renderer process has Node.js integration enabled
2019-06-16 20:19:06 +00:00
and attempted to load remote content from '${window.location}'. This
2019-06-20 21:19:08 +00:00
exposes users of this app to severe security risks.\n ${moreInformation}`
console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
'font-weight: bold;', warning)
}
}
2019-06-16 20:19:06 +00:00
// Currently missing since it has ramifications and is still experimental:
// #3 Enable context isolation in all renderers that display remote content
//
// Currently missing since we can't easily programmatically check for those cases:
// #4 Use ses.setPermissionRequestHandler() in all sessions that load remote content
2019-06-20 21:19:08 +00:00
2019-06-16 20:19:06 +00:00
/**
* #5 on the checklist: Do not disable websecurity
*
* Logs a warning message about disabled webSecurity.
*/
const warnAboutDisabledWebSecurity = function (webPreferences) {
2019-06-20 21:19:08 +00:00
if (!webPreferences || webPreferences.webSecurity !== false) return
const warning = `This renderer process has "webSecurity" disabled. This
exposes users of this app to severe security risks.\n ${moreInformation}`
console.warn('%cElectron Security Warning (Disabled webSecurity)',
'font-weight: bold;', warning)
}
2019-06-16 20:19:06 +00:00
/**
* #6 on the checklist: Define a Content-Security-Policy and use restrictive
* rules (i.e. script-src 'self')
*
* #7 on the checklist: Disable eval
*
* Logs a warning message about unset or insecure CSP
*/
const warnAboutInsecureCSP = function () {
2019-06-20 21:19:08 +00:00
isUnsafeEvalEnabled().then((enabled) => {
if (!enabled) return
const warning = `This renderer process has either no Content Security
2019-06-16 20:19:06 +00:00
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
2019-06-20 21:19:08 +00:00
this app to unnecessary security risks.\n ${moreInformation}`
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
'font-weight: bold;', warning)
})
}
2019-06-16 20:19:06 +00:00
/**
* #8 on the checklist: Do not set allowRunningInsecureContent to true
*
* Logs a warning message about disabled webSecurity.
*/
const warnAboutInsecureContentAllowed = function (webPreferences) {
2019-06-20 21:19:08 +00:00
if (!webPreferences || !webPreferences.allowRunningInsecureContent) return
const warning = `This renderer process has "allowRunningInsecureContent"
2019-06-16 20:19:06 +00:00
enabled. This exposes users of this app to severe security risks.\n
2019-06-20 21:19:08 +00:00
${moreInformation}`
console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
'font-weight: bold;', warning)
}
2019-06-16 20:19:06 +00:00
/**
* #9 on the checklist: Do not enable experimental features
*
* Logs a warning message about experimental features.
*/
const warnAboutExperimentalFeatures = function (webPreferences) {
2019-06-20 21:19:08 +00:00
if (!webPreferences || (!webPreferences.experimentalFeatures)) {
return
}
const warning = `This renderer process has "experimentalFeatures" enabled.
2019-06-16 20:19:06 +00:00
This exposes users of this app to some security risk. If you do not need
2019-06-20 21:19:08 +00:00
this feature, you should disable it.\n ${moreInformation}`
console.warn('%cElectron Security Warning (experimentalFeatures)',
'font-weight: bold;', warning)
}
2019-06-16 20:19:06 +00:00
/**
* #10 on the checklist: Do not use enableBlinkFeatures
*
* Logs a warning message about enableBlinkFeatures
*/
const warnAboutEnableBlinkFeatures = function (webPreferences) {
2019-06-20 21:19:08 +00:00
if (webPreferences === null ||
!webPreferences.hasOwnProperty('enableBlinkFeatures') ||
webPreferences.enableBlinkFeatures.length === 0) {
return
}
const warning = `This renderer process has additional "enableBlinkFeatures"
2019-06-16 20:19:06 +00:00
enabled. This exposes users of this app to some security risk. If you do not
2019-06-20 21:19:08 +00:00
need this feature, you should disable it.\n ${moreInformation}`
console.warn('%cElectron Security Warning (enableBlinkFeatures)',
'font-weight: bold;', warning)
}
2019-06-16 20:19:06 +00:00
/**
* #11 on the checklist: Do Not Use allowpopups
*
* Logs a warning message about allowed popups
*/
const warnAboutAllowedPopups = function () {
2019-06-20 21:19:08 +00:00
if (document && document.querySelectorAll) {
const domElements = document.querySelectorAll('[allowpopups]')
if (!domElements || domElements.length === 0) {
return
}
const warning = `A <webview> has "allowpopups" set to true. This exposes
2019-06-16 20:19:06 +00:00
users of this app to some security risk, since popups are just
BrowserWindows. If you do not need this feature, you should disable it.\n
2019-06-20 21:19:08 +00:00
${moreInformation}`
console.warn('%cElectron Security Warning (allowpopups)',
'font-weight: bold;', warning)
}
}
const warnAboutNodeIntegrationDefault = function (webPreferences) {
if (webPreferences && webPreferences.nodeIntegration && !webPreferences.nodeIntegrationWasExplicitlyEnabled) {
const warning = `This window has node integration enabled by default. In ` +
`Electron 5.0.0, node integration will be disabled by default. To prepare ` +
`for this change, set {nodeIntegration: true} in the webPreferences for ` +
`this window, or ensure that this window does not rely on node integration ` +
`and set {nodeIntegration: false}.`
console.warn('%cElectron Deprecation Warning (nodeIntegration default change)', 'font-weight: bold;', warning)
}
}
const warnAboutContextIsolationDefault = function (webPreferences) {
if (webPreferences && webPreferences.preload && !webPreferences.contextIsolation && !webPreferences.contextIsolationWasExplicitlyDisabled) {
const url = 'https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content'
const warning = `This window has context isolation disabled by default. In ` +
`Electron 5.0.0, context isolation will be enabled by default. To prepare ` +
`for this change, set {contextIsolation: false} in the webPreferences for ` +
`this window, or ensure that this window does not rely on context ` +
`isolation being disabled, and set {contextIsolation: true}.\n\n` +
`For more information, see ${url}`
console.warn('%cElectron Deprecation Warning (contextIsolation default change)', 'font-weight: bold;', warning)
}
}
const warnAboutDeprecatedWebviewTagDefault = function (webPreferences) {
if (!webPreferences) {
return
}
if (webPreferences.webviewTagWasExplicitlyEnabled) {
return
}
if (!document || !document.getElementsByTagName) {
return
}
const webviews = document.getElementsByTagName('webview')
if (webviews && webviews.length > 0) {
const url = 'https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#new-browserwindow-webpreferences-'
const warning = `This window has the <webview> tag enabled by default. In ` +
`Electron 5.0.0, <webview> tags will be disabled by default. To prepare ` +
`for this change, set {webviewTag: true} in the webPreferences for ` +
`this window.\n\n` +
`For more information, see ${url}`
console.warn('%cElectron Deprecation Warning (webviewTag default change)',
'font-weight: bold;', warning)
}
}
2019-06-16 20:19:06 +00:00
// Currently missing since we can't easily programmatically check for it:
// #12WebViews: Verify the options and params of all `<webview>` tags
2019-06-20 21:19:08 +00:00
2019-06-16 20:19:06 +00:00
const logSecurityWarnings = function (webPreferences, nodeIntegration) {
2019-06-20 21:19:08 +00:00
warnAboutNodeWithRemoteContent(nodeIntegration)
warnAboutDisabledWebSecurity(webPreferences)
warnAboutInsecureResources()
warnAboutInsecureContentAllowed(webPreferences)
warnAboutExperimentalFeatures(webPreferences)
warnAboutEnableBlinkFeatures(webPreferences)
warnAboutInsecureCSP()
warnAboutAllowedPopups()
warnAboutNodeIntegrationDefault(webPreferences)
warnAboutContextIsolationDefault(webPreferences)
warnAboutDeprecatedWebviewTagDefault(webPreferences)
}
2019-06-16 20:19:06 +00:00
const getWebPreferences = function () {
2019-06-20 21:19:08 +00:00
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const errorUtils = require('@electron/internal/common/error-utils')
const [ error, result ] = ipcRenderer.sendSync('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
if (error) {
console.warn(`getLastWebPreferences() failed: ${errorUtils.deserialize(error)}`)
return null
} else {
return result
}
}
module.exports = function (nodeIntegration) {
const loadHandler = function () {
if (shouldLogSecurityWarnings()) {
const webPreferences = getWebPreferences()
logSecurityWarnings(webPreferences, nodeIntegration)
2019-06-16 20:19:06 +00:00
}
2019-06-20 21:19:08 +00:00
}
window.addEventListener('load', loadHandler, { once: true })
}