企业级 Electron 应用的安全不能停留在“关掉 nodeIntegration”这种基础要求上。面对数据泄露、代码注入、供应链攻击等真实威胁,你需要一套覆盖开发、构建、分发、运行全链路的加固方案。以下每一项都是经过大量生产环境验证的硬措施,没有理论空谈。
25.4.1 渲染进程的硬隔离
1. 强制开启 contextIsolation,绝不回退
从 Electron 12 开始,contextIsolation 默认为 true,并且官方强烈建议不要关闭。这一步必须保留并显式确认:
// main.js
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true, // 必须为 true
nodeIntegration: false, // 必须为 false
sandbox: true, // 见下文
}
});
关闭 contextIsolation 会让渲染进程的 JavaScript 可以直接访问主进程的 require 和 Electron 内部对象,等同于把整个系统的钥匙交给了网页内容。任何 XSS 漏洞都可以直接变成远程代码执行(RCE)。
2. 启用沙盒(sandbox)
在支持的操作系统上,将渲染进程运行在操作系统级的沙箱中,进一步限制其文件系统、网络和系统调用权限:
webPreferences: {
sandbox: true
}
启用 sandbox 后,渲染进程默认无法使用 Node.js API,预加载脚本也需要通过 contextBridge 暴露有限功能。即使网页内容被攻破,攻击者也被困在一个极低权限的环境里。
25.4.2 预加载脚本的安全基线
预加载脚本是主进程与渲染进程之间唯一的数据通道,必须做到权限最小化。
1. 使用 contextBridge 精确暴露 API
永远不要通过 preload 直接向 window 对象注入 Node.js 模块或 Electron 的完整 ipcRenderer。正确的做法是:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('secureApi', {
// 只暴露这一个方法,并且做参数校验
saveFile: (filePath, content) => {
// 参数可在主进程再次验证
return ipcRenderer.invoke('save-file', { filePath, content });
},
getAppVersion: () => ipcRenderer.invoke('get-version')
});
渲染进程只能调用 window.secureApi.saveFile(),它接触不到 ipcRenderer 本体,也无法随意向主进程发送任意消息。
2. 在主进程对 IPC 调用进行参数验证和权限判断
所有来自渲染进程的 invoke 或 send,主进程处理时要把它们当作“不可信数据”:
// main.js
ipcMain.handle('save-file', async (event, { filePath, content }) => {
// 1. 验证调用来源的窗口或 WebContents(白名单)
if (!isCallerAllowed(event.sender)) throw new Error('Unauthorized');
// 2. 检查文件路径是否在允许的目录内(防目录遍历)
const safePath = path.resolve(app.getPath('userData'), 'files', path.basename(filePath));
// 3. 内容大小、类型检查
if (content.length > MAX_FILE_SIZE) throw new Error('File too large');
return fs.promises.writeFile(safePath, content, 'utf-8');
});
绝不要在 IPC 通道中直接暴露 fs.readFile、child_process.exec 等底层 API,必须在主进程封装一层白名单式的业务方法。
25.4.3 内容安全策略(CSP)
为应用的所有网页设置严格的 CSP 头,这能在浏览器层面阻止 XSS 代码的执行和数据外泄。
1. 通过 meta 标签或响应头设置 CSP
<!-- index.html -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
connect-src 'self' https://api.mycompany.com;">
default-src 'self':所有资源必须来自应用自身,阻止加载外部恶意脚本。script-src 'self':禁止内联脚本和eval,这是防御 XSS 的关键。如果必须使用内联,可以配合 nonce 或 hash。connect-src:限定应用可以发起的网络请求目标,即使页面被注入代码,也无法将数据发送到攻击者控制的服务器。style-src 'unsafe-inline'可以根据实际情况收紧,但为了 UI 组件库的兼容性常需保留。
2. 在主进程中通过 session 统一设置
// main.js
const { session } = require('electron');
app.on('ready', () => {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'; script-src 'self'; connect-src 'self' https://api.mycompany.com"
]
}
});
});
});
这样可以确保所有加载的页面(包括 iframe、webview)都强制应用同一套策略。
25.4.4 依赖与供应链安全
企业应用很可能使用大量 npm 包,你必须防范恶意依赖和已知漏洞。
1. 锁定依赖版本与完整性校验
使用 package-lock.json 或 yarn.lock 并提交到仓库,禁止使用 * 或 ^ 的范围版本。在 CI 中启用 npm 的完整性校验(npm ci 而不是 npm install)。
2. 定期运行安全审计
npm audit
# 或
yarn audit
将审计结果集成到 CI 流程中,对高危漏洞设置阻断。但不要盲目全量升级,应在测试环境验证兼容性。
3. SCA(软件成分分析)工具
使用 Snyk、WhiteSource 或 OWASP Dependency-Check 扫描项目依赖,并建立企业级漏洞响应流程。
4. 自建 npm 代理或镜像
通过 Verdaccio、Nexus 等工具自建私有 registry,缓存并审查所有依赖包,防止投毒和供应链中断。
25.4.5 应用签名与完整性
未签名的应用容易被篡改或替换,用户也无法验证其来源。
1. Windows:代码签名证书
从 DigiCert、GlobalSign 或 Certum 购买 EV 代码签名证书,使用 electron-builder 配置自动签名:
// package.json (electron-builder 配置)
"win": {
"sign": "./scripts/sign.js",
"certificateFile": "cert.pfx",
"certificatePassword": "env:CERT_PASSWORD"
}
对于 Windows,使用 signtool 或 electron-builder 的集成签名即可。签名后的应用在安装和运行时不会触发 SmartScreen 警告。
2. macOS:公证(Notarization)
自 macOS Catalina 起,未公证的软件会被 Gatekeeper 拦截。你需要:
- 加入 Apple Developer Program。
- 配置
electron-builder使用notarize工具(如@electron/notarize):
// electron-builder 配置
"mac": {
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "entitlements.mac.plist",
"entitlementsInherit": "entitlements.mac.plist"
},
"afterSign": "scripts/notarize.js"
3. 应用更新也必须验证签名
使用 electron-updater 时,在 app-update.yml 中必须配置 publisherName 并启用签名检查:
provider: generic
url: https://updates.mycompany.com
publisherName: My Company Inc.
这种验证能确保更新包在下载和安装过程中未被篡改。
25.4.6 数据安全与本地存储
企业应用通常会在本地存储敏感数据(token、用户配置、离线数据)。
1. 加密本地存储
- 对于键值对数据,使用
safeStorageAPI 进行加密后存入localStorage或 JSON 文件:
const { safeStorage } = require('electron');
const encrypted = safeStorage.encryptString('my-sensitive-data');
// 存入文件或 sqlite
safeStorage 使用操作系统级的加密(macOS Keychain、Windows DPAPI)。
- 对于结构化数据(如 SQLite),考虑使用
sqlcipher或better-sqlite3配合自定义加密层,但更推荐将关键字段单独加密存储。
2. 清除敏感痕迹
- 禁止在崩溃报告、日志文件中写入敏感信息。所有日志输出前需做脱敏处理。
- 应用退出时清除剪贴板中的敏感内容(如果业务需要):
const { clipboard } = require('electron');
clipboard.clear();
3. 网络传输强制 HTTPS
即使在 Electron 里,也不允许回退到 HTTP。可以通过 webRequest 强制升级:
session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
if (details.url.startsWith('http://')) {
callback({ redirectURL: details.url.replace('http://', 'https://') });
} else {
callback({});
}
});
25.4.7 进程与权限控制
1. 禁止 remote 模块
@electron/remote 已废弃,新项目严禁使用。它让渲染进程几乎可以调用主进程的所有能力,完全破坏了隔离模型。
2. 限制 webview 标签
如果必须使用 webview,要为其单独设置 preload、sandbox 和严格的 CSP,并在其 console 中监控异常。最好的做法是尽量不用 webview,改用 BrowserView 或 iframe(配合合适的安全策略)。
3. 关闭不必要的 Chromium 特性
在 main.js 中通过 app.commandLine.appendSwitch 关掉可能被利用的调试功能和实验特性:
app.commandLine.appendSwitch('disable-http2');
// 生产环境移除或限制 --inspect
app.commandLine.appendSwitch('remote-debugging-port', '0');
另外,务必在打包时设置 ELECTRON_ENABLE_LOGGING=false 并移除 --inspect 相关的启动参数。
25.4.8 安全更新与应急响应
1. 内置强制更新机制
使用 electron-updater 并配置为启动时自动检查更新,对关键安全补丁实行强制升级:
autoUpdater.on('update-downloaded', () => {
// 提示用户,如果版本是最低安全基线以下则退出应用
if (currentVersion < MINIMUM_SECURE_VERSION) {
dialog.showMessageBox({ message: '安全更新已完成,应用即将重启' });
autoUpdater.quitAndInstall();
}
});
2. 漏洞应急流程
- 建立对 Electron 和 Chromium 安全公告(Electron Security)的监控。
- 确定内部修复 SLA(通常高危漏洞在 48 小时内完成修复和推送)。
- 紧急情况下通过
autoUpdater推送补丁版本,并利用应用内置的消息通道广播给所有在线用户。
25.4.9 自动化安全检查
最后,把这些策略固化为 CI 规则和代码扫描,避免人工遗忘。
1. 使用 Electronegativity
Electronegativity 是一款专门针对 Electron 应用的静态分析工具,能检测 nodeIntegration 开启、contextIsolation 关闭、危险 API 使用等常见配置错误。将其集成到 CI 管道:
npx @doyensec/electronegativity -i ./app
如果检出高危问题,直接阻断构建。
2. ESLint 安全插件
在项目中启用 eslint-plugin-security 和自定义规则,禁止在渲染进程代码中直接出现 require('fs') 或 ipcRenderer.send 等模式。
3. 定期渗透测试
即使有自动化工具,仍然需要定期邀请安全团队或外部机构对应用进行渗透测试,特别是针对主进程 IPC 接口的模糊测试。
企业级安全防护不是一次性配置,而是一套持续集成的流程。上面这些措施每一个都能显著提升应用的抗攻击能力,合在一起才能构建起纵深防御。结合你的具体业务场景,可以先从“关闭 nodeIntegration + contextIsolation + CSP” 三项立即生效的硬措施开始,然后逐步向签名、加密存储和 CI 检测推进。