本节摘要:Electron 安全的第一层是 BrowserWindow 的 webPreferences 开关组:nodeIntegration 关、contextIsolation 开、sandbox 开、webSecurity 开是现代基线,远程内容另加导航限制。本节逐项讲每个开关的攻击面含义、给出可直接套用的基线模板与自查方法,并用一次真实的安全体检案例展示基线如何落地为流程。
把 webPreferences 想象成窗口的安检配置单。每一个开关对应一类"放进窗口里的东西",而攻击者拿到窗口控制权后能撬动多少资产,直接由这张单子决定。核心开关的正确取值与理由:
nodeIntegration: false。它的字面意思是"页面里禁用 Node"。开着的时候,页面脚本可以 require 任意模块、执行任意命令——一个 XSS 就等于一次完整的系统沦陷。现代版本默认关闭,但显式写出它,是为了在代码审查时一眼可见。这个开关在加载远程内容的应用里开启,等于把用户电脑的钥匙挂在网页上。
contextIsolation: true。上一章讲过的双上下文结构:页面摸不到 preload 内部、只能见白名单。它关着的时候,攻击者可以通过污染原型链逐步蚕食 preload 的能力(2.4 节的攻击演示)。
sandbox: true。操作系统级的牢房(2.5 节),渲染进程即便被完全攻陷也摸不到文件系统。
webSecurity: true。控制同源策略与跨域限制。有的开发者为了绕过跨域问题把它关掉——这是用安全换便利的典型交易,正确做法是在主进程做网络代理(4.2 节展开思路)。
// 基线模板:可直接套用的窗口安全配置 const win = new BrowserWindow({ width: 1024, height: 720, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, // 基线一:页面无 Node contextIsolation: true, // 基线二:双上下文隔离 sandbox: true, // 基线三:操作系统级沙箱 webSecurity: true, // 基线四:同源策略不放松 devTools: process.env.NODE_ENV === 'development' // 生产构建关闭 DevTools 入口 } });
加载远程内容的应用再加三条。如果窗口要加载网络上的页面(混合形态应用很常见),风险等级上调一档,导航控制必须跟上:拦截 will-navigate 事件只放行自家域名;用 setWindowOpenHandler 把 window.open 的请求一律改道到系统默认浏览器,而不是在应用里开新窗口养未知内容;禁用开新 webContents 的权限。
// 远程内容窗口的导航管制 win.webContents.on('will-navigate', (event, url) => { if (!url.startsWith('https://app.example.com')) { event.preventDefault(); // 页面想跳去别处?拦下 } }); win.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); // 外部链接交给系统浏览器 return { action: 'deny' }; // 应用内不开新窗口 });

背景:一个团队的应用要接企业客户的采购流程,对方安全团队发来一份检查清单,其中 Electron 相关的有六项。团队自己先做了一轮体检。
操作:体检分两步。第一步静态扫描——用官方的安全检查工具跑主进程与 preload,工具输出两项告警:某窗口的 devTools 在生产构建里仍可打开;一个工具类窗口没配 preload 却开着上下文隔离以外的默认值之外的历史参数。第二步人工复核——按 4.4 节的自查表逐项过 IPC 通道,发现一个调试期遗留的 eval:any 通道(接收任意表达式并求值),它在基线扫描里不告警(配置层面合法),但本质是给页面留了一个"任意代码执行"后门。
结果:三项问题全部修复——devTools 加了环境开关、遗留参数清理、调试通道删除。企业客户的渗透测试随后通过。
解读:这个案例的要点是工具与人工缺一不可。工具扫的是"配置是否越线"(开关层面),人工查的是"通道是否过权"(语义层面)——那个 eval:any 通道所有开关都合规,却是整个应用最大的洞。变式:如果应用需要插件系统,"任意代码执行"可能是刻意设计的功能,那就必须把插件放进独立进程并配能力授予机制(第七章会从 VS Code 的架构里取经),而不是在主进程里裸开通道。
基线最大的敌人是时间:今天合规,三个月后某个同事为了赶工关掉 sandbox 提交上线。两道固化手段:其一,把基线检查接进持续集成,安全开关的取值断言直接写成测试用例,违规即构建失败;其二,代码评审清单里固定一条——凡动 webPreferences 的提交必须双人复核。安全配置的变更频率应该趋近于零,一旦变更就值得最高规格的审视。
// 用测试固化基线(示意):断言窗口工厂的产物配置合规 const expected = { nodeIntegration: false, contextIsolation: true, sandbox: true }; assert.deepEqual(pickSecurityOptions(createMainWindow), expected);
下一节给窗口里的内容立规矩:内容安全策略如何从源头限制"什么代码有资格运行"。