security-warnings.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. 'use strict'
  2. let shouldLog = null
  3. /**
  4. * This method checks if a security message should be logged.
  5. * It does so by determining whether we're running as Electron,
  6. * which indicates that a developer is currently looking at the
  7. * app.
  8. *
  9. * @returns {boolean} - Should we log?
  10. */
  11. const shouldLogSecurityWarnings = function () {
  12. if (shouldLog !== null) {
  13. return shouldLog
  14. }
  15. const { platform, execPath, env } = process
  16. switch (platform) {
  17. case 'darwin':
  18. shouldLog = execPath.endsWith('MacOS/Electron') ||
  19. execPath.includes('Electron.app/Contents/Frameworks/')
  20. break
  21. case 'freebsd':
  22. case 'linux':
  23. shouldLog = execPath.endsWith('/electron')
  24. break
  25. case 'win32':
  26. shouldLog = execPath.endsWith('\\electron.exe')
  27. break
  28. default:
  29. shouldLog = false
  30. }
  31. if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
  32. (window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
  33. shouldLog = false
  34. }
  35. if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
  36. (window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
  37. shouldLog = true
  38. }
  39. return shouldLog
  40. }
  41. /**
  42. * Checks if the current window is remote.
  43. *
  44. * @returns {boolean} - Is this a remote protocol?
  45. */
  46. const getIsRemoteProtocol = function () {
  47. if (window && window.location && window.location.protocol) {
  48. return /^(http|ftp)s?/gi.test(window.location.protocol)
  49. }
  50. }
  51. /**
  52. * Tries to determine whether a CSP without `unsafe-eval` is set.
  53. *
  54. * @returns {boolean} Is a CSP with `unsafe-eval` set?
  55. */
  56. const isUnsafeEvalEnabled = function () {
  57. const { webFrame } = require('electron')
  58. return new Promise((resolve) => {
  59. webFrame.executeJavaScript(`(${(() => {
  60. try {
  61. new Function('') // eslint-disable-line no-new,no-new-func
  62. } catch (err) {
  63. return false
  64. }
  65. return true
  66. }).toString()})()`, resolve)
  67. })
  68. }
  69. const moreInformation = `\nFor more information and help, consult
  70. https://electronjs.org/docs/tutorial/security.\n This warning will not show up
  71. once the app is packaged.`
  72. /**
  73. * #1 Only load secure content
  74. *
  75. * Checks the loaded resources on the current page and logs a
  76. * message about all resources loaded over HTTP or FTP.
  77. */
  78. const warnAboutInsecureResources = function () {
  79. if (!window || !window.performance || !window.performance.getEntriesByType) {
  80. return
  81. }
  82. const resources = window.performance
  83. .getEntriesByType('resource')
  84. .filter(({ name }) => /^(http|ftp):/gi.test(name || ''))
  85. .map(({ name }) => `- ${name}`)
  86. .join('\n')
  87. if (!resources || resources.length === 0) {
  88. return
  89. }
  90. const warning = `This renderer process loads resources using insecure
  91. protocols.This exposes users of this app to unnecessary security risks.
  92. Consider loading the following resources over HTTPS or FTPS. \n ${resources}
  93. \n ${moreInformation}`
  94. console.warn('%cElectron Security Warning (Insecure Resources)',
  95. 'font-weight: bold;', warning)
  96. }
  97. /**
  98. * #2 on the checklist: Disable the Node.js integration in all renderers that
  99. * display remote content
  100. *
  101. * Logs a warning message about Node integration.
  102. */
  103. const warnAboutNodeWithRemoteContent = function (nodeIntegration) {
  104. if (!nodeIntegration) return
  105. if (getIsRemoteProtocol()) {
  106. const warning = `This renderer process has Node.js integration enabled
  107. and attempted to load remote content from '${window.location}'. This
  108. exposes users of this app to severe security risks.\n ${moreInformation}`
  109. console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
  110. 'font-weight: bold;', warning)
  111. }
  112. }
  113. // Currently missing since it has ramifications and is still experimental:
  114. // #3 Enable context isolation in all renderers that display remote content
  115. //
  116. // Currently missing since we can't easily programmatically check for those cases:
  117. // #4 Use ses.setPermissionRequestHandler() in all sessions that load remote content
  118. /**
  119. * #5 on the checklist: Do not disable websecurity
  120. *
  121. * Logs a warning message about disabled webSecurity.
  122. */
  123. const warnAboutDisabledWebSecurity = function (webPreferences) {
  124. if (!webPreferences || webPreferences.webSecurity !== false) return
  125. const warning = `This renderer process has "webSecurity" disabled. This
  126. exposes users of this app to severe security risks.\n ${moreInformation}`
  127. console.warn('%cElectron Security Warning (Disabled webSecurity)',
  128. 'font-weight: bold;', warning)
  129. }
  130. /**
  131. * #6 on the checklist: Define a Content-Security-Policy and use restrictive
  132. * rules (i.e. script-src 'self')
  133. *
  134. * #7 on the checklist: Disable eval
  135. *
  136. * Logs a warning message about unset or insecure CSP
  137. */
  138. const warnAboutInsecureCSP = function () {
  139. isUnsafeEvalEnabled().then((enabled) => {
  140. if (!enabled) return
  141. const warning = `This renderer process has either no Content Security
  142. Policy set or a policy with "unsafe-eval" enabled. This exposes users of
  143. this app to unnecessary security risks.\n ${moreInformation}`
  144. console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
  145. 'font-weight: bold;', warning)
  146. })
  147. }
  148. /**
  149. * #8 on the checklist: Do not set allowRunningInsecureContent to true
  150. *
  151. * Logs a warning message about disabled webSecurity.
  152. */
  153. const warnAboutInsecureContentAllowed = function (webPreferences) {
  154. if (!webPreferences || !webPreferences.allowRunningInsecureContent) return
  155. const warning = `This renderer process has "allowRunningInsecureContent"
  156. enabled. This exposes users of this app to severe security risks.\n
  157. ${moreInformation}`
  158. console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
  159. 'font-weight: bold;', warning)
  160. }
  161. /**
  162. * #9 on the checklist: Do not enable experimental features
  163. *
  164. * Logs a warning message about experimental features.
  165. */
  166. const warnAboutExperimentalFeatures = function (webPreferences) {
  167. if (!webPreferences || (!webPreferences.experimentalFeatures)) {
  168. return
  169. }
  170. const warning = `This renderer process has "experimentalFeatures" enabled.
  171. This exposes users of this app to some security risk. If you do not need
  172. this feature, you should disable it.\n ${moreInformation}`
  173. console.warn('%cElectron Security Warning (experimentalFeatures)',
  174. 'font-weight: bold;', warning)
  175. }
  176. /**
  177. * #10 on the checklist: Do not use enableBlinkFeatures
  178. *
  179. * Logs a warning message about enableBlinkFeatures
  180. */
  181. const warnAboutEnableBlinkFeatures = function (webPreferences) {
  182. if (webPreferences === null ||
  183. !webPreferences.hasOwnProperty('enableBlinkFeatures') ||
  184. webPreferences.enableBlinkFeatures.length === 0) {
  185. return
  186. }
  187. const warning = `This renderer process has additional "enableBlinkFeatures"
  188. enabled. This exposes users of this app to some security risk. If you do not
  189. need this feature, you should disable it.\n ${moreInformation}`
  190. console.warn('%cElectron Security Warning (enableBlinkFeatures)',
  191. 'font-weight: bold;', warning)
  192. }
  193. /**
  194. * #11 on the checklist: Do Not Use allowpopups
  195. *
  196. * Logs a warning message about allowed popups
  197. */
  198. const warnAboutAllowedPopups = function () {
  199. if (document && document.querySelectorAll) {
  200. const domElements = document.querySelectorAll('[allowpopups]')
  201. if (!domElements || domElements.length === 0) {
  202. return
  203. }
  204. const warning = `A <webview> has "allowpopups" set to true. This exposes
  205. users of this app to some security risk, since popups are just
  206. BrowserWindows. If you do not need this feature, you should disable it.\n
  207. ${moreInformation}`
  208. console.warn('%cElectron Security Warning (allowpopups)',
  209. 'font-weight: bold;', warning)
  210. }
  211. }
  212. const warnAboutNodeIntegrationDefault = function (webPreferences) {
  213. if (webPreferences && webPreferences.nodeIntegration && !webPreferences.nodeIntegrationWasExplicitlyEnabled) {
  214. const warning = `This window has node integration enabled by default. In ` +
  215. `Electron 5.0.0, node integration will be disabled by default. To prepare ` +
  216. `for this change, set {nodeIntegration: true} in the webPreferences for ` +
  217. `this window, or ensure that this window does not rely on node integration ` +
  218. `and set {nodeIntegration: false}.`
  219. console.warn('%cElectron Deprecation Warning (nodeIntegration default change)', 'font-weight: bold;', warning)
  220. }
  221. }
  222. const warnAboutContextIsolationDefault = function (webPreferences) {
  223. if (webPreferences && webPreferences.preload && !webPreferences.contextIsolation && !webPreferences.contextIsolationWasExplicitlyDisabled) {
  224. const url = 'https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content'
  225. const warning = `This window has context isolation disabled by default. In ` +
  226. `Electron 5.0.0, context isolation will be enabled by default. To prepare ` +
  227. `for this change, set {contextIsolation: false} in the webPreferences for ` +
  228. `this window, or ensure that this window does not rely on context ` +
  229. `isolation being disabled, and set {contextIsolation: true}.\n\n` +
  230. `For more information, see ${url}`
  231. console.warn('%cElectron Deprecation Warning (contextIsolation default change)', 'font-weight: bold;', warning)
  232. }
  233. }
  234. const warnAboutDeprecatedWebviewTagDefault = function (webPreferences) {
  235. if (!webPreferences) {
  236. return
  237. }
  238. if (webPreferences.webviewTagWasExplicitlyEnabled) {
  239. return
  240. }
  241. if (!document || !document.getElementsByTagName) {
  242. return
  243. }
  244. const webviews = document.getElementsByTagName('webview')
  245. if (webviews && webviews.length > 0) {
  246. const url = 'https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#new-browserwindow-webpreferences-'
  247. const warning = `This window has the <webview> tag enabled by default. In ` +
  248. `Electron 5.0.0, <webview> tags will be disabled by default. To prepare ` +
  249. `for this change, set {webviewTag: true} in the webPreferences for ` +
  250. `this window.\n\n` +
  251. `For more information, see ${url}`
  252. console.warn('%cElectron Deprecation Warning (webviewTag default change)',
  253. 'font-weight: bold;', warning)
  254. }
  255. }
  256. // Currently missing since we can't easily programmatically check for it:
  257. // #12WebViews: Verify the options and params of all `<webview>` tags
  258. const logSecurityWarnings = function (webPreferences, nodeIntegration) {
  259. warnAboutNodeWithRemoteContent(nodeIntegration)
  260. warnAboutDisabledWebSecurity(webPreferences)
  261. warnAboutInsecureResources()
  262. warnAboutInsecureContentAllowed(webPreferences)
  263. warnAboutExperimentalFeatures(webPreferences)
  264. warnAboutEnableBlinkFeatures(webPreferences)
  265. warnAboutInsecureCSP()
  266. warnAboutAllowedPopups()
  267. warnAboutNodeIntegrationDefault(webPreferences)
  268. warnAboutContextIsolationDefault(webPreferences)
  269. warnAboutDeprecatedWebviewTagDefault(webPreferences)
  270. }
  271. const getWebPreferences = function () {
  272. const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
  273. const errorUtils = require('@electron/internal/common/error-utils')
  274. const [ error, result ] = ipcRenderer.sendSync('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
  275. if (error) {
  276. console.warn(`getLastWebPreferences() failed: ${errorUtils.deserialize(error)}`)
  277. return null
  278. } else {
  279. return result
  280. }
  281. }
  282. module.exports = function (nodeIntegration) {
  283. const loadHandler = function () {
  284. if (shouldLogSecurityWarnings()) {
  285. const webPreferences = getWebPreferences()
  286. logSecurityWarnings(webPreferences, nodeIntegration)
  287. }
  288. }
  289. window.addEventListener('load', loadHandler, { once: true })
  290. }