06 · 安全与性能:生产前必读清单
安全心智模型
Electron ≠ 浏览器。你的 JS 能访问文件系统和 shell,权力越大攻击面越大。核心原则:
- 渲染进程当作不可信的浏览器标签页对待——尤其加载远程内容时
- 最小权限暴露:
contextBridge只开白名单,绝不裸暴露ipcRenderer - 保持最新:Electron/Chromium/Node 旧版本是已知漏洞的重灾区
官方 20 条完整清单见安全文档,下面挑最关键的精讲。
高危项精讲(违反即事故级别)
1. 远程内容禁用 Node 集成(默认已禁,别手贱打开)
// 错误:远程页面获得 RCE 能力
new BrowserWindow({ webPreferences: { nodeIntegration: true } }).loadURL('https://example.com')
// 正确:只给 preload 白名单
new BrowserWindow({ webPreferences: { preload: path.join(app.getAppPath(), 'preload.js') } })XSS 在普通网站是脚本注入,在开了 Node 的 Electron 里直接升级成远程代码执行(RCE)。
2. contextBridge 暴露姿势(安全清单第 20 条)
// 错误 ×2
contextBridge.exposeInMainWorld('api', { on: ipcRenderer.on })
contextBridge.exposeInMainWorld('api', {
onUpdate: (cb) => ipcRenderer.on('ch', cb) // event 对象泄露了
})
// 正确
contextBridge.exposeInMainWorld('api', {
onUpdate: (cb) => ipcRenderer.on('ch', (_event, value) => cb(value))
})3. 验证 IPC 消息的 sender(第 17 条)
任何特权 IPC handler 都要验证来源,iframe/子窗口也能发消息:
// 错误
ipcMain.handle('get-secrets', () => getSecrets())
// 正确
ipcMain.handle('get-secrets', (e) => {
if (!validateSender(e.senderFrame)) return null
return getSecrets()
})
function validateSender (frame) {
return (new URL(frame.url)).host === 'electronjs.org' // 用真 URL 解析 + 白名单
}4. 限制导航与新窗口(第 13、14 条)
app.on('web-contents-created', (event, contents) => {
// 阻止跳转到白名单之外
contents.on('will-navigate', (event, navigationUrl) => {
if (new URL(navigationUrl).origin !== 'https://example.com') {
event.preventDefault()
}
})
// 拒绝意外弹窗,外链交给系统浏览器
contents.setWindowOpenHandler(({ url }) => {
if (isSafeForExternalOpen(url)) setImmediate(() => shell.openExternal(url))
return { action: 'deny' }
})
})注意 startsWith('https://example.com') 会被 https://example.com.attacker.com 绕过,必须用 URL 解析。
5. 别用 file://,用自定义协议(第 18 条)
file:// 页面理论上可读机器上所有文件,XSS 一配合就全泄露。用 protocol.handle 自定义协议,只服务指定文件集。
6. 其他默认值不要动
保持默认即可,动了就是风险:
| 配置 | 默认 | 后果 |
|---|---|---|
contextIsolation | true | 关了 = 页面可篡改你暴露的 API |
sandbox(20+) | true | 关了 = 渲染器权限放大 |
webSecurity | true | 关了 = 同源策略失效 |
allowRunningInsecureContent | false | 开了 = 混合内容执行 |
experimentalFeatures / enableBlinkFeatures | 关 | 未充分测试的 Chromium 特性 |
7. 杂项
shell.openExternal不传用户可控内容- 远程内容用 HTTPS;
<webview>不加allowpopups,创建前在will-attach-webview里强制安全配置 - 设置 CSP:
<meta http-equiv="Content-Security-Policy" content="script-src 'self'"> - 用
@electron/fuses关闭不需要的运行时能力(runAsNode、nodeCliInspect等可被滥用执行命令) - 升级策略:一次只迁一个大版本,对照破坏性变更文档
性能检查清单
官方 8 条性能清单的完整详解(含每条的原理、代码示例、量化命令)见 09-performance。速记:
- 别阻塞主进程——主进程卡 = 整个应用卡(最严重)
- 别阻塞渲染进程——
requestIdleCallback/ Web Workers - 依赖懒加载 + 打包成单文件,减少
require()开销 - 不必要的 polyfill / 网络请求砍掉,不变的字体图片打包进应用
- 心法:Profile → 找瓶颈 → 优化 → 重复(DevTools Performance/Memory 面板)
发布前对照表(Checklist)
- Electron 升到最新稳定版
-
contextIsolation: true、sandbox: true、nodeIntegration: false(默认即如此,确认没改) - 所有
contextBridge暴露项均为白名单函数,无裸ipcRenderer - 特权 IPC handler 验证
senderFrame -
will-navigate/setWindowOpenHandler已加防护 - 本地页面走自定义协议或至少带严格 CSP
- 依赖
npm audit无高危 - 已签名(+ macOS 公证)
- Fuses 按需翻转
小结
- 安全的本质是权限边界:渲染器不可信、暴露白名单化、来源必验证
- 性能的本质是不阻塞事件循环:主进程尤其金贵
- 返回总览:README