文件上传是Web应用常见的安全攻击面,用户上传的文件可能包含恶意软件、ZIP炸弹或伪造MIME类型。大多数Node.js项目仅做文件扩展名检查,远远不够。pompelmi是一个Node.js库,它能在文件落盘之前完成ClamAV扫描,返回类型化的verdict symbol,不依赖第三方运行时。
工作原理
pompelmi通过Node.js内置的child_process调用clamscan,直接读取退出码并将其映射为Symbol类型的结果。没有stdout解析,没有正则匹配,没有隐式状态,干净利落。
安装依赖
首先安装Node.js库:
然后在操作系统上安装ClamAV:
- macOS:- brew install clamav && freshclam
复制代码 - Debian/Ubuntu:- sudo apt-get install -y clamav clamav-daemon && sudo freshclam
复制代码 - Windows:
基本用法
导入模块并扫描文件:- 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;
- }
复制代码
返回值映射关系:
- Verdict.Clean:ClamAV退出码0,未发现威胁
- Verdict.Malicious:退出码1,匹配到已知病毒签名
- Verdict.ScanError:退出码2,扫描本身失败,文件状态未知
在Express中集成
使用multer处理文件上传,在保存前调用pompelmi扫描:- 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 });
- }
- });
复制代码
扫描失败或检测到恶意文件时,立即删除临时文件并返回422状态码。
远程扫描(Docker环境)
当ClamAV运行在容器中时,通过TCP socket连接:- const result = await scan('/path/to/file.zip', {
- host: '127.0.0.1',
- port: 3310,
- });
复制代码 API保持不变,verdict类型不变,无缝切换。
错误处理
scan函数可能抛出以下异常:
- filePath不是字符串 → 'filePath must be a string'
- 文件不存在 → 'File not found: <path>'
- clamscan不在PATH中 → ENOENT
- 未知退出码 → 'Unexpected exit code: N'
建议在try/catch中处理:- try {
- const result = await scan(path.resolve(filePath));
- return result;
- } catch (err) {
- console.error('扫描异常:', err.message);
- return null;
- }
复制代码
特性总结
- 零运行时依赖,仅使用Node.js内置child_process
- 不解析stdout,直接读取退出码,性能可靠
- 支持TypeScript,verdict为Symbol类型,防止拼写错误
- 支持本地clamscan和远程clamd TCP socket
- 跨平台:macOS、Linux、Windows
通过pompelmi库,开发者可以用最少的代码在文件上传流程中集成ClamAV病毒扫描,提升应用安全性,避免用户上传恶意文件造成危害。 |