security-warnings.ts 8.8 KB

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