Electron 桌面端安全基础
🎯 引言
Electron 页面同时拥有浏览器页面和桌面应用的可能能力。权限越大,页面受到恶意内容影响时风险越高,所以我们要给页面一把“只开必要房间的钥匙”。
学完本篇,你将能够:
- 说清
contextIsolation、sandbox和nodeIntegration的作用。 - 通过 preload 暴露小而明确的接口。
- 校验 IPC 参数,限制页面导航和新窗口打开。
- 为页面添加基础 CSP。
🧱 三个重要配置
创建窗口时,先使用下面这组适合入门项目的配置:
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
sandbox: true,
nodeIntegration: false,
},
});
| 配置 | 作用 |
|---|---|
contextIsolation | 页面脚本和 preload 使用不同的 JavaScript 环境 |
sandbox | 限制渲染进程直接使用系统能力 |
nodeIntegration: false | 不让页面直接使用 Node.js 模块 |
这些配置不是替代 IPC,而是给 IPC 划出边界。页面仍然可以通过 preload 使用经过筛选的功能。
🛡 只暴露用途明确的接口
不要把完整的 ipcRenderer 暴露给页面:
const { contextBridge, ipcRenderer } = require('electron/renderer');
contextBridge.exposeInMainWorld('electronAPI', {
getAppVersion: () => ipcRenderer.invoke('app:get-version'),
});
页面只能调用 getAppVersion,不能自行拼接任意 IPC 频道。主进程还要检查参数,不能只相信页面传来的数据。
ipcMain.handle('file:read-text', async (_event, filePath) => {
if (typeof filePath !== 'string' || !filePath.endsWith('.txt')) {
throw new Error('只允许读取文本文件');
}
return readFile(filePath, 'utf8');
});
🚫 限制导航和新窗口
Electron 页面可能显示服务端返回的内容、用户导入的 Markdown 或富文本。这些内容中的链接不一定由桌面端代码直接控制。
如果窗口跳到了外部网页,原来的应用界面就会被替换。外部网页也不应该直接运行在桌面应用的窗口环境里。
因此先规定一个简单规则:Electron 窗口只显示自己的页面,外部链接不在 Electron 中打开。
下面的示例假设打包后的应用通过 loadFile 加载本地页面:
win.webContents.on('will-navigate', (event, url) => {
if (!url.startsWith('file:')) {
event.preventDefault();
}
});
will-navigate 会在当前窗口准备跳转时触发。只要目标地址不是应用自己的 file: 页面,就取消这次跳转。
页面还可能通过 window.open() 或 target="_blank" 请求新窗口。比如页面中有这样一个链接:
<a href="https://www.electronjs.org/" target="_blank">
查看 Electron 文档
</a>
点击它时,Electron 会触发 setWindowOpenHandler。我们可以把 HTTPS 页面交给系统默认浏览器,再禁止 Electron 创建自己的新窗口:
const { shell } = require('electron/main');
win.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https://')) {
shell.openExternal(url);
}
return { action: 'deny' };
});
这段代码的执行过程是:
- 用户点击
target="_blank"链接。 setWindowOpenHandler收到准备打开的新窗口地址。shell.openExternal(url)调用系统默认浏览器打开 HTTPS 页面。{ action: 'deny' }告诉 Electron:不要再创建新的 Electron 窗口。
所以,shell.openExternal() 不是自动执行的 API,而是我们在确认地址符合要求后主动调用的“交给系统浏览器打开”方法。
开发环境使用 Vite 时,允许的地址还要包含 http://localhost:5173。这只是开发地址,打包后不要继续依赖它。
🧾 小节总结
- 页面默认不应该直接使用 Node.js 或 Electron 模块。
- preload 只暴露完成具体任务的方法。
- IPC 参数要在主进程校验。
- 应用应限制页面导航和新窗口来源。
- CSP 可以减少页面加载未知脚本的机会。
❓ 知识问答
Q1:contextIsolation 开启后还能使用 preload 吗?
A:可以。preload 通过 contextBridge 暴露有限 API,正是隔离环境下的常用做法。
Q2:为什么不能把 ipcRenderer 整个暴露出去?
A:页面就可以调用任意频道,接口边界会失去意义,也更难校验参数。
🧪 小练习
把下面的接口改成只允许调用固定的 app:get-version 频道:
contextBridge.exposeInMainWorld('electronAPI', {
// 请在这里编写代码
});
🎉 现在你已经知道如何给 Electron 页面设置基本安全边界了。
