跳到主要内容

安全最佳实践

Electron 应用拥有比 Web 应用更大的权限,因此安全问题尤为重要。本节介绍 Electron 应用的安全最佳实践。

安全风险​

Electron 应用可以访问文件系统、执行系统命令、访问原生 API。如果加载了恶意内容,攻击者可能:

  1. 读取敏感文件
  2. 执行任意代码
  3. 访问用户数据
  4. 发起网络请求

核心安全原则​

1. 只加载可信内容​

永远不要加载和执行来自不可信来源的远程代码。

const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})

mainWindow.loadFile('index.html')

如果必须加载远程内容,确保使用 HTTPS:

mainWindow.loadURL('https://example.com')

2. 禁用 Node.js 集成​

默认情况下,渲染进程不应该有 Node.js 访问权限。

const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
})

3. 启用上下文隔离​

上下文隔离确保预加载脚本和渲染进程运行在不同的 JavaScript 上下文中。

const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
sandbox: true
}
})

4. 启用进程沙箱​

沙箱限制了渲染进程的系统能力。

const mainWindow = new BrowserWindow({
webPreferences: {
sandbox: true
}
})

5. 使用安全的预加载脚本​

不要直接暴露整个 ipcRenderer:

const { contextBridge, ipcRenderer } = require('electron')

// 错误做法
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: ipcRenderer
})

// 正确做法
contextBridge.exposeInMainWorld('electronAPI', {
send: (channel, data) => {
const validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
}
})

安全配置清单​

webPreferences 配置​

const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
webSecurity: true,
allowRunningInsecureContent: false,
experimentalFeatures: false,
enableBlinkFeatures: '',
disableBlinkFeatures: ''
}
})

配置说明​

配置项推荐值说明
nodeIntegrationfalse禁用 Node.js 集成
contextIsolationtrue启用上下文隔离
sandboxtrue启用沙箱
webSecuritytrue启用同源策略
allowRunningInsecureContentfalse禁止加载不安全内容
experimentalFeaturesfalse禁用实验性功能

内容安全策略​

CSP(Content Security Policy)可以限制资源加载来源。

设置 CSP​

const { session } = require('electron')

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"connect-src 'self' https://api.example.com"
].join('; ')
}
})
})

HTML meta 标签​

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

CSP 指令说明​

指令说明
default-src默认资源来源
script-srcJavaScript 来源
style-srcCSS 来源
img-src图片来源
connect-src网络请求来源
font-src字体来源

IPC 安全​

验证发送者​

ipcMain.handle('get-secrets', (event) => {
const senderFrame = event.senderFrame
const url = new URL(senderFrame.url)

if (url.origin !== 'file://') {
return null
}

return getSecrets()
})

白名单通道​

const validChannels = new Set([
'file:open',
'file:save',
'user:login'
])

contextBridge.exposeInMainWorld('electronAPI', {
send: (channel, data) => {
if (validChannels.has(channel)) {
ipcRenderer.send(channel, data)
}
},
invoke: (channel, data) => {
if (validChannels.has(channel)) {
return ipcRenderer.invoke(channel, data)
}
return Promise.reject(new Error('Invalid channel'))
}
})

不要信任渲染进程数据​

ipcMain.handle('file:read', async (event, filePath) => {
const resolvedPath = path.resolve(filePath)
const appPath = app.getAppPath()

if (!resolvedPath.startsWith(appPath)) {
throw new Error('Access denied')
}

return fs.promises.readFile(resolvedPath, 'utf-8')
})

导航安全​

限制导航​

const { URL } = require('node:url')

mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl)

if (parsedUrl.origin !== 'https://example.com') {
event.preventDefault()
}
})

限制新窗口​

mainWindow.webContents.setWindowOpenHandler(({ url }) => {
const parsedUrl = new URL(url)

if (parsedUrl.protocol === 'https:') {
shell.openExternal(url)
}

return { action: 'deny' }
})

WebView 安全​

如果使用 <webview> 标签,需要特别注意安全:

验证 WebView 配置​

app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
delete webPreferences.preload
webPreferences.nodeIntegration = false
webPreferences.contextIsolation = true

if (!params.src.startsWith('https://example.com/')) {
event.preventDefault()
}
})
})

禁用不安全的属性​

<!-- 不要使用这些属性 -->
<webview nodeIntegration src="..."></webview>
<webview allowpopups src="..."></webview>

<!-- 安全的做法 -->
<webview src="https://example.com"></webview>

shell 模块安全​

不要使用 shell.openExternal 打开不可信的 URL:

const { shell } = require('electron')
const { URL } = require('node:url')

function openExternal(url) {
const parsedUrl = new URL(url)
const allowedProtocols = ['http:', 'https:']

if (allowedProtocols.includes(parsedUrl.protocol)) {
shell.openExternal(url)
}
}

安全更新​

保持 Electron 更新​

npm update electron

检查依赖漏洞​

npm audit
npm audit fix

使用最新 LTS 版本​

Electron 定期发布安全更新,确保使用最新的稳定版本。

安全检查清单​

在发布应用前,检查以下项目:

  • nodeIntegration 设置为 false
  • contextIsolation 设置为 true
  • sandbox 设置为 true
  • webSecurity 设置为 true
  • 只加载 HTTPS 内容
  • 设置了 CSP
  • IPC 通道使用白名单
  • 验证了 IPC 消息发送者
  • 限制了导航和新窗口
  • 使用最新版本的 Electron
  • 检查了依赖漏洞

常见安全问题​

XSS 攻击​

跨站脚本攻击可以窃取用户数据或执行恶意操作。

防护措施:

  1. 不要使用 innerHTML
  2. 对用户输入进行转义
  3. 设置严格的 CSP

远程代码执行​

如果启用了 nodeIntegration,XSS 可能导致远程代码执行。

防护措施:

  1. 禁用 nodeIntegration
  2. 启用 contextIsolation
  3. 不要加载远程内容

中间人攻击​

HTTP 连接可能被劫持。

防护措施:

  1. 只使用 HTTPS
  2. 设置 webSecurity: true
  3. 不要禁用证书验证

总结​

Electron 应用安全需要从多个层面考虑:

  1. 配置安全:正确设置 webPreferences
  2. 内容安全:只加载可信内容,设置 CSP
  3. 通信安全:验证 IPC 消息,使用白名单
  4. 导航安全:限制导航和新窗口
  5. 依赖安全:保持更新,检查漏洞

安全是一个持续的过程,需要在开发的每个阶段都保持警惕。