Electron 安全模型
Electron 的安全模型基于 Chromium 的多进程架构和 Node.js 的集成。理解安全模型对于构建安全的 Electron 应用至关重要。本文档详细介绍 Electron 的安全架构、核心安全配置及最佳实践。
架构概述
Electron 采用多进程架构,继承自 Chromium 的设计理念。每个进程有明确的职责边界和安全限制。
code
┌─────────────────────────────────────────────────────────────┐
│ Main Process │
│ 主进程 - 单一实例 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 完全访问 Node.js 和 Electron API │ │
│ │ • 管理窗口(BrowserWindow)和应用生命周期 │ │
│ │ • 处理系统级操作(文件、原生菜单、对话框等) │ │
│ │ • 作为唯一可信源处理敏感操作 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ IPC (进程间通信 - 异步消息传递)
▼
┌─────────────────────────────────────────────────────────────┐
│ Preload Script │
│ 预加载脚本 - 每个渲染进程一个 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 在渲染进程创建前执行 │ │
│ │ • 访问有限的 Node.js API 子集 │ │
│ │ • 通过 contextBridge 安全暴露 API 给渲染进程 │ │
│ │ • 作为主进程与渲染进程之间的桥梁 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ contextBridge.exposeInMainWorld()
▼
┌─────────────────────────────────────────────────────────────┐
│ Renderer Process │
│ 渲染进程 - 可多个实例 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • 运行 Web 内容(HTML/CSS/JavaScript) │ │
│ │ • 默认无 Node.js 访问权限 │ │
│ │ • 通过暴露的 API 与主进程通信 │ │
│ │ • 可能加载不可信内容,需要严格隔离 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘安全数据流向
code
┌──────────────┐ IPC 消息验证 ┌──────────────┐
│ Renderer │ ──────────────────► │ Main │
│ (不可信区) │ 严格校验参数 │ (可信区) │
│ │ ◄────────────────── │ │
└──────────────┘ 返回安全数据 └──────────────┘
│ │
│ contextBridge │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Preload.js │ │ Node.js API │
│ (受限访问) │ │ (完全访问) │
└──────────────┘ └──────────────┘进程职责与安全边界
主进程(Main Process)
| 职责 | 说明 | 安全注意 |
|---|---|---|
| 应用生命周期 | 管理 app 模块的生命周期事件 | 避免在 will-quit 前泄露敏感数据 |
| 窗口管理 | 创建和管理 BrowserWindow 实例 | 严格配置 webPreferences |
| IPC 处理 | 处理来自渲染进程的请求 | 验证所有输入参数 |
| 原生集成 | 菜单、托盘、快捷键等 | 权限最小化原则 |
| 文件系统 | 读写文件、访问数据库 | 路径验证、权限控制 |
预加载脚本(Preload Script)
| 职责 | 说明 | 安全注意 |
|---|---|---|
| API 暴露 | 通过 contextBridge 暴露安全 API | 使用白名单机制 |
| 数据转换 | 处理渲染进程与主进程间的数据传递 | 过滤敏感信息 |
| 权限控制 | 限制渲染进程可执行的操作 | 最小暴露原则 |
渲染进程(Renderer Process)
| 职责 | 说明 | 安全注意 |
|---|---|---|
| UI 渲染 | 渲染用户界面 | 防止 XSS 攻击 |
| 用户交互 | 处理用户输入 | 输入验证和清理 |
| 网络请求 | 发送 HTTP 请求(如有权限) | 使用 HTTPS |
核心安全配置
配置参数总览
| 参数 | 推荐值 | 默认值 | 说明 |
|---|---|---|---|
contextIsolation | true | true (v12+) | 上下文隔离 |
nodeIntegration | false | false | Node.js 集成 |
sandbox | true | true (v20+) | 沙箱模式 |
webSecurity | true | true | Web 安全策略 |
allowRunningInsecureContent | false | false | 允许混合内容 |
enableRemoteModule | false | false (v14+) | 远程模块 |
nodeIntegrationInWorker | false | false | Worker 中 Node 集成 |
nodeIntegrationInSubFrames | false | false | 子框架中 Node 集成 |
1. contextIsolation(上下文隔离)
状态:从 Electron v12 开始默认启用
必须启用,这是最重要的安全设置:
javascript
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true // 默认为 true,强烈建议保持
}
})工作原理
code
┌─────────────────────────────────────────────────────────────┐
│ Preload Context │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ const secret = 'SENSITIVE_DATA' │ │
│ │ const ipcRenderer = require('electron').ipcRenderer │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ contextBridge (只读暴露) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ window.safeAPI = { │ │
│ │ sendMessage: (msg) => ipcRenderer.send(msg) │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ 隔离边界(无法穿越)
▼
┌─────────────────────────────────────────────────────────────┐
│ Renderer Context │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ // 无法访问 secret 或 ipcRenderer │ │
│ │ window.safeAPI.sendMessage('hello') // ✓ 可用 │ │
│ │ console.log(secret) // ✗ undefined │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘防护效果
- ✅ 隔离 preload 脚本和渲染进程的 JavaScript 上下文
- ✅ 防止渲染进程直接访问 preload 中的变量
- ✅ 防止原型污染攻击
- ✅ 阻止渲染进程修改预加载脚本的全局对象
2. nodeIntegration(Node.js 集成)
状态:默认禁用
必须禁用:
javascript
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: false // 默认为 false,必须保持
}
})危险示例
javascript
// ❌ 极度危险配置
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true, // 允许渲染进程访问 Node.js
contextIsolation: false, // 无上下文隔离
webSecurity: false // 禁用同源策略
}
})
win.loadURL('https://untrusted-site.com')
// 后果:远程网站可执行任意系统命令
// <script>require('child_process').exec('rm -rf /')</script>禁用原因
| 风险 | 说明 |
|---|---|
| 远程代码执行 | 加载远程内容时可执行任意 Node.js 代码 |
| 文件系统访问 | 可读写任意本地文件 |
| 系统命令执行 | 可执行 shell 命令 |
| 进程管理 | 可创建子进程 |
3. sandbox(沙箱模式)
状态:从 Electron v20 开始默认启用
推荐启用:
javascript
const win = new BrowserWindow({
webPreferences: {
sandbox: true // 启用沙箱
}
})沙箱限制
code
┌─────────────────────────────────────────────────────────────┐
│ Sandbox 环境 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ✗ 无文件系统访问 (fs) │ │
│ │ ✗ 无子进程创建 (child_process) │ │
│ │ ✗ 无原生模块加载 │ │
│ │ ✗ 无系统信息获取 (os) │ │
│ │ ✓ 仅可通过 IPC 与主进程通信 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘Preload 脚本在沙箱中的行为
javascript
// preload.js (sandbox: true 时)
const { contextBridge, ipcRenderer } = require('electron')
// ✅ 可用:ipcRenderer
// ✅ 可用:contextBridge
// ✅ 可用:process 对象(有限)
// ❌ 不可用:fs、child_process、原生模块
contextBridge.exposeInMainWorld('electronAPI', {
readFile: (path) => ipcRenderer.invoke('file:read', path)
})4. webSecurity(Web 安全策略)
状态:默认启用
不要禁用:
javascript
const win = new BrowserWindow({
webPreferences: {
webSecurity: true // 默认为 true,不要禁用
}
})禁用后果
javascript
// ❌ 禁用 webSecurity 后
// 1. 同源策略失效
// 2. 可跨域读取任意网站数据
// 3. 可加载混合内容(HTTP + HTTPS)
// 4. 易受 CSRF 攻击正确处理跨域
javascript
// ✅ 使用主进程代理跨域请求
// main.js
ipcMain.handle('api:fetch', async (event, url, options) => {
const response = await fetch(url, options)
return response.json()
})
// preload.js
contextBridge.exposeInMainWorld('api', {
fetch: (url, options) => ipcRenderer.invoke('api:fetch', url, options)
})安全的窗口配置
推荐配置模板
javascript
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// === 安全配置(必须)===
contextIsolation: true, // 上下文隔离
nodeIntegration: false, // 禁用 Node.js 集成
webSecurity: true, // 启用 Web 安全
allowRunningInsecureContent: false, // 禁止混合内容
enableRemoteModule: false, // 禁用远程模块
// === 安全配置(推荐)===
sandbox: true, // 启用沙箱
nodeIntegrationInWorker: false, // 禁用 Worker Node 集成
nodeIntegrationInSubFrames: false, // 禁用子框架 Node 集成
// === 预加载脚本 ===
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(createWindow)不同场景的配置
场景一:纯本地应用
javascript
// 加载本地 HTML,无远程内容
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')场景二:加载可信远程内容
javascript
// 加载可信的远程服务
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
webSecurity: true,
// 不设置 preload,或仅暴露有限 API
}
})
win.loadURL('https://trusted-app.example.com')场景三:开发环境
javascript
// 开发时可能需要额外配置
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: path.join(__dirname, 'preload.js')
}
})
// 仅开发环境启用 DevTools
if (process.env.NODE_ENV === 'development') {
win.webContents.openDevTools()
}安全的 IPC 通信
通信模式
code
┌──────────────┐ ┌──────────────┐
│ Renderer │ │ Main │
│ │ ipcRenderer.invoke() │ │
│ │ ──────────────────────► │ │
│ │ ipcMain.handle() │ │
│ │ ◄────────────────────── │ │
│ │ 返回结果 │ │
└──────────────┘ └──────────────┘Preload 脚本安全模式
javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
// 定义允许的通道白名单
const ALLOWED_CHANNELS = {
invoke: ['dialog:open', 'file:read', 'file:write', 'app:getVersion'],
send: ['window:minimize', 'window:maximize', 'window:close'],
on: ['update:available', 'update:progress', 'notification:show']
}
// 验证通道是否在白名单中
function validateChannel(type, channel) {
if (!ALLOWED_CHANNELS[type]?.includes(channel)) {
console.error(`Blocked invalid IPC channel: ${channel}`)
return false
}
return true
}
// 安全暴露 API
contextBridge.exposeInMainWorld('electronAPI', {
// 双向通信(返回 Promise)
invoke: (channel, ...args) => {
if (!validateChannel('invoke', channel)) {
return Promise.reject(new Error(`Invalid channel: ${channel}`))
}
return ipcRenderer.invoke(channel, ...args)
},
// 单向发送
send: (channel, ...args) => {
if (!validateChannel('send', channel)) {
return
}
ipcRenderer.send(channel, ...args)
},
// 监听事件
on: (channel, callback) => {
if (!validateChannel('on', channel)) {
return () => {}
}
const subscription = (event, ...args) => callback(...args)
ipcRenderer.on(channel, subscription)
// 返回取消订阅函数
return () => ipcRenderer.removeListener(channel, subscription)
},
// 一次性监听
once: (channel, callback) => {
if (!validateChannel('on', channel)) {
return
}
ipcRenderer.once(channel, (event, ...args) => callback(...args))
}
})主进程验证
javascript
// main.js
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('path')
const fs = require('fs')
// 允许访问的基础目录
const ALLOWED_BASE_PATHS = [
app.getPath('documents'),
app.getPath('downloads'),
app.getPath('userData')
]
// 验证发送者
function isValidSender(webContentsId) {
// 实现你的验证逻辑,例如检查是否为已知窗口
return true
}
// 验证路径安全性
function isPathSafe(requestedPath, basePaths = ALLOWED_BASE_PATHS) {
const normalizedPath = path.normalize(requestedPath)
return basePaths.some(basePath =>
normalizedPath.startsWith(path.normalize(basePath))
)
}
// 文件读取处理器
ipcMain.handle('file:read', async (event, filePath) => {
// 1. 验证来源
if (!isValidSender(event.sender.id)) {
throw new Error('Unauthorized sender')
}
// 2. 验证参数类型
if (typeof filePath !== 'string' || filePath.length === 0) {
throw new TypeError('filePath must be a non-empty string')
}
// 3. 路径安全检查
const resolvedPath = path.resolve(filePath)
if (!isPathSafe(resolvedPath)) {
throw new Error(`Access denied: path outside allowed directories`)
}
// 4. 检查文件是否存在
if (!fs.existsSync(resolvedPath)) {
throw new Error(`File not found: ${resolvedPath}`)
}
// 5. 执行操作
try {
const content = await fs.promises.readFile(resolvedPath, 'utf-8')
return { success: true, content }
} catch (error) {
throw new Error(`Failed to read file: ${error.message}`)
}
})
// 文件写入处理器
ipcMain.handle('file:write', async (event, filePath, content) => {
// 参数验证
if (typeof filePath !== 'string') {
throw new TypeError('filePath must be a string')
}
if (typeof content !== 'string') {
throw new TypeError('content must be a string')
}
// 路径验证
const resolvedPath = path.resolve(filePath)
if (!isPathSafe(resolvedPath)) {
throw new Error('Access denied: path outside allowed directories')
}
// 写入文件
await fs.promises.writeFile(resolvedPath, content, 'utf-8')
return { success: true }
})
// 对话框处理器
ipcMain.handle('dialog:open', async (event, options = {}) => {
const result = await dialog.showOpenDialog({
title: options.title || '选择文件',
defaultPath: options.defaultPath,
filters: options.filters || [{ name: 'All Files', extensions: ['*'] }],
properties: ['openFile', ...(options.multiSelections ? ['multiSelections'] : [])]
})
// 返回选择的文件路径(已经是绝对路径)
return result.filePaths
})渲染进程使用
javascript
// renderer.js
async function readFile() {
try {
const result = await window.electronAPI.invoke('file:read', '/path/to/file.txt')
console.log('File content:', result.content)
} catch (error) {
console.error('Failed to read file:', error.message)
}
}
// 监听事件(返回取消订阅函数)
const unsubscribe = window.electronAPI.on('update:available', (info) => {
console.log('Update available:', info)
})
// 取消监听
// unsubscribe()内容安全策略(CSP)
什么是 CSP
内容安全策略(Content Security Policy)是一层额外的安全保护,用于限制资源加载来源,防止 XSS 和数据注入攻击。
设置方式
方式一:HTML Meta 标签
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
font-src 'self' data:;
">
<title>My Electron App</title>
</head>
<body>
<!-- 内容 -->
</body>
</html>方式二:主进程设置
javascript
const { session } = require('electron')
// 为默认会话设置 CSP
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: https:; " +
"connect-src 'self' https://api.example.com;"
]
}
})
})CSP 指令详解
| 指令 | 说明 | 示例 |
|---|---|---|
default-src | 默认资源来源 | 'self' 仅限同源 |
script-src | JavaScript 来源 | 'self' 不允许内联脚本 |
style-src | CSS 来源 | 'self' 'unsafe-inline' |
img-src | 图片来源 | 'self' data: https: |
connect-src | 网络请求目标 | 'self' https://api.example.com |
font-src | 字体来源 | 'self' data: |
media-src | 媒体来源 | 'self' |
object-src | 插件来源 | 'none' 禁止插件 |
不同场景的 CSP 配置
严格模式(推荐用于本地应用)
html
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
connect-src 'self';
font-src 'self';
">开发模式
html
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws://localhost:* http://localhost:*;
">允许特定 CDN
html
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
">远程内容安全
安全原则
- 避免加载不受信任的远程内容
- 如需加载,使用严格的安全配置
- 验证所有导航和窗口创建请求
危险示例
javascript
// ❌ 极度危险:加载远程内容且启用 Node.js
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
win.loadURL('https://untrusted-site.com')
// 攻击者可在网页中执行:require('child_process').exec('malware')安全示例
javascript
// ✅ 安全:加载本地内容
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
// ✅ 如必须加载远程内容,使用严格限制
const remoteWin = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
webSecurity: true,
enableRemoteModule: false,
// 不设置 preload,或仅暴露非常有限的 API
}
})
remoteWin.loadURL('https://trusted-site.com')导航验证
javascript
const { app, shell } = require('electron')
// 全局导航控制
app.on('web-contents-created', (event, contents) => {
// 阻止导航到非白名单 URL
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl)
// 白名单检查
const allowedOrigins = [
'https://myapp.com',
'https://api.myapp.com'
]
if (!allowedOrigins.includes(parsedUrl.origin)) {
console.warn(`Blocked navigation to: ${navigationUrl}`)
event.preventDefault()
}
})
// 控制新窗口打开
contents.setWindowOpenHandler(({ url }) => {
// 在默认浏览器中打开外部链接
if (url.startsWith('http://') || url.startsWith('https://')) {
shell.openExternal(url)
}
// 阻止 Electron 创建新窗口
return { action: 'deny' }
})
// 阻止 iframe 嵌入不可信内容
contents.on('will-attach-webview', (event, webPreferences, params) => {
// 移除 preload 路径
delete webPreferences.preload
// 禁用 Node.js
webPreferences.nodeIntegration = false
webPreferences.contextIsolation = true
})
})常见攻击向量与防护
1. XSS(跨站脚本攻击)
javascript
// ❌ 危险:直接插入用户内容
document.innerHTML = userInput
element.insertAdjacentHTML('beforeend', userInput)
// ✅ 安全:使用 textContent
element.textContent = userInput
// ✅ 安全:使用 DOM API
const div = document.createElement('div')
div.textContent = userInput
container.appendChild(div)XSS 防护清单
- 使用
textContent而非innerHTML - 对用户输入进行转义
- 设置严格的 CSP
- 启用
contextIsolation - 禁用
nodeIntegration
2. 原型污染攻击
javascript
// ❌ 危险:可被污染原型
const obj = {}
obj.__proto__.polluted = true // 污染 Object.prototype
// ✅ 安全:使用 Object.create(null)
const obj = Object.create(null)
// ✅ 安全:使用 Map 代替普通对象
const map = new Map()
map.set('key', 'value')原型污染防护
javascript
// 在 preload.js 中,contextIsolation 已提供保护
// 但仍需注意以下情况:
// ❌ 危险:直接合并用户输入
Object.assign(target, userInput)
// ✅ 安全:使用 Object.defineProperty
Object.defineProperty(target, key, {
value: userInput[key],
writable: true,
configurable: true,
enumerable: true
})
// ✅ 安全:使用深度克隆
function safeClone(obj) {
return JSON.parse(JSON.stringify(obj))
}3. 路径遍历攻击
javascript
// ❌ 危险:未验证路径
const content = fs.readFileSync(userInput, 'utf-8')
// userInput 可能是 "../../../../etc/passwd"
// ✅ 安全:验证路径
const path = require('path')
function safeReadFile(userInput, baseDir) {
// 解析为绝对路径
const resolvedPath = path.resolve(baseDir, userInput)
// 确保路径在允许的目录内
if (!resolvedPath.startsWith(path.resolve(baseDir))) {
throw new Error('Access denied: path outside base directory')
}
return fs.readFileSync(resolvedPath, 'utf-8')
}4. 代码注入攻击
javascript
// ❌ 危险:执行动态代码
eval(userInput)
new Function(userInput)()
setTimeout(userInput, 0)
setInterval(userInput, 0)
// ✅ 安全:避免执行动态代码
// 使用预定义的函数映射
const actions = {
'save': saveFile,
'load': loadFile,
'delete': deleteFile
}
function executeAction(actionName, ...args) {
const action = actions[actionName]
if (typeof action === 'function') {
return action(...args)
}
throw new Error(`Unknown action: ${actionName}`)
}5. 中间人攻击
javascript
// ❌ 危险:加载 HTTP 内容
win.loadURL('http://example.com')
// ❌ 危险:禁用证书验证
app.commandLine.appendSwitch('ignore-certificate-errors')
// ✅ 安全:仅加载 HTTPS 内容
win.loadURL('https://example.com')
// ✅ 安全:证书验证(生产环境)
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
// 仅信任特定证书
if (certificate.issuerName === 'My Trusted CA') {
callback(true) // 信任
} else {
callback(false) // 拒绝
}
event.preventDefault()
})安全检查清单
必须项(Critical)
| 检查项 | 说明 | 验证方式 |
|---|---|---|
contextIsolation: true | 上下文隔离 | 检查 webPreferences |
nodeIntegration: false | 禁用 Node.js 集成 | 检查 webPreferences |
webSecurity: true | 启用 Web 安全 | 检查 webPreferences |
enableRemoteModule: false | 禁用远程模块 | 检查 webPreferences |
使用 contextBridge | 安全暴露 API | 检查 preload 脚本 |
| IPC 通道白名单 | 限制可用的 IPC 通道 | 检查 preload 脚本 |
| 设置 CSP | 内容安全策略 | 检查 HTML 或主进程 |
推荐项(Recommended)
| 检查项 | 说明 |
|---|---|
sandbox: true | 启用沙箱模式 |
| 验证 IPC 参数 | 主进程验证所有输入 |
| 限制文件访问路径 | 使用白名单目录 |
| 导航验证 | 监听 will-navigate |
| 新窗口控制 | 使用 setWindowOpenHandler |
禁止项(Forbidden)
| 检查项 | 风险 |
|---|---|
❌ contextIsolation: false | 原型污染、API 泄露 |
❌ nodeIntegration: true | 远程代码执行 |
❌ webSecurity: false | 同源策略失效 |
❌ enableRemoteModule: true | 安全漏洞 |
❌ 暴露整个 ipcRenderer | 权限过大 |
| ❌ 加载不信任的远程内容 | 远程代码执行 |
常见问题解答
Q1: 为什么禁用 nodeIntegration 后,渲染进程无法使用 require?
A: 这是安全设计的预期行为。渲染进程不应直接访问 Node.js API。正确做法是通过 preload 脚本和 contextBridge 暴露需要的 API:
javascript
// preload.js
const { contextBridge } = require('electron')
const fs = require('fs')
contextBridge.exposeInMainWorld('nodeAPI', {
readFile: (path) => fs.readFileSync(path, 'utf-8')
})Q2: sandbox: true 和 sandbox: false 有什么区别?
A:
| 特性 | sandbox: false | sandbox: true |
|---|---|---|
| Preload 脚本 Node.js 访问 | 完整 | 受限 |
| 原生模块 | 可用 | 不可用 |
| 安全性 | 较低 | 更高 |
推荐启用沙箱,需要在主进程处理文件系统等操作。
Q3: 如何安全地加载第三方网页?
A:
javascript
// 创建隔离的 BrowserView 或 BrowserWindow
const win = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
webSecurity: true,
// 不设置 preload
}
})
// 设置导航限制
win.webContents.on('will-navigate', (event) => {
event.preventDefault()
})
win.loadURL('https://trusted-site.com')Q4: CSP 设置后部分功能不工作怎么办?
A: 检查浏览器控制台的 CSP 错误信息,逐步放宽限制:
html
<!-- 调试时可以先使用宽松策略 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self' 'unsafe-inline' 'unsafe-eval';
connect-src 'self' *;
">
<!-- 生产环境逐步收紧 -->Q5: 如何验证应用的安全性?
A: 使用以下工具和方法:
- Electron Security Warnings: 开发时会在控制台显示安全警告
- Electronegativity: Electron 应用安全审计工具
bash
npx @doyensec/electronegativity -i ./app- 代码审计: 检查所有
BrowserWindow配置 - 渗透测试: 尝试 XSS、路径遍历等攻击
Q6: contextBridge 暴露的 API 可以被篡改吗?
A: 不可以。contextBridge 暴露的值是只读的,渲染进程无法修改:
javascript
// preload.js
contextBridge.exposeInMainWorld('api', {
secret: 'value'
})
// renderer.js
window.api.secret = 'hacked' // 静默失败,值不变
console.log(window.api.secret) // 仍然是 'value'
delete window.api.secret // 静默失败