首页
/ Electron 应用安全加固完全指南:基于官方 Security 清单的 20 条防护实践

Electron 应用安全加固完全指南:基于官方 Security 清单的 20 条防护实践

2026-09-07 14:12:12作者:裘晴惠Vivianne

本文基于 Electron 官方仓库 docs/tutorial/security.md 的安全文档展开,面向想要构建可安全上线的桌面应用开发者。文章以官方推荐的 20 条安全检查清单为骨架,结合仓库内渲染进程安全告警实现 lib/renderer/security-warnings.ts、WebPreferences 默认值解析 shell/browser/web_contents_preferences.cc 等源码证据,帮助你理解每一项加固措施"为什么必须做、怎么做、不做的后果是什么",最终交付一个不把用户机器暴露在远程代码执行(RCE)风险之下的 Electron 应用。

从 Web 到桌面:理解 Electron 的"权力与责任"

作为 Web 开发者,我们习惯了浏览器提供的安全网:网页代码运行在沙箱中,权限极其有限,而且背后有一个庞大团队维护、能快速响应新威胁的浏览器。但在 Electron 中情况完全不同——Electron 不是 Web 浏览器。它允许你用熟悉的 Web 技术构建功能丰富的桌面应用,代价是你的 JavaScript 拥有了远超网页的能力:可以访问文件系统、用户 Shell 等本地资源。这让你能构建高质量的原生应用,但固有安全风险也随之放大

因此必须牢记:展示来自不可信来源的任意内容,是一种 Electron 并不打算替你兜底的严重安全风险。事实上最流行的 Electron 应用(Atom、Slack、Visual Studio Code 等)主要展示的是本地内容,或关闭 Node 集成的可信远程内容——如果你的应用要执行来自在线来源的代码,确保代码无恶意是你自己的责任。

安全漏洞上报:如何正确上报 Electron 漏洞,参见 SECURITY.md;上游 Chromium 漏洞方面,Electron 始终跟随交替的 Chromium 版本,可参考 Electron 发布节奏

安全总则:三条人人有责的基线

你应用的最终安全水平 = Chromium 的安全性 × Node.js 的安全性 × Electron 自身的安全性 × 所有 NPM 依赖的安全性 × 你自己的代码质量。因此需要遵循三条重要实践:

  1. 让应用始终跟随最新的 Electron 框架版本。 发布产品时,你同时发布了一个由 Electron、Chromium 共享库和 Node.js 组成的捆绑体。任何组件的漏洞都可能波及你的应用。升级到最新版,意味着类似 nodeIntegration 绕过 这类严重漏洞已被修补、无法在你的应用中被利用。详见下文第 16 条

  2. 审查你的依赖。 NPM 提供海量可复用包,但选择可信的第三方库是你的责任。使用存在已知漏洞的过期库、或依赖维护不善的代码,会让应用安全亮红灯。

  3. 采用安全的编码实践。 应用的第一道防线是你自己的代码。像跨站脚本(XSS)这类常见 Web 漏洞在 Electron 应用中危害更大,因此强烈建议遵循安全开发最佳实践并开展安全测试。

不可信内容的隔离:安全心智模型

无论何时,只要你的应用"从不可信来源(如远程服务器)接收代码并在本地执行",就存在安全问题。例如:在默认 BrowserWindow 中展示一个远程网站,一旦攻击者篡改了该内容(无论是直接攻击源站,还是"中间人"拦截在你的应用与目标之间),他们就能在用户机器上执行原生代码

⚠️ 绝对禁止在启用 Node.js 集成的情况下加载并执行远程代码。执行 Node.js 代码只应使用本地文件(随应用一起打包)。展示远程内容请使用 <webview> 标签或 WebContentsView,并务必关闭 nodeIntegration、开启 contextIsolation

开发期的"安全哨兵":Electron 安全告警

Electron 会在开发者控制台打印安全告警与建议。这些告警只在二进制名仍是 Electron 时显示,代表开发者此刻正盯着控制台。这一点在源码中得到印证:渲染进程的告警模块 lib/renderer/security-warnings.ts 会按平台判断可执行文件路径是否仍以 electron/Electron.app 命名,从而决定是否输出告警。

你可以强制开关这些告警,只需在 process.envwindow 对象上设置:

  • ELECTRON_ENABLE_SECURITY_WARNINGS:强制开启;
  • ELECTRON_DISABLE_SECURITY_WARNINGS:强制关闭。
process.env.ELECTRON_ENABLE_SECURITY_WARNINGS = 'true'
// 或 window.ELECTRON_ENABLE_SECURITY_WARNINGS = 'true'

security-warnings.ts 中可以看到这些开关的优先级实现,以及它逐条实现的检查逻辑。有意思的是,它甚至能静态探测页面中的风险项:比如通过 performance.getEntriesByType('resource') 收集所有用 HTTP/FTP 加载的不安全资源并逐一列出(对应清单第 1 条);探测 <webview allowpopups>(对应第 11 条);通过 webFrame._isEvalAllowed() 判断页面是否设了包含 unsafe-eval 的 CSP(对应第 7 条)。开发期善用这些告警,等于让 Electron 帮你做一轮自动化安全体检。

安全检查清单:20 条加固实践

按官方文档要求,至少应完成以下 20 步以提升应用安全性。每一条都给出 Why(风险原理)与 How(落地代码)。

1. 只加载安全内容

应用中所有非随包资源都应使用安全协议加载,如 HTTPS。不要用不安全的 HTTP。同理推荐用 WSS 而非 WS、用 FTPS 而非 FTP

为什么? HTTPS 有两个核心收益:

  1. 数据完整性:保证数据在应用与主机之间传输时未被篡改;
  2. 传输加密:加密用户与目标主机之间的流量,让窃听更难。

怎么做?

// 错误
browserWindow.loadURL('http://example.com')

// 正确
browserWindow.loadURL('https://example.com')
<!-- 错误 -->
<script crossorigin src="http://example.com/react.js"></script>
<link rel="stylesheet" href="http://example.com/style.css">

<!-- 正确 -->
<script crossorigin src="https://example.com/react.js"></script>
<link rel="stylesheet" href="https://example.com/style.css">

补充:源码中判断"不安全资源"时对 localhost/127.0.0.1/[::1] 做了豁免(见 security-warnings.ts),即本地开发服务器走 HTTP 不会触发告警,但生产环境必须全量 HTTPS

2. 不要对远程内容启用 Node.js 集成

自 Electron 5.0.0 起,这已是默认行为。

在任何加载远程内容的渲染器(BrowserWindowWebContentsView<webview>)中,绝不要开启 Node.js 集成。目标是把授予远程内容的权力降到最低——即便攻击者获得了在页面上执行 JavaScript 的能力,想伤害用户也难得多。

为什么? 跨站脚本(XSS)攻击如果能让攻击者跳出渲染进程、在用户电脑上执行代码,就会升级为远程代码执行(RCE)。XSS 本身很常见但危害通常局限于所在网站;关闭 Node.js 集成正是防止 XSS 升级为 RCE 的关键。

怎么做?

// 错误
const mainWindow = new BrowserWindow({
  webPreferences: {
    contextIsolation: false,
    nodeIntegration: true,
    nodeIntegrationInWorker: true
  }
})
mainWindow.loadURL('https://example.com')

// 正确
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(app.getAppPath(), 'preload.js')
  }
})
mainWindow.loadURL('https://example.com')
<!-- 错误 -->
<webview nodeIntegration src="page.html"></webview>

<!-- 正确 -->
<webview src="page.html"></webview>

关闭 Node.js 集成并不妨碍你向网站暴露自定义 API:预加载脚本仍可访问 require 等 Node 特性,开发者可以用 contextBridge API 向远程加载的内容暴露受控 API。

3. 在所有渲染进程中开启 Context Isolation(上下文隔离)

自 Electron 12.0.0 起,这已是默认行为。

上下文隔离允许开发者让预加载脚本与 Electron API 运行在独立的 JavaScript 上下文中。实践意义是:渲染进程里的脚本无法篡改 Array.prototype.pushJSON.parse 这类全局对象。Electron 复用了 Chromium Content Scripts 的同一套技术。

即使设置了 nodeIntegration: false,也必须同时开启 contextIsolation 才能真正强制隔离、阻止 Node 原语被使用。

另外要警惕:对某个渲染进程设置 nodeIntegration: true(即关闭上下文隔离)同时也会关闭该进程的进程沙箱——参见下一节。

想深入了解 contextIsolation 是什么、如何开启,参见专门文档 Context Isolation

4. 开启进程沙箱

自 Electron 20.0.0 起,这已是默认行为;还可通过"全局启用沙箱"对整个应用的所有渲染进程强制开启,参见 Process Sandboxing 文档再次强调:关闭上下文隔离(见上)会连带关闭进程沙箱——无论默认值、sandbox: false 还是全局沙箱都是如此!

沙箱是 Chromium 特性:借助操作系统大幅限制渲染进程能访问的内容。所有渲染器都应开启沙箱。切勿在非沙箱进程(包括主进程)中加载、读取或处理任何不可信内容

从源码可以印证沙箱与 Node 集成的绑定关系:在 web_contents_preferences.cc 中,如果没有显式设置 sandbox,则默认取 sandboxed = !(nodeIntegration || nodeIntegrationInWorker)——即只要开启了 Node 集成,进程就自动非沙箱化。这就是为什么官方文档反复强调"不要为了图省事把 nodeIntegration 打开"。

沙箱开启后渲染进程只拥有 CPU 与内存权限,需要文件系统等特权操作时必须通过 IPC 委托给主进程。关于沙箱行为与沙箱化预加载脚本可用模块的细节,参见 Process Sandboxing

怎么做? 保持默认即可:

const mainWindow = new BrowserWindow({}) // 沙箱默认开启

5. 用 ses.setPermissionRequestHandler() 处理所有会话的权限请求

你在 Chrome 中见过权限请求——网站想使用需要用户手动批准的特性(如通知)时弹出。该 API 基于 Chromium permissions API,实现相同类型的权限。

为什么? 默认情况下,除非开发者手动配置自定义处理器,Electron 会自动批准所有权限请求。这虽然是稳健的默认值,但安全意识强的开发者应该假设完全相反的情形。

怎么做?app.whenReady 之后配置(注意:session.defaultSession 只有在 app.whenReady 被调用后才可用):

const { session } = require('electron')
const { URL } = require('node:url')

session
  .defaultSession
  .setPermissionRequestHandler((webContents, permission, callback) => {
    const parsedUrl = new URL(webContents.getURL())

    if (permission === 'notifications') {
      // 批准该权限请求
      callback(true)
    }

    // 校验 URL
    if (parsedUrl.protocol !== 'https:' || parsedUrl.host !== 'example.com') {
      // 拒绝该权限请求
      return callback(false)
    }
  })

从源码看,该处理器最终被桥接到 ElectronPermissionManagershell/browser/electron_permission_manager.cc),并经由 electron_api_session.cc 暴露为 session 的方法,权限决策会统一收敛到主进程这一入口。

6. 不要关闭 webSecurity

这是 Electron 的默认值。

为什么? 关闭 webSecurity禁用同源策略并把 allowRunningInsecureContent 置为 true——等于允许跨域执行不安全代码。

怎么做?

// 错误
const mainWindow = new BrowserWindow({
  webPreferences: {
    webSecurity: false
  }
})

// 正确
const mainWindow = new BrowserWindow()
<!-- 错误 -->
<webview disablewebsecurity src="page.html"></webview>

<!-- 正确 -->
<webview src="page.html"></webview>

开发期若在控制台看到 Electron Security Warning (Disabled webSecurity),说明有渲染器显式把 webSecurity 设成了 false,参见 security-warnings.ts

7. 定义内容安全策略(Content-Security-Policy)

CSP 是抵御 XSS 与数据注入攻击的额外一层防护。建议 Electron 内加载的任何网站都启用。

为什么? CSP 允许内容服务器限制并控制 Electron 能为该网页加载哪些资源:https://example.com 只应从你定义的源加载脚本,而 https://evil.attacker.com 的脚本不应运行。

怎么做? 下面这条 CSP 允许 Electron 从当前网站与 apis.example.com 执行脚本:

// 错误
Content-Security-Policy: '*'

// 正确
Content-Security-Policy: script-src 'self' https://apis.example.com

通过 HTTP 响应头下发 CSP

Electron 尊重 Content-Security-Policy HTTP 响应头,可用 Electron 的 webRequest.onHeadersReceived 处理器设置(同样需在 app.whenReady 之后):

const { session } = require('electron')

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  callback({
    responseHeaders: {
      ...details.responseHeaders,
      'Content-Security-Policy': ['default-src \'none\'']
    }
  })
})

通过 meta 标签下发 CSP

CSP 的首选传递机制是 HTTP 头。但使用 file:// 协议加载资源时无法用响应头,这时可以在页面标记里直接用 <meta> 设置策略:

<meta http-equiv="Content-Security-Policy" content="default-src 'none'">

补充:渲染进程告警逻辑会检查页面是否设置了不含 unsafe-eval 的 CSP——若未设置 CSP 或策略带 unsafe-eval,控制台会出现 Electron Security Warning (Insecure Content-Security-Policy),实现见 security-warnings.ts

8. 不要启用 allowRunningInsecureContent

这是 Electron 的默认值。

默认情况下,Electron 不允许 HTTPS 加载的网页再从不安全源(HTTP加载并执行脚本、CSS 或插件。把 allowRunningInsecureContent 设为 true 会关闭这层保护。HTTPS 加载首页 HTML、再用 HTTP 加载其余资源,即所谓"混合内容"(mixed content)

为什么? HTTPS 加载保证资源真实性与完整性并加密流量(见第 1 条)。

怎么做?

// 错误
const mainWindow = new BrowserWindow({
  webPreferences: {
    allowRunningInsecureContent: true
  }
})

// 正确
const mainWindow = new BrowserWindow({})

9. 不要启用实验性特性

这是 Electron 的默认值。

高级用户可用 experimentalFeatures 属性开启 Chromium 实验性特性。

为什么? 实验性特性顾名思义尚未面向所有 Chromium 用户启用,它们对 Electron 整体的影响大概率未经测试。存在合理的应用场景,但除非你清楚自己在做什么,否则不应开启。

怎么做?

// 错误
const mainWindow = new BrowserWindow({
  webPreferences: {
    experimentalFeatures: true
  }
})

// 正确
const mainWindow = new BrowserWindow({})

10. 不要使用 enableBlinkFeatures

这是 Electron 的默认值。

Blink 是 Chromium 背后的渲染引擎。与 experimentalFeatures 一样,enableBlinkFeatures 允许开发者开启默认被禁用(或启用)的特定功能。

为什么? 一般而言,某个功能默认没开多半有充分理由。合理用途确实存在,但作为开发者你必须确切知道为何需要、后果是什么、如何影响应用安全——绝不要投机性地开启特性

怎么做?

// 错误
const mainWindow = new BrowserWindow({
  webPreferences: {
    enableBlinkFeatures: 'ExecCommandInJavaScript'
  }
})

// 正确
const mainWindow = new BrowserWindow()

11. WebView 不要使用 allowpopups

这是 Electron 的默认值。

如果你用 <webview>,其内部加载的页面/脚本可能需要打开新窗口。allowpopups 属性允许它们用 window.open() 创建新的 BrowserWindow;没有该属性,<webview> 默认不允许创建新窗口。

为什么? 遵循最小权限原则:不需要弹窗就别默认允许创建新窗口。

怎么做?

<!-- 错误 -->
<webview allowpopups src="page.html"></webview>

<!-- 正确 -->
<webview src="page.html"></webview>

渲染进程侧,告警逻辑会通过 document.querySelectorAll('[allowpopups]') 静态扫描 DOM,发现带 allowpopups<webview> 即打印告警(security-warnings.ts)。

12. 创建 WebView 前校验其选项与参数

在没有 Node.js 集成的渲染进程中创建的 WebView,自身无法再开启集成;但 WebView 总会创建一个带独立 webPreferences 的渲染进程。因此最好从主进程控制新 <webview> 标签的创建,并校验其 webPreferences 没有关闭安全特性。

为什么? 由于 <webview> 活在 DOM 里,即使 Node.js 集成被关闭,网站脚本也可以创建它。Electron 允许开发者关闭各种安全特性,但大多数情况下并不需要——你不应允许新建的 <webview> 使用不同的配置。

怎么做?<webview> 标签被 attach 之前,Electron 会在宿主 webContents 上触发 will-attach-webview 事件。利用它阻止可能不安全的选项:

app.on('web-contents-created', (event, contents) => {
  contents.on('will-attach-webview', (event, webPreferences, params) => {
    // 若不需要 preload 脚本则移除,或校验其位置是否合法
    delete webPreferences.preload

    // 关闭 Node.js 集成
    webPreferences.nodeIntegration = false

    // 校验加载的 URL
    if (!params.src.startsWith('https://example.com/')) {
      event.preventDefault()
    }
  })
})

再次提醒:这份清单只是降低风险而非消除风险。如果你的目标是展示某个网站,浏览器才是更安全的选择。

13. 禁用或限制导航

如果应用不需要导航、或只需导航到已知页面,最好把导航整体限制在该已知范围内,拒绝任何其他导航。

为什么? 导航是常见攻击向量。攻击者若能诱导你的应用离开当前页面,就可能强制它打开互联网上的任意网站。即便你的 webContents 已配置得更安全(如关闭 nodeIntegration、开启 contextIsolation),让应用打开随机网站仍会大幅降低攻击难度。常见手法是通过链接、插件或其他用户生成内容诱使用户与应用交互、使应用导航到攻击者页面。

怎么做? 无需导航时,可在 will-navigate 处理器中调用 event.preventDefault();若知道可能导航到哪些页面,则在处理器里校验 URL、只放行预期地址。

务必用 Node 的 URL 解析器做校验——简单的字符串比较容易被骗:startsWith('https://example.com') 会放行 https://example.com.attacker.com

const { app } = require('electron')
const { URL } = require('node:url')

app.on('web-contents-created', (event, contents) => {
  contents.on('will-navigate', (event, navigationUrl) => {
    const parsedUrl = new URL(navigationUrl)

    if (parsedUrl.origin !== 'https://example.com') {
      event.preventDefault()
    }
  })
})

14. 禁用或限制新窗口的创建

如果应用有明确的窗口集合,最好限制运行时额外窗口的创建。

为什么? 与导航类似,webContents 的创建是常见攻击向量——攻击者诱使应用创建比原进程拥有更多权限的新窗口、新 frame 或其他渲染进程。对于"只开一个 BrowserWindow、运行时不需要任意数量额外窗口"的应用,禁用创建是零成本的安全增益。

怎么做? webContents 在创建新窗口前会委托给其 window open handler,处理器会收到请求打开的 url 与创建选项等参数。注册处理器监控窗口创建并拒绝任何意外的窗口创建

const { app, shell } = require('electron')

app.on('web-contents-created', (event, contents) => {
  contents.setWindowOpenHandler(({ url }) => {
    // 本例请求操作系统在默认浏览器中打开该 url。
    // 哪些 url 可以放行给 shell.openExternal,参见下一节的考量。
    if (isSafeForExternalOpen(url)) {
      setImmediate(() => {
        shell.openExternal(url)
      })
    }

    return { action: 'deny' }
  })
})

相关 API 绑定位于 lib/browser/api/web-contents.tsWebContents.prototype.setWindowOpenHandler

15. 不要对不可信内容使用 shell.openExternal

shell 模块的 openExternal API 允许用桌面原生工具打开给定协议 URI。例如 macOS 上该函数类似 open 终端命令,会按 URI 与文件类型关联打开特定应用。

为什么? 滥用 openExternal 可能危及用户主机——当它与不可信内容一起使用时,可能被利用来执行任意命令

怎么做?

// 错误
const { shell } = require('electron')
shell.openExternal(USER_CONTROLLED_DATA_HERE)

// 正确
const { shell } = require('electron')
shell.openExternal('https://example.com/index.html')

16. 始终使用当前版本的 Electron

应尽量使用 Electron 的最新可用版本。每当新的大版本发布,尽快升级你的应用。

为什么? 用旧版 Electron、Chromium 与 Node.js 构建的应用比新版更容易成为攻击目标——旧版本的安全问题与利用方式传播更广。Chromium 与 Node.js 都是大量顶尖工程师的杰作,其安全性也经受着同等顶尖安全研究者的测试分析。多数研究者遵循负责任披露,即在公开前给官方留出修复时间。因此,运行较新版本(从而自带较新的 Chromium 与 Node.js)的应用,潜在安全问题没那么广为人知,也就更安全。

怎么做? 逐大版本迁移,同时对照 Electron 的 Breaking Changes 文档确认是否有需要更新的代码。

17. 校验所有 IPC 消息的 sender

默认应对所有 IPC 消息校验 sender,确保你没有对不可信渲染器执行操作或发送信息。

为什么? 理论上所有 Web Frame 都能向主进程发送 IPC 消息——某些场景下包括 iframe 和子窗口。如果你的 IPC 消息通过 event.reply 回传用户数据、或执行渲染器本身无法完成的特权操作,就必须确保没有在监听第三方 Web Frame。

怎么做?

// 错误
ipcMain.handle('get-secrets', () => {
  return getSecrets()
})

// 正确
ipcMain.handle('get-secrets', (e) => {
  if (!validateSender(e.senderFrame)) return null
  return getSecrets()
})

function validateSender (frame) {
  // 用真正的 URL 解析器 + 白名单校验 URL 的 host
  if ((new URL(frame.url)).host === 'electronjs.org') return true
  return false
}

注意这里校验的是 e.senderFrame.url 而非笼统的 sender——senderFrame 能精确到发起消息的 frame,可有效挡住 iframe 冒充。白名单判断同样要基于真正的 URL 解析器。

18. 避免 file:// 协议,优先使用自定义协议

本地页面应通过自定义协议提供,而不是 file://

为什么? file:// 在 Electron 中获得的特权比在 Web 浏览器中更多,即便在浏览器里它也与 http/https URL 处理方式不同。运行在 file:// 的页面拥有访问机器上每个文件的单边权限——这意味着 XSS 可被用来加载用户机器上的任意文件。使用自定义协议则能规避这类问题:你可以把协议限制为只服务特定文件集合,并保留对"何时能加载什么"的更强控制,行为更接近经典 Web URL。

怎么做? 参考 protocol.handle 的示例学习如何用自定义协议服务文件/内容。

延伸阅读:file:// 额外特权本质上是可裁剪的。Electron 提供了 grantFileProtocolExtraPrivileges fuse 来关闭这些超出传统浏览器的特权(详见 fuses.md)——它控制 file:// 页面能否用 fetch 加载其他 file:// 资产、能否使用 Service Worker 等。若你不从 file:// 服务页面,官方建议直接禁用该 fuse。

19. 检查可以变更哪些 fuses

Electron 自带一些"可能有用但大部分应用用不到"的选项。为避免为裁剪功能而自行编译维护一个 Electron fork,这些选项可以通过 Fuses打包时开/关

为什么? 某些 fuse(如 runAsNodenodeCliInspect)允许应用在被特定环境变量或 CLI 参数启动时表现出不同行为,可能被用来通过你的应用在设备上执行命令——让外部脚本运行它们原本无权、但你的应用可能有权执行的操作。

怎么做? @electron/fuses 就是为方便翻转 fuse 而制作的模块。用法与潜在错误场景参见该模块 README,以及我们的文档 How do I flip fuses?

仓库中 docs/tutorial/fuses.md 完整列出了当前全部 fuse 及其默认值,例如:

Fuse 默认 说明
runAsNode 启用 是否尊重 ELECTRON_RUN_AS_NODE 环境变量;关闭可防御一类 "living off the land" 攻击
cookieEncryption 禁用 是否用 OS 级密钥加密磁盘上的 cookie 存储(注意为单向迁移)
nodeOptions 启用 是否尊重 NODE_OPTIONS / NODE_EXTRA_CA_CERTS
nodeCliInspect 启用 是否尊重 --inspect 等调试参数及 SIGUSR1 信号
embeddedAsarIntegrityValidation 禁用 加载 app.asar 时校验其内容(macOS/Windows)
onlyLoadAppFromAsar 禁用 只允许从 app.asar 加载应用代码,配合上者可杜绝加载未校验代码
grantFileProtocolExtraPrivileges 启用 是否给 file:// 页面超出传统浏览器的特权

推荐的安全基线:大多数应用可安全禁用 runAsNodenodeOptionsnodeCliInspect,并启用 embeddedAsarIntegrityValidationonlyLoadAppFromAsarcookieEncryption。简单翻转方式:

const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')

flipFuses(
  // Electron 路径
  require('electron'),
  // 要翻转的 fuses
  {
    version: FuseVersion.V1,
    [FuseV1Options.RunAsNode]: false
  }
)

20. 不要向不可信 Web 内容暴露 Electron API

在预加载脚本中不应直接把 Electron API(尤其是 IPC)暴露给不可信 Web 内容

为什么? 暴露 ipcRenderer.on 之类的裸 API 很危险:它让渲染进程直接接触整个 IPC 事件系统,从而能监听任何 IPC 事件,而不只是为它准备的那几个。我们也不能把回调直接传过去——因为 IPC 事件回调的第一个参数是 IpcRendererEvent 对象,其中包含 sender 等可触及底层 ipcRenderer 实例的属性;即使你只监听特定事件,直接传回调也意味着渲染器拿到了这个事件对象。简言之:不可信 Web 内容只应获得必要的信息与 API

怎么做?

// 错误
contextBridge.exposeInMainWorld('electronAPI', {
  on: ipcRenderer.on
})

// 也错误(回调被直接传入,事件对象泄漏)
contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback)
})

// 正确(在预加载侧剥离事件对象,只回传 value)
contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value))
})

想深入了解 contextIsolation 以及如何用它加固应用,参见 Context Isolation

从源码看:这些安全默认值如何被"落地"

纵观全篇清单,很多建议其实已被 Electron 做成了默认值。这一点在主进程 WebPreferences 解析处有清晰的源码佐证:在 shell/browser/web_contents_preferences.cc 的构造函数中,node_integration_node_integration_in_worker_ 被初始化为 false,而 context_isolation_ 初始化为 true——它们共同构成了"渲染进程默认隔离"的基石。当这些偏好被同步给渲染进程时(同文件第 399-404 行),nodeIntegrationnodeIntegrationInWorkercontextIsolation 等键值被写入进程偏好字典,驱动 Chromium 层真正生效。

把源码结论与文档对照,可以归纳出三条"默认值即安全"的演进主线,这也是新项目应当遵循的最低起点:

  • Electron 5:默认关闭 Node 集成(对应第 2 条);
  • Electron 12:默认开启上下文隔离(对应第 3 条);
  • Electron 20:渲染进程默认开启沙箱(对应第 4 条)。

安全不是一次性动作,而是贯穿"框架升级 → 依赖审计 → 代码实现 → 运行配置 → 打包裁剪"全生命周期的持续实践。把官方这份 20 条清单当作应用发布前的强制门禁,配合控制台的 Electron Security Warning 逐项自检,你的 Electron 应用才能在保留桌面级能力的同时,不把用户暴露在 RCE 的悬崖边缘。

登录后查看全文
热门项目推荐
相关项目推荐