不能直接用 fs.readFile 在渲染进程读 XML,因 Electron 渲染进程默认禁用 Node.js 模块,require(‘fs’) 不可用;强行启用会破坏沙箱安全,应通过 ipcRenderer.invoke 调用主进程经白名单校验和安全解析的 xml:read 接口。
为什么不能直接用 fs.readFile 在渲染进程读 XML
Electron 渲染进程默认运行在沙箱环境(即使没显式开启 contextIsolation: true),require('fs') 也根本不可用。强行注入 Node.js 模块或关闭 nodeIntegration 都会大幅削弱安全边界,属于高危操作。
常见错误现象包括:ReferenceError: require is not defined、fs is not defined,或更隐蔽的——XML 文件被意外写入恶意内容后,主进程未经校验直接解析,触发 XXE 或路径遍历。
- 渲染进程只能通过
ipcRenderer发起受控请求 - 主进程必须对所有传入路径做白名单校验,禁止用户输入直接拼接
fs调用 - XML 解析必须禁用外部实体(
libxmljs默认启用,fast-xml-parser默认禁用)
如何用 ipcMain.handle 安全暴露 XML 读取能力
主进程应只暴露最小必要接口,且每个调用都需验证路径合法性与文件扩展名。不建议把整个 fs 封装成通用 API。
const { app, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs').promises;
const xmlParser = require('fast-xml-parser');
// 白名单:只允许读取 resources/xml/ 下的 .xml 文件
const ALLOWED_XML_DIR = path.join(app.getAppPath(), 'resources', 'xml');
ipcMain.handle('xml:read', async (event, filename) => {
// 1. 拒绝路径遍历
if (filename.includes('..') || filename.startsWith('/')) {
throw new Error('Invalid filename');
}
const fullPath = path.join(ALLOWED_XML_DIR, filename);
// 2. 确保解析前路径仍在白名单内
const resolved = await fs.realpath(fullPath);
if (!resolved.startsWith(ALLOWED_XML_DIR)) {
throw new Error('Access denied');
}
// 3. 检查扩展名
if (path.extname(fullPath) !== '.xml') {
throw new Error('Only .xml files allowed');
}
try {
const content = await fs.readFile(fullPath, 'utf8');
// 4. 使用安全解析器(不解析 DTD / 外部实体)
const isValid = xmlParser.validate(content);
if (isValid !== true) throw new Error('Invalid XML structure');
return xmlParser.parse(content, {
ignoreAttributes: false,
ignoreNameSpace: true,
allowBooleanAttributes: false,
parseNodeValue: true,
parseAttributeValue: true,
trimValues: true
});
} catch (err) {
throw new Error(`Failed to read/parse ${filename}: ${err.message}`);
}
});
渲染进程中如何调用并处理返回结果
使用 ipcRenderer.invoke 替代 send+on,避免竞态和未处理 promise。注意:返回的是解析后的 JS 对象,不是原始字符串 —— 这意味着你无法再用 DOMParser 处理命名空间等高级特性,但换来的是安全性。