乐闻世界logo
搜索文章和话题

Electron 安全性最佳实践

2月18日 10:42

Electron 应用的安全性至关重要,因为它结合了 Web 技术和 Node.js 的强大功能。如果不正确配置,可能会暴露系统资源给恶意代码。

核心安全配置

1. 禁用 nodeIntegration

nodeIntegration 允许渲染进程直接访问 Node.js API,这是最大的安全风险。

javascript
// main.js const mainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: false, // 必须设置为 false contextIsolation: true, // 必须设置为 true enableRemoteModule: false // 必须设置为 false } })

2. 启用 contextIsolation

contextIsolation 将预加载脚本与渲染进程隔离,防止恶意代码访问 Node.js API。

javascript
// main.js webPreferences: { contextIsolation: true, preload: path.join(__dirname, 'preload.js') }

3. 使用 preload 脚本

preload 脚本是在渲染进程加载之前运行的脚本,可以安全地暴露 API。

javascript
// preload.js const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { // 只暴露必要的 API readFile: (filePath) => ipcRenderer.invoke('read-file', filePath), writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content), getVersion: () => ipcRenderer.invoke('get-version') })
javascript
// renderer.js // 渲染进程只能访问暴露的 API window.electronAPI.readFile('path/to/file.txt') .then(content => console.log(content))

内容安全策略(CSP)

CSP 是一个额外的安全层,帮助防止跨站脚本攻击(XSS)。

html
<!-- index.html --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;">

CSP 配置说明

javascript
// main.js mainWindow = new BrowserWindow({ webPreferences: { webSecurity: true // 启用 Web 安全策略 } }) // 为远程内容设置 CSP session.defaultSession.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': ["default-src 'none'"] } }) })

加载内容的安全

1. 只加载可信内容

javascript
// 好的做法 - 加载本地文件 mainWindow.loadFile('index.html') // 好的做法 - 加载可信的 HTTPS 网站 mainWindow.loadURL('https://example.com') // 不好的做法 - 加载不可信的 HTTP 网站 mainWindow.loadURL('http://untrusted-site.com')

2. 验证加载的 URL

javascript
// main.js app.on('web-contents-created', (event, contents) => { contents.on('will-navigate', (event, navigationUrl) => { const parsedUrl = new URL(navigationUrl) // 只允许导航到白名单域名 if (['localhost', 'example.com'].includes(parsedUrl.hostname)) { return } event.preventDefault() }) contents.on('new-window', (event, navigationUrl) => { event.preventDefault() // 使用默认浏览器打开外部链接 shell.openExternal(navigationUrl) }) })

IPC 通信安全

1. 验证输入数据

javascript
// main.js ipcMain.handle('read-file', async (event, filePath) => { // 验证文件路径 if (!isValidFilePath(filePath)) { throw new Error('Invalid file path') } // 确保路径在允许的目录内 const allowedDir = path.join(app.getPath('userData'), 'files') const fullPath = path.resolve(filePath) if (!fullPath.startsWith(allowedDir)) { throw new Error('Access denied') } return fs.promises.readFile(filePath, 'utf-8') }) function isValidFilePath(filePath) { // 实现路径验证逻辑 return typeof filePath === 'string' && filePath.length > 0 }

2. 限制 IPC 通道

javascript
// main.js const allowedChannels = ['read-file', 'write-file', 'get-version'] ipcMain.on('channel-name', (event, ...args) => { // 验证通道名称 if (!allowedChannels.includes('channel-name')) { console.warn('Unauthorized IPC channel:', 'channel-name') return } // 处理消息 })

3. 使用白名单

javascript
// preload.js const { contextBridge, ipcRenderer } = require('electron') const allowedChannels = ['read-file', 'write-file'] contextBridge.exposeInMainWorld('electronAPI', { invoke: (channel, ...args) => { if (allowedChannels.includes(channel)) { return ipcRenderer.invoke(channel, ...args) } throw new Error(`Unauthorized channel: ${channel}`) } })

权限管理

1. 限制系统权限

javascript
// main.js const mainWindow = new BrowserWindow({ webPreferences: { // 禁用不必要的权限 sandbox: false, // 根据需求启用沙盒 webSecurity: true, allowRunningInsecureContent: false, experimentalFeatures: false, plugins: false } })

2. 使用沙盒模式

javascript
// main.js const mainWindow = new BrowserWindow({ webPreferences: { sandbox: true, // 启用沙盒模式 nodeIntegration: false, contextIsolation: true } })

数据保护

1. 敏感数据加密

javascript
// main.js const crypto = require('crypto') function encrypt(text, key) { const algorithm = 'aes-256-cbc' const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv) let encrypted = cipher.update(text, 'utf8', 'hex') encrypted += cipher.final('hex') return iv.toString('hex') + ':' + encrypted } function decrypt(text, key) { const algorithm = 'aes-256-cbc' const parts = text.split(':') const iv = Buffer.from(parts.shift(), 'hex') const encrypted = parts.join(':') const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') return decrypted }

2. 安全存储凭据

javascript
// 使用 keytar 存储敏感信息 const keytar = require('keytar') async function saveCredentials(username, password) { await keytar.setPassword('MyApp', username, password) } async function getCredentials(username) { return await keytar.getPassword('MyApp', username) }

更新安全

1. 验证更新包

javascript
// main.js const { autoUpdater } = require('electron-updater') autoUpdater.setFeedURL({ url: 'https://your-server.com/updates', headers: { 'Authorization': 'Bearer your-token' } }) autoUpdater.on('update-downloaded', (info) => { // 验证更新包的签名 if (verifyUpdateSignature(info)) { autoUpdater.quitAndInstall() } })

2. 使用 HTTPS

javascript
// 确保所有网络请求使用 HTTPS const protocol = 'https:' const updateUrl = `${protocol}//your-server.com/updates`

开发与生产环境分离

javascript
// config.js const isDev = process.env.NODE_ENV === 'development' module.exports = { isDev, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), // 开发环境可以启用 DevTools devTools: isDev } }

安全检查清单

  • 禁用 nodeIntegration
  • 启用 contextIsolation
  • 使用 preload 脚本
  • 配置 CSP
  • 验证所有输入数据
  • 限制 IPC 通道
  • 使用 HTTPS
  • 加密敏感数据
  • 验证更新包
  • 定期更新依赖项
  • 使用沙盒模式(如适用)
  • 禁用不必要的权限

常见安全问题

Q: 为什么不能在渲染进程中直接使用 require?A: 因为这会暴露 Node.js API 给网页代码,可能被恶意代码利用来访问系统资源。

Q: contextIsolation 是如何工作的?A: 它将预加载脚本和渲染进程的 JavaScript 上下文完全隔离,防止渲染进程访问预加载脚本中的对象。

Q: 如何处理用户上传的文件?A: 验证文件类型和大小,将文件存储在隔离的目录中,使用安全的文件名,避免路径遍历攻击。

Q: 是否应该使用 remote 模块?A: 不应该,remote 模块已被弃用,因为它会绕过安全隔离。使用 IPC 代替。

标签:Electron