06 · 安全与性能:生产前必读清单

目标:掌握官方安全清单(20 条)中最致命的几条,以及性能检查清单。发布前对照走一遍。 官方对应:安全、性能

安全心智模型

Electron ≠ 浏览器。你的 JS 能访问文件系统和 shell,权力越大攻击面越大。核心原则:

  1. 渲染进程当作不可信的浏览器标签页对待——尤其加载远程内容时
  2. 最小权限暴露:contextBridge 只开白名单,绝不裸暴露 ipcRenderer
  3. 保持最新: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. 其他默认值不要动

保持默认即可,动了就是风险:

配置默认后果
contextIsolationtrue关了 = 页面可篡改你暴露的 API
sandbox(20+)true关了 = 渲染器权限放大
webSecuritytrue关了 = 同源策略失效
allowRunningInsecureContentfalse开了 = 混合内容执行
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。速记:

  1. 别阻塞主进程——主进程卡 = 整个应用卡(最严重)
  2. 别阻塞渲染进程——requestIdleCallback / Web Workers
  3. 依赖懒加载 + 打包成单文件,减少 require() 开销
  4. 不必要的 polyfill / 网络请求砍掉,不变的字体图片打包进应用
  5. 心法: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