文件上传是常见的攻击面。用户上传的文件可能包含恶意软件、ZIP 炸弹或 伪造的 MIME 类型。大多数 Node.js 项目只做扩展名检查,这远远不够。
pompelmi 是一个 Node.js 库,在文件落盘之前完成扫描,返回类型化的 verdict symbol,不依赖任何第三方运行时。
GitHub: github.com/pompelmi/po…
工作原理
- 验证参数是否为字符串,文件是否存在
- 通过 child_process 调用 clamscan,读取退出码
- 将退出码映射为 Symbol
没有 stdout 解析,没有正则,没有隐式状态。
安装
需要 Node.js 和 ClamAV。
npm install pompelmi
安装 ClamAV:
# macOS
brew install clamav && freshclam
# Debian / Ubuntu
sudo apt-get install -y clamav clamav-daemon && sudo freshclam
# Windows
choco install clamav -y
基本用法
const { scan, Verdict } = require('pompelmi');
const result = await scan('/path/to/file.zip');
switch (result) {
case Verdict.Clean:
// 文件安全,继续处理
break;
case Verdict.Malicious:
throw new Error('检测到恶意软件,文件已拒绝');
case Verdict.ScanError:
// 扫描未完成,按不可信文件处理
console.warn('扫描失败,拒绝文件');
break;
}
返回值:
| 结果 | ClamAV 退出码 | 含义 |
|---|---|---|
Verdict.Clean | 0 | 未发现威胁 |
Verdict.Malicious | 1 | 匹配到已知病毒签名 |
Verdict.ScanError | 2 | 扫描本身失败,文件状态未知 |
在 Express 中集成
const express = require('express');
const multer = require('multer');
const { scan, Verdict } = require('pompelmi');
const path = require('path');
const fs = require('fs');
const app = express();
const upload = multer({ dest: 'tmp/' });
app.post('/upload', upload.single('file'), async (req, res) => {
const filePath = path.resolve(req.file.path);
try {
const result = await scan(filePath);
if (result === Verdict.Malicious) {
fs.unlinkSync(filePath);
return res.status(422).json({ error: '文件包含恶意软件' });
}
if (result === Verdict.ScanError) {
fs.unlinkSync(filePath);
return res.status(422).json({ error: '扫描失败,文件已拒绝' });
}
// Verdict.Clean — 继续保存文件
return res.status(200).json({ verdict: 'clean' });
} catch (err) {
fs.unlinkSync(filePath);
return res.status(500).json({ error: err.message });
}
});
远程扫描(Docker)
如果 ClamAV 运行在容器中,通过 TCP socket 连接:
const result = await scan('/path/to/file.zip', {
host: '127.0.0.1',
port: 3310,
});
API 保持不变,verdict 类型不变。
错误处理
try {
const result = await scan(path.resolve(filePath));
return result;
} catch (err) {
// filePath 不是字符串 → 'filePath must be a string'
// 文件不存在 → 'File not found: <path>'
// clamscan 不在 PATH 中 → ENOENT
// 未知退出码 → 'Unexpected exit code: N'
console.error('扫描异常:', err.message);
return null;
}
特性
- 零运行时依赖,仅使用 Node.js 内置 child_process
- 不解析 stdout,直接读取退出码
- 支持 TypeScript,verdict 为 Symbol 类型,防止拼写错误
- 支持本地 clamscan 和远程 clamd TCP socket
- 跨平台:macOS、Linux、Windows
相关链接
- GitHub: github.com/pompelmi/po…
- npm: www.npmjs.com/package/pom…
- 文档: pompelmi.app