如果你准备将应用分发给真实用户,安全配置就不是“可选项”,而是必须认真对待的底线。Electron 的强大之处在于它赋予了 Web 页面访问系统层的能力,但这也意味着任何一个前端漏洞都可能被放大为本地攻击。庆幸的是,通过一组明确且经过行业验证的配置,你可以极大程度地缩小攻击面。下面这份清单覆盖了每个 Electron 应用都应该遵循的安全基线。
1. 开启上下文隔离(Context Isolation)
配置项:webPreferences.contextIsolation
推荐值:true(Electron 12 起默认为 true)
这是 Electron 安全模型中最关键的一道防线。开启上下文隔离后,渲染进程的 JavaScript 环境与预加载脚本的 JavaScript 环境被完全分开,页面无法直接访问 require 或 Electron 内部 API,只能通过 contextBridge 暴露的有限接口与主进程通信。
如果你还在维护老项目,见到 contextIsolation: false 应立即列入迁移计划。它可以被简单理解为:关闭它,等于把主进程的大门钥匙直接扔在页面上。
2. 关闭 Node.js 集成
配置项:webPreferences.nodeIntegration
推荐值:false(Electron 5 起默认为 false)
该选项控制渲染进程是否可以直接使用 Node.js 的 require。在生产环境中应始终设置为 false,否则任何 XSS 攻击都可能通过 require('child_process') 直接执行系统命令。如果你确实需要在页面中使用 Node API,请通过预加载脚本 (preload) 使用 contextBridge 安全地暴露,绝不能在全局范围内开启。
3. 启用沙盒模式
配置项:app.enableSandbox() 或 webPreferences.sandbox
推荐值:true
开启沙盒后,渲染进程会被限制在操作系统的严格权限隔离环境中,即使攻击者成功执行恶意代码,也无法直接访问文件系统、网络或系统调用(除非经由主进程明确授权)。从 Electron 20 开始,沙盒默认开启,但你需要确保不额外关闭它。同时要注意,沙盒模式下除非你使用 contextBridge 或者通过主进程转发,否则预加载脚本也无法直接使用 Node.js 模块。
4. 防止敏感 API 的直接暴露
配置项:webPreferences.preload 脚本中的 contextBridge 用法
推荐做法:永远不要在预加载脚本中直接暴露整个 ipcRenderer 或 shell 等模块。应该封装功能函数,比如:
// 错误:直接暴露全能 api
contextBridge.exposeInMainWorld('api', {
send: ipcRenderer.send,
invoke: ipcRenderer.invoke,
});
// 正确:暴露有限语义化操作
contextBridge.exposeInMainWorld('fileAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content) => ipcRenderer.invoke('fs:saveFile', content),
});
这种做法限缩了渲染进程的攻击面,避免恶意页面通过 invoke 调用任意频道,绕过后端校验。
5. 启用 webSecurity 并谨慎处理跨域
配置项:webPreferences.webSecurity
推荐值:true(默认值)
禁用 webSecurity 会让你的渲染页面失去同源策略保护,可以随意加载远程脚本、发起跨域请求。永远不要为了开发方便而在生产环境禁用它。有条件访问远程内容的场景,应通过主进程代理请求或使用 CORS 白名单,而不是关闭安全锁。
6. 配置内容安全策略(CSP)
配置方式:通过 HTTP 头或 <meta> 标签在渲染页面中设置
推荐策略:至少包含以下指令
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
CSP 是缓解 XSS 攻击后果的重要机制。因为 Electron 页面通常从本地文件加载(file:// 协议),常规的 HTTP 头无法下发,因此强烈建议在 HTML 中通过 <meta> 标签直接定义。禁止 eval 和不必要的外部脚本加载,能有效控制被注入后的灾难范围。
7. 限制或禁止远程内容加载
配置项:webPreferences.webviewTag
推荐值:false(除非必要)
<webview> 标签本质上是另一个独立的渲染进程,可用来嵌入第三方网页。但它的配置项复杂,且容易配置错误形成安全漏洞。建议改用 BrowserView(由主进程管控)代替。如果必须使用 webview,务必对其 preload、nodeIntegration、allowpopups 等属性单独限制。
同时,务必验证应用中任何加载的远程 URL 是否都来自受信来源。不要使用 win.loadURL(userInput) 这样的模式,避免用户控制窗口加载的地址。
8. 安全地处理用户输入和文件路径
原则:所有来自渲染进程的输入都视为不可信。
实践:在主进程接收到文件路径或命令参数时,不要直接拼接到 shell.openPath 或 child_process.exec 中。使用白名单校验、路径规范化(例如 path.normalize() 并检查是否在允许的目录内)来防止路径遍历攻击。避免使用 eval 或 new Function 处理用户提供的数据。
9. 控制新窗口的创建
配置项:webPreferences 或监听 new-window 事件
推荐做法:在主进程中监听 webContents 的 new-window 事件,并强制所有外部链接在外部浏览器中打开。
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https://yourdomain.com')) {
return { action: 'allow' };
}
require('electron').shell.openExternal(url);
return { action: 'deny' };
});
这避免恶意页面通过 window.open 偷偷创建无边栏的新窗口进行钓鱼攻击。
10. 使用安全的进程间通信(IPC)
推荐:完全使用 ipcRenderer.invoke / ipcMain.handle 模式(请求-响应式),避免使用 send/on 进行双向通信。
理由:invoke/handle 返回 Promise,代码更清晰,且能确保每一次调用都有对应的处理逻辑,减少消息劫持和逻辑混乱的风险。在主进程中,务必对传入的参数进行校验,不要盲目信任频道名称和数据负载。
// 主进程中
ipcMain.handle('user:getById', async (event, userId) => {
if (typeof userId !== 'number') throw new Error('Invalid parameter');
return await db.getUser(userId);
});
11. 自动更新与签名
虽然不是运行时配置,但分发环节的安全同样重要。始终为你的安装包进行代码签名(Windows 使用 Authenticode,macOS 使用 Apple 开发者证书),并配置 electron-updater 或类似工具实现仅接受经过签名的更新包。这能防止中间人攻击篡改应用升级流程。
12. 定期审查 Electron 版本与依赖
Electron 团队会定期修复安全漏洞(通常跟随 Chromium 的安全更新周期)。你应该订阅 Electron 的安全公告,并制定计划至少在每个主要版本发布后进行升级。同时,审查 package.json 中的第三方依赖是否引入了已知漏洞(可使用 npm audit 或 Snyk 等工具)。
以上清单覆盖了构建安全 Electron 应用的必要配置。其核心思想可以概括为三条通用原则:
- 最小权限:渲染进程只应拥有完成界面工作所需的最少能力。
- 信任边界:永远把主进程和渲染进程之间的通信视为不可信边界,严格执行输入验证和操作限定。
- 纵深防御:即便某层被突破(如 XSS),后续的沙盒、CSP、权限控制依然能限制损失的扩散。
从你着手构建第一个生产窗口开始,就用这份清单作为你的安全检查表。安全不是一个后期打补丁的过程,而是融入架构选择阶段的基本意识。