在 Electron 应用中,渲染进程是用户看得见、摸得着的部分——窗口里的网页内容、按钮点击、动画效果全部在这里运行。但正因为它是“网页”,从安全角度看,它也是最容易被攻击的入口。Electron 没有把渲染进程设计成“无所不能的上帝线程”,而是从一开始就为它划定了严格的能力边界,并通过一系列默认关闭的功能,迫使开发者采用安全的通信模式。
8.1.1 默认禁用的能力
如果你不做任何配置,一个标准的渲染进程不能做以下事情:
- 不能直接使用 Node.js 模块
在较新版本的 Electron 中(≥12),nodeIntegration 默认为 false。这意味着你无法在渲染进程的 JS 代码中直接写 require('fs') 或 require('os'),任何尝试都会抛出错误。这避免了网页中的恶意脚本获取本地文件系统的访问权。
- 不能直接调用 Electron 原生 API
像 ipcRenderer、shell、clipboard 等 Electron 提供的模块,默认也无法在渲染进程中直接使用。这是因为 Electron 原生 API 往往拥有系统级权限(例如 shell.openExternal 可以打开任意应用程序或链接,clipboard 可以读写剪贴板内容),一旦直接暴露给不安全的网页,后果严重。
- 不能绕过预加载脚本暴露的接口
渲染进程只能访问 preload 脚本中通过 contextBridge.exposeInMainWorld 显式暴露出来的 API,以及常规的 Web API(如 DOM、fetch、WebSocket 等)。任何未暴露的能力,对于渲染进程来说都是不可见的——这就像一个双向过滤的代理,确保“你能用到的,都是我们允许的”。
8.1.2 contextIsolation:默认开启的沙箱保护
Electron 从 12 版本开始默认启用 contextIsolation: true,这是一个关键的安全特性。它的作用是:将预加载脚本和网页自身的 JavaScript 运行环境彻底隔离。
在隔离之前,preload 脚本和网页页面共享同一个全局作用域。这意味着网页代码可以意外覆盖或篡改预加载脚本中暴露的 API,甚至直接访问到 require 函数(如果预加载脚本不小心把它泄露到全局)。而开启上下文隔离后,preload 运行在一个独立的、受限的上下文中,能够使用 contextBridge 安全地向网页传递函数和对象,但网页无法反向访问预加载脚本的内部状态或修改其原型链。
实际使用中,你应该总是保持 contextIsolation: true,并在 preload 中这样暴露接口:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile'),
getAppVersion: () => ipcRenderer.invoke('app:getVersion'),
});
网页中只能通过 window.electronAPI.openFile() 使用函数,无法直接接触 ipcRenderer 对象,更不可能调用 require。这种设计让任何通过 XSS 注入的恶意脚本最多只能在网页层面搞破坏,而无法触及系统资源。
8.1.3 显式可选的能力放开
尽管我们提倡默认隔离,但在一些特殊场景(比如开发工具、内部使用的管理后台)中,你可能需要渲染进程使用一些额外能力。Electron 提供了明确配置选项来有选择地放开:
- nodeIntegration: true
如果你确实需要在渲染进程中频繁使用 Node.js 模块(比如早期的 Electron 项目),可以手动开启。但必须同步设置 contextIsolation: false,因为 Node.js 环境无法与上下文隔离同时存在。这种方式风险很高,仅应在完全信任内容源的场景下使用(例如加载的是本地 HTML 文件,内容完全由你控制)。
- contextIsolation: false
关闭上下文隔离可以让预加载脚本和页面共享全局空间,网页可以直接调用 require(如果预加载脚本未做清理)。这只适用于需要兼容老旧代码或依赖特殊库的情况,绝不应用在可能加载第三方或远程内容的窗口上。
- sandbox: true
开启后,渲染进程会运行在操作系统的沙箱中,权限进一步收窄,即使攻击者利用了渲染进程漏洞,也难以突破到系统层面。这是 Chromium 本身的安全机制,建议在对安全性要求极高的场景下与 contextIsolation: true 搭配使用,但要注意,某些原生模块可能因此无法正常工作。
8.1.4 实际工作中的能力边界
在实际开发中,渲染进程的典型能力集合如下:
- ✅ 操作 DOM、执行 JavaScript、绘制 Canvas、播放音视频
- ✅ 使用 Fetch API 或 WebSocket 发起网络请求(受制于 CSP 和同源策略)
- ✅ 调用
window.electronAPI中预先定义好的方法(需要主进程配合) - ✅ 使用浏览器原生的 localStorage、IndexedDB 等存储方案(数据保存在用户数据目录)
- ❌ 直接读写文件、操作进程、执行系统命令
- ❌ 直接访问 Electron 或 Node.js 模块(除非明确通过
preload暴露并受控) - ❌ 修改窗口标题、菜单、托盘图标(这些是主进程权限,只能通过 IPC 通知主进程执行)
任何渲染进程想要做“出格”的事情,必须通过 IPC 向主进程发送请求,由主进程在受控环境下执行并返回结果。这就是渲染进程的能力边界:它必须通过主进程这道“闸门”,才能触及操作系统的核心资源。
8.1.5 牢记在心的安全守则
无论应用多简单,都应该默认遵守以下几条规则:
- 永远使用
contextIsolation: true和contextBridge,绝不让渲染进程直接接触 Node.js 或 Electron 模块。 - 永远不在
preload脚本中暴露整个ipcRenderer对象,只提供具体的、命名的函数接口。 - 永远不对不可信的内容开启
nodeIntegration或关闭contextIsolation。如果必须加载远程 URL,一定要限制其权限到最小化。 - 在 IPC 通信中始终校验来源与参数,不要盲目信任渲染进程发来的消息。攻击者可能通过 XSS 调用你暴露的
electronAPI方法,尝试发送恶意的路径或命令给主进程执行。
把渲染进程视作一个“不受信任的客户端”,是设计安全 Electron 应用的最佳心态。限制它的能力,就是在保护用户的系统安全。