pompelmi
Node.js 安全文件上传扫描
隐私优先的恶意软件检测,支持 YARA、ZIP 炸弹防护和框架适配器
在文件写入磁盘前扫描 • 保持用户数据私密 • 零云依赖
📚 文档 •
💾 安装 •
⚡ 快速开始 •
🧩 适配器 •
🧬 YARA •
🤖 CI/CD
覆盖率徽章反映核心库(src/**);适配器单独计算。
## 📦 安装说明
```
npm install pompelmi
```
## ⚡ 快速开始
扫描文件并在三行代码内处理结果:
```
import { scanFile } from 'pompelmi';
const result = await scanFile('path/to/upload.pdf');
// result.verdict → "clean" | "suspicious" | "malicious"
if (result.verdict !== 'clean') {
console.error('Blocked:', result.verdict, result.reasons);
} else {
console.log('Safe to process.');
}
```
就是这样。无需服务器,无框架依赖 —— 可在任何 Node.js 脚本或服务中独立运行。
## 🎬 演示

**想立即尝试?** 查看我们的[在线示例](./examples/)或本地安装运行:
```
npm i pompelmi @pompelmi/express-middleware
```
## ✨ 功能特性
**pompelmi** 为 Node.js 应用提供企业级文件扫描:
- **🔒 隐私优先架构** — 所有扫描均在进程内进行。**无云端调用,无数据泄露。** 您的文件永远不会离开您的基础设施。
- **⚡ 极速** — 进程内扫描,**零网络延迟**。可为高吞吐量场景配置并发。
- **🧩 可组合扫描器** — 混合使用启发式和签名;设置 `stopOn` 和超时。自带 YARA 规则。
- **📦 深度 ZIP 检测** — 目录遍历/炸弹防护、多态文件和宏提示、嵌套归档扫描(可配置深度限制)。
- **🔌 框架适配器** — 支持 Express、Koa、Fastify、Next.js、Nuxt/Nitro 和 **NestJS** 的开箱即用中间件,提供一流的 TypeScript 支持。
- **🌊 基于流的处理** — 内存高效的扫描,支持可配置的缓冲区限制。无需将大文件完全加载到内存中即可扫描。
- **🔍 多态文件检测** — 高级魔数分析可检测混合格式文件和嵌入式脚本,支持 **30+ 种文件签名**。
- **⚙️ CI/CD CLI** — 独立的命令行工具,用于扫描文件和目录,支持监视模式和多种输出格式。
- **📘 TypeScript 优先** — 完整的类型定义,现代 ESM/CJS 构建,极简接口,支持摇树优化。
- **⚡ 零核心依赖** — 核心库依赖极少,可快速安装并降低供应链风险。
## 目录
- [安装说明](#-installation)
- [快速开始](#-quickstart)
- [演示](#-demo)
- [功能特性](#-features)
- [为什么选择 pompelmi?](#-why-pompelmi)
- [使用场景](#-use-cases)
- [入门指南](#-getting-started)
- [代码示例](#-code-examples)
- [适配器](#-adapters)
- [GitHub Action](#-github-action)
- [图表](#️-diagrams)
- [配置](#️-configuration)
- [生产环境检查清单](#-production-checklist)
- [YARA 入门](#-yara-getting-started)
- [安全说明](#-security-notes)
- [发布与安全](#-releases--security)
- [社区与致谢](#-community--recognition)
- [常见问题](#-faq)
- [测试与覆盖率](#-tests--coverage)
- [贡献](#-contributing)
- [许可证](#-license)
## 🌍 翻译
pompelmi 文档提供多种语言版本,以帮助全球开发者:
- 🇮🇹 **[Italiano (Italian)](docs/i18n/README.it.md)** — Documentazione completa in italiano
- 🇫🇷 **[Français (French)](docs/i18n/README.fr.md)** — Documentation complète en français
- 🇪🇸 **[Español (Spanish)](docs/i18n/README.es.md)** — Documentación completa en español
- 🇩🇪 **[Deutsch (German)](docs/i18n/README.de.md)** — Vollständige Dokumentation auf Deutsch
- 🇯🇵 **[日本語 (Japanese)](docs/i18n/README.ja.md)** — 日本語による完全なドキュメント
- 🇨🇳 **[简体中文 (Simplified Chinese)](docs/i18n/README.zh-CN.md)** — 完整的简体中文文档
- 🇰🇷 **[한국어 (Korean)](docs/i18n/README.ko.md)** — 완전한 한국어 문서
- 🇧🇷 **[Português (Brasil)](docs/i18n/README.pt-BR.md)** — Documentação completa em português
- 🇷🇺 **[Русский (Russian)](docs/i18n/README.ru.md)** — Полная документация на русском
- 🇹🇷 **[Türkçe (Turkish)](docs/i18n/README.tr.md)** — Türkçe tam dokümantasyon
**帮助改进翻译:** 我们欢迎各方贡献以改进和维护翻译。英文 README 是权威来源。如需贡献,请提交包含您改进内容的 Pull Request。
## 🧠 为什么选择 pompelmi?
pompelmi 提供**隐私优先**的恶意软件检测,具有**零云依赖** —— 保持数据安全,延迟为零。
### 为什么选择 Pompelmi?
- **设备端、私密扫描** – 无外部调用,无数据共享。
- **早期阻断** – 在写入磁盘或持久化*之前*运行。
- **适配您的技术栈** – Express、Koa、Next.js、Nuxt/Nitro 的开箱即用适配器(Fastify 插件处于 alpha 阶段)。
- **纵深防御** – ZIP 遍历限制、比例上限、服务端 MIME 嗅探、大小上限。
- **可插拔检测** – 通过简单的 `{ scan(bytes) }` 接口引入您自己的引擎(如 YARA)。
### 适用人群?
- 无法将上传文件发送给第三方 AV API 的团队。
- 需要可预测、低延迟内联决策的应用。
- 想要简单、类型化构建模块而非守护进程的开发者。
### 对比表
| 功能 | **Pompelmi** | ClamAV | 云 API (VirusTotal 等) |
|---------|-------------|---------|-------------------------------|
| **设置时间** | ⚡ 秒级(`npm install`) | ⏱️ 复杂(守护进程设置) | ⏱️ API 密钥 + 集成 |
| **隐私** | ✅ **进程内**(数据不外传) | ✅ 本地(独立守护进程) | ❌ **外部**(数据发送到云端) |
| **延迟** | ⚡ **零**(无网络调用) | 🔄 IPC 开销 | 🌐 **高**(网络往返) |
| **成本** | 💰 **免费**(MIT 许可证) | 💰 免费(GPL) | 💸 **按次付费** |
| **框架集成** | ✅ Express, Koa, Next.js, NestJS | ❌ 手动集成 | ❌ 手动集成 |
| **TypeScript 支持** | ✅ 一流支持 | ❌ 社区类型 | ❓ 不一 |
| **YARA 集成** | ✅ 内置 | ⚙️ 手动设置 | ❓ 有限 |
### 🎯 开发者体验
从第一天起就为开发者着想。简单的 API、全面的 TypeScript 类型和出色的文档意味着您可以在几分钟而不是几天内集成安全文件扫描。
### 🚀 性能优先
针对高吞吐量场景进行了优化,支持可配置的并发、流式处理和最小的内存开销。扫描在进程内运行,无 IPC 开销。
### 🔐 安全不妥协
多层防御包括 MIME 类型验证(魔数)、扩展名验证、大小限制、ZIP 炸弹保护和可选的 YARA 集成。每一层都是可配置的,以匹配您的威胁模型。
### 🌍 隐私保证
您的数据永远不会离开您的基础设施。无遥测,无云依赖,无第三方 API 调用。非常适合受监管行业(医疗保健、金融、政府)和注重隐私的应用程序。
## 💡 使用场景
pompelmi 受到各行各业和场景的信赖:
### 🏥 医疗保健(HIPAA 合规)
扫描患者文档上传,而无需将 PHI 发送到第三方服务。在您的基础设施上保持医疗记录和影像文件的安全。
### 🏦 金融服务(PCI DSS)
验证客户文档上传(ID 验证、税务表格),而不将敏感财务数据暴露给外部 API。
### 🎓 教育平台
保护学习管理系统免受恶意文件上传,同时维护学生隐私。
### 🏢 企业文档管理
在企业文件共享平台、维基和协作工具的摄入时间扫描文件。
### 🎨 媒体与创意平台
在处理和存储之前验证用户生成的内容上传(图像、视频、文档)。
## 🚀 入门指南
使用 pompelmi 的零配置默认值,在 5 分钟内实现安全文件扫描。
### 第 1 步:创建安全策略
创建可重用的安全策略和扫描器配置。
```
// lib/security.ts
import { CommonHeuristicsScanner, createZipBombGuard, composeScanners } from 'pompelmi';
// Optional: import types for explicit annotation
// import type { NamedScanner, ComposeScannerOptions } from 'pompelmi';
export const policy = {
includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt'],
allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
maxFileSizeBytes: 20 * 1024 * 1024, // 20MB
timeoutMs: 5000,
concurrency: 4,
failClosed: true, // Block uploads on scanner errors
onScanEvent: (event: unknown) => console.log('[scan]', event)
};
export const scanner = composeScanners(
[
['zipGuard', createZipBombGuard({
maxEntries: 512,
maxTotalUncompressedBytes: 100 * 1024 * 1024,
maxCompressionRatio: 12
})],
['heuristics', CommonHeuristicsScanner],
// Add your own scanners or YARA rules here
],
{
parallel: false,
stopOn: 'suspicious',
timeoutMsPerScanner: 1500,
tagSourceName: true
}
);
```
### 第 2 步:选择您的集成方式
选择与您的框架匹配的集成:
#### Express
```
import express from 'express';
import multer from 'multer';
import { createUploadGuard } from '@pompelmi/express-middleware';
import { policy, scanner } from './lib/security';
const app = express();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: policy.maxFileSizeBytes }
});
app.post('/upload',
upload.any(),
createUploadGuard({ ...policy, scanner }),
(req, res) => {
// File is safe - proceed with your logic
res.json({
success: true,
verdict: (req as any).pompelmi?.verdict || 'clean'
});
}
);
app.listen(3000, () => console.log('🚀 Server running on http://localhost:3000'));
```
#### Next.js App Router
```
// app/api/upload/route.ts
import { createNextUploadHandler } from '@pompelmi/next-upload';
import { policy, scanner } from '@/lib/security';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const POST = createNextUploadHandler({ ...policy, scanner });
```
#### Koa
```
import Koa from 'koa';
import Router from '@koa/router';
import multer from '@koa/multer';
import { createKoaUploadGuard } from '@pompelmi/koa-middleware';
import { policy, scanner } from './lib/security';
const app = new Koa();
const router = new Router();
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: policy.maxFileSizeBytes }
});
router.post('/upload',
upload.any(),
createKoaUploadGuard({ ...policy, scanner }),
(ctx) => {
ctx.body = {
success: true,
verdict: (ctx as any).pompelmi?.verdict || 'clean'
};
}
);
app.use(router.routes()).use(router.allowedMethods());
app.listen(3003, () => console.log('🚀 Server running on http://localhost:3003'));
```
#### 独立/编程方式
```
import { scanFile } from 'pompelmi';
const result = await scanFile('path/to/file.zip');
console.log(result.verdict); // "clean" | "suspicious" | "malicious"
if (result.verdict === 'malicious') {
console.error('⚠️ Malicious file detected!');
console.error(result.reasons);
}
```
### 第 3 步:测试
上传测试文件以验证一切正常:
```
curl -X POST http://localhost:3000/upload \
-F "file=@test.pdf"
```
✅ **完成!** 您的应用现在已具备安全文件上传扫描功能。
## 📘 代码示例
### 示例 1:带自定义错误处理的 Express
```
import express from 'express';
import multer from 'multer';
import { createUploadGuard } from '@pompelmi/express-middleware';
import { policy, scanner } from './lib/security';
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
app.post('/upload',
upload.single('file'),
createUploadGuard({ ...policy, scanner }),
(req, res) => {
const scanResult = (req as any).pompelmi;
if (scanResult?.verdict === 'malicious') {
return res.status(422).json({
error: 'Malicious file detected',
reasons: scanResult.reasons
});
}
if (scanResult?.verdict === 'suspicious') {
// Log for review but allow upload
console.warn('Suspicious file uploaded:', req.file?.originalname);
}
// Process clean file
res.json({ success: true, fileName: req.file?.originalname });
}
);
app.listen(3000);
```
### 示例 2:带自定义响应的 Next.js 路由处理器
```
// app/api/scan/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { scanBuffer } from 'pompelmi';
import { scanner } from '@/lib/security';
export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
}
const buffer = Buffer.from(await file.arrayBuffer());
const result = await scanner.scan(buffer);
return NextResponse.json({
fileName: file.name,
verdict: result.verdict,
safe: result.verdict === 'clean',
reasons: result.reasons || []
});
}
```
### 示例 3:NestJS 控制器
```
// app.module.ts
import { Module } from '@nestjs/common';
import { PompelmiModule } from '@pompelmi/nestjs-integration';
import { CommonHeuristicsScanner } from 'pompelmi';
@Module({
imports: [
PompelmiModule.forRoot({
includeExtensions: ['pdf', 'zip', 'png', 'jpg'],
allowedMimeTypes: ['application/pdf', 'application/zip', 'image/png', 'image/jpeg'],
maxFileSizeBytes: 10 * 1024 * 1024,
scanners: [CommonHeuristicsScanner],
}),
],
})
export class AppModule {}
// upload.controller.ts
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { PompelmiInterceptor, PompelmiResult } from '@pompelmi/nestjs-integration';
@Controller('upload')
export class UploadController {
@Post()
@UseInterceptors(FileInterceptor('file'), PompelmiInterceptor)
async uploadFile(@UploadedFile() file: Express.Multer.File & { pompelmi?: PompelmiResult }) {
if (file.pompelmi?.verdict === 'malicious') {
throw new BadRequestException('Malicious file detected');
}
return {
success: true,
verdict: file.pompelmi?.verdict,
fileName: file.originalname
};
}
}
```
## 🤖 GitHub Action
在 CI 中运行 **pompelmi** 以扫描仓库文件或构建产物。
**最小化用法**
```
name: Security scan (pompelmi)
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan repository with pompelmi
uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
with:
path: .
deep_zip: true
fail_on_detect: true
```
**扫描单个产物**
```
- uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
with:
artifact: build.zip
deep_zip: true
fail_on_detect: true
```
**输入参数**
| 输入 | 默认值 | 描述 |
| --- | --- | --- |
| `path` | `.` | 要扫描的目录。 |
| `artifact` | `""` | 要扫描的单个文件/归档。 |
| `yara_rules` | `""` | YARA 规则的 glob 路径(例如 `rules/*.yar`)。 |
| `deep_zip` | `true` | 启用深度嵌套归档检测。 |
| `max_depth` | `3` | 最大嵌套归档深度。 |
| `fail_on_detect` | `true` | 如果检测到威胁则使任务失败。 |
## 🧩 适配器
使用与您的 Web 框架匹配的适配器。所有适配器共享相同的策略选项和扫描契约。
### 可用适配器
| 框架 | 包 | 状态 | 安装 |
|-----------|---------|--------|---------|
| **Express** | `@pompelmi/express-middleware` | ✅ 稳定 | `npm ipompelmi/express-middleware` |
| **Koa** | `@pompelmi/koa-middleware` | ✅ 稳定 | `npm i @pompelmi/koa-middleware` |
| **Next.js** | `@pompelmi/next-upload` | ✅ 稳定 | `npm i @pompelmi/next-upload` |
| **Nuxt/Nitro** | `pompelmi`(本地)或远程 API | ✅ 文档 | [查看指南](https://pompelmi.github.io/pompelmi/how-to/nuxt-nitro/) |
| **NestJS** | `@pompelmi/nestjs-integration` | ✅ 稳定 | `npm i @pompelmi/nestjs-integration` |
| **Fastify** | `@pompelmi/fastify-plugin` | 🔶 Alpha | `npm i @pompelmi/fastify-plugin` |
| **Remix** | - | 🔜 计划中 | 即将推出 |
| **SvelteKit** | - | 🔜 计划中 | 即将推出 |
| **hapi** | - | 🔜 计划中 | 即将推出 |
```
# Express
npm i @pompelmi/express-middleware
# Koa
npm i @pompelmi/koa-middleware
# Next.js
npm i @pompelmi/next-upload
# NestJS
npm i @pompelmi/nestjs-integration
# Fastify (alpha)
npm i @pompelmi/fastify-plugin
# Standalone CLI
npm i -g @pompelmi/cli
```
请参阅上方的[📘 代码示例](#-code-examples)部分以获取集成示例。
👉 **[查看适配器文档 →](https://pompelmi.github.io/pompelmi/)** | **[浏览所有示例 →](./examples/)**
## 🗺️ 图表
### 上传扫描流程
```
flowchart TD
A["Client uploads file(s)"] --> B["Web App Route"]
B --> C{"Pre-filters
(ext, size, MIME)"}
C -- fail --> X["HTTP 4xx"]
C -- pass --> D{"Is ZIP?"}
D -- yes --> E["Iterate entries
(limits & scan)"]
E --> F{"Verdict?"}
D -- no --> F{"Scan bytes"}
F -- malicious/suspicious --> Y["HTTP 422 blocked"]
F -- clean --> Z["HTTP 200 ok + results"]
```
Mermaid 源码
```
flowchart TD
A["Client uploads file(s)"] --> B["Web App Route"]
B --> C{"Pre-filters
(ext, size, MIME)"}
C -- fail --> X["HTTP 4xx"]
C -- pass --> D{"Is ZIP?"}
D -- yes --> E["Iterate entries
(limits & scan)"]
E --> F{"Verdict?"}
D -- no --> F{"Scan bytes"}
F -- malicious/suspicious --> Y["HTTP 422 blocked"]
F -- clean --> Z["HTTP 200 ok + results"]
```
### 序列图(应用 ↔ pompelmi ↔ YARA)
```
sequenceDiagram
participant U as User
participant A as App Route (/upload)
participant P as pompelmi (adapter)
participant Y as YARA engine
U->>A: POST multipart/form-data
A->>P: guard(files, policies)
P->>P: MIME sniff + size + ext checks
alt ZIP archive
P->>P: unpack entries with limits
end
P->>Y: scan(bytes)
Y-->>P: matches[]
P-->>A: verdict (clean/suspicious/malicious)
A-->>U: 200 or 4xx/422 with reason
```
Mermaid 源码
```
sequenceDiagram
participant U as User
participant A as App Route (/upload)
participant P as pompelmi (adapter)
participant Y as YARA engine
U->>A: POST multipart/form-data
A->>P: guard(files, policies)
P->>P: MIME sniff + size + ext checks
alt ZIP archive
P->>P: unpack entries with limits
end
P->>Y: scan(bytes)
Y-->>P: matches[]
P-->>A: verdict (clean/suspicious/malicious)
A-->>U: 200 or 4xx/422 with reason
```
### 组件(monorepo)
```
flowchart LR
subgraph Repo
core["pompelmi (core)"]
express["@pompelmi/express-middleware"]
koa["@pompelmi/koa-middleware"]
next["@pompelmi/next-upload"]
fastify(("fastify-plugin · planned"))
nest(("nestjs · planned"))
remix(("remix · planned"))
hapi(("hapi-plugin · planned"))
svelte(("sveltekit · planned"))
end
core --> express
core --> koa
core --> next
core -.-> fastify
core -.-> nest
core -.-> remix
core -.-> hapi
core -.-> svelte
```
Mermaid 源码
```
flowchart LR
subgraph Repo
core["pompelmi (core)"]
express["@pompelmi/express-middleware"]
koa["@pompelmi/koa-middleware"]
next["@pompelmi/next-upload"]
fastify(("fastify-plugin · planned"))
nest(("nestjs · planned"))
remix(("remix · planned"))
hapi(("hapi-plugin · planned"))
svelte(("sveltekit · planned"))
end
core --> express
core --> koa
core --> next
core -.-> fastify
core -.-> nest
core -.-> remix
core -.-> hapi
core -.-> svelte
```
## ⚙️ 配置
所有适配器都接受一组通用选项:
| 选项 | 类型 (TS) | 用途 |
| --- | --- | --- |
| `scanner` | `{ scan(bytes: Uint8Array): Promise
}` | 您的扫描引擎。干净时返回 `[]`;非空则标记。 |
| `includeExtensions` | `string[]` | 文件扩展名白名单。不区分大小写评估。 |
| `allowedMimeTypes` | `string[]` | 魔数嗅探后的 MIME 类型白名单。 |
| `maxFileSizeBytes` | `number` | 单文件大小上限。超大文件会被提前拒绝。 |
| `timeoutMs` | `number` | 单文件扫描超时;防止扫描器卡死。 |
| `concurrency` | `number` | 并行扫描的文件数。 |
| `failClosed` | `boolean` | 如果为 `true`,错误/超时会阻止上传。 |
| `onScanEvent` | `(event: unknown) => void` | 用于日志记录/指标的可选遥测钩子。 |
**常用配置**
仅允许最大 5 MB 的图片:
```
includeExtensions: ['png','jpg','jpeg','webp'],
allowedMimeTypes: ['image/png','image/jpeg','image/webp'],
maxFileSizeBytes: 5 * 1024 * 1024,
failClosed: true,
```
## ✅ 生产环境检查清单
- [ ] **严格限制文件大小**(`maxFileSizeBytes`)。
- [ ] **将扩展名和 MIME 限制**为您的应用真正需要的类型。
- [ ] **在生产环境中设置 `failClosed: true`** 以在超时/错误时阻止上传。
- [ ] **小心处理 ZIP**(启用深度 ZIP,保持低嵌套级别,限制条目大小)。
- [ ] **组合扫描器**,使用 `composeScanners()` 并启用 `stopOn` 以便在早期检测时快速失败。
- [ ] **记录扫描事件**(`onScanEvent`)并监控峰值。
- [ ] **尽可能在单独的进程/容器中运行扫描**,以实现纵深防御。
- [ ] **清理文件名和路径**(如果您持久化上传)。
- [ ] **优先使用内存存储 + 后处理**;避免在策略通过之前写入不受信任的字节。
- [ ] **使用 GitHub Action 添加 CI 扫描**,以捕获仓库/产物中的不良文件。
## 🧬 YARA 入门
YARA 允许您使用模式匹配规则检测可疑或恶意内容。
**pompelmi** 将 YARA 匹配视为信号,您可以将其映射到自己的判定
(例如,将高置信度规则标记为 `malicious`,启发式规则标记为 `suspicious`)。
### 入门规则
以下是三个您可以调整的示例规则:
`rules/starter/eicar.yar`
```
rule EICAR_Test_File
{
meta:
description = "EICAR antivirus test string (safe)"
reference = "https://www.eicar.org"
confidence = "high"
verdict = "malicious"
strings:
$eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
condition:
$eicar
}
```
`rules/starter/pdf_js.yar`
```
rule PDF_JavaScript_Embedded
{
meta:
description = "PDF contains embedded JavaScript (heuristic)"
confidence = "medium"
verdict = "suspicious"
strings:
$magic = { 25 50 44 46 } // "%PDF"
$js1 = "/JavaScript" ascii
$js2 = "/JS" ascii
$open = "/OpenAction" ascii
$aa = "/AA" ascii
condition:
uint32(0) == 0x25504446 and ( $js1 or $js2 ) and ( $open or $aa )
}
```
`rules/starter/office_macros.yar`
```
rule Office_Macro_Suspicious_Words
{
meta:
description = "Heuristic: suspicious VBA macro keywords"
confidence = "medium"
verdict = "suspicious"
strings:
$s1 = /Auto(Open|Close)/ nocase
$s2 = "Document_Open" nocase ascii
$s3 = "CreateObject(" nocase ascii
$s4 = "WScript.Shell" nocase ascii
$s5 = "Shell(" nocase ascii
$s6 = "Sub Workbook_Open()" nocase ascii
condition:
2 of ($s*)
}
```
### 最小集成(适配器契约)
如果您使用 YARA 绑定(例如 `@automattic/yara`),请将其封装在 `scanner` 契约之后:
```
// Example YARA scanner adapter (pseudo‑code)
import * as Y from '@automattic/yara';
// Compile your rules from disk at boot (recommended)
// const sources = await fs.readFile('rules/starter/*.yar', 'utf8');
// const compiled = await Y.compile(sources);
export const YourYaraScanner = {
async scan(bytes: Uint8Array) {
// const matches = await compiled.scan(bytes, { timeout: 1500 });
const matches = []; // plug your engine here
// Map to the structure your app expects; return [] when clean.
return matches.map((m: any) => ({
rule: m.rule,
meta: m.meta ?? {},
tags: m.tags ?? [],
}));
}
};
```
然后将其包含在您的组合扫描器中:
```
import { composeScanners, CommonHeuristicsScanner } from 'pompelmi';
// import { YourYaraScanner } from './yara-scanner';
export const scanner = composeScanners(
[
['heuristics', CommonHeuristicsScanner],
// ['yara', YourYaraScanner],
],
{ parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
);
```
### 策略建议(将匹配映射 → 判定)
- **malicious**:高置信度规则(例如 `EICAR_Test_File`)
- **suspicious**:启发式规则(例如 PDF JavaScript、宏关键字)
- **clean**:无匹配
将 YARA 与 MIME 嗅探、ZIP 安全限制以及严格的大小/时间上限结合使用。
## 🧪 快速测试(无需 EICAR)
使用上面的示例,然后发送一个包含风险标记的**最小 PDF**(这将触发内置启发式规则)。
**1) 创建一个包含风险操作的小型 PDF**
Linux:
```
printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%EOF\n' > risky.pdf
```
macOS:
```
printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%EOF\n' > risky.pdf
```
**2) 将其发送到您的端点**
Express(来自快速开始的默认配置):
```
curl -F "file=@risky.pdf;type=application/pdf" http://localhost:3000/upload -i
```
您应该会看到 HTTP **422 Unprocessable Entity**(被策略阻止)。干净的文件返回 **200 OK**。预过滤失败(大小/扩展名/MIME)应返回 **4xx**。根据需要为您的应用调整这些约定。
## 🔒 安全说明
- 该库只**读取**字节;它从不执行文件。
- YARA 检测取决于您**提供的规则**;预期会有一些误报/漏报。
- ZIP 扫描应用限制(条目数、单条目大小、总解压大小、嵌套)以降低归档炸弹风险。
- 优先在**专用进程/容器**中运行扫描以实现纵深防御。
## 发布与安全
- **更新日志/发布:** 请参阅 [GitHub Releases](https://github.com/pompelmi/pompelmi/releases)。
- **安全披露:** 请使用 [GitHub Security Advisories](https://github.com/pompelmi/pompelmi/security/advisories)。我们将在公开披露之前协调修复。
- **生产用户:** 请开启一个 [Discussion](https://github.com/pompelmi/pompelmi/discussions) 以分享需求或请求适配器。
## 🏆 社区与致谢
pompelmi 曾在**领先的安全和开发者出版物**中亮相,并受到全球团队的信赖,用于安全的文件上传处理。
### 🌟 在权威刊物中亮相
HelpNet Security
Leading Cybersecurity News
|
Snyk
Security Verified
|
Detection Engineering Weekly
Issue #124
|
Node Weekly
Issue #594
|
Bytes Newsletter
Issue #429
|
daily.dev
Featured Article
|
### 🎖️ 在 Awesome 列表中被提及
### 💬 开发者怎么说
_想分享您的经验?[开启一个讨论](https://github.com/pompelmi/pompelmi/discussions)!_
### 🤝 社区与支持
**需要帮助?我们为您服务!**
- 📖 **[文档](https://pompelmi.github.io/pompelmi/)** — 完整的 API 参考、指南和教程
- 💬 **[GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)** — 提问、分享想法、获得社区支持
- 🐛 **[Issue Tracker](https://github.com/pompelmi/pompelmi/issues)** — 报告错误、请求功能
- 🔒 **[安全策略](https://github.com/pompelmi/pompelmi/security)** — 私下报告安全漏洞
- 💼 **商业支持** — 如需企业支持和咨询,请联系维护者
- 💖 **[赞助 pompelmi](https://github.com/sponsors/pompelmi)** — 通过 GitHub Sponsors 支持持续开发
**支持的框架:**
- ✅ Express
- ✅ Koa
- ✅ Next.js (App & Pages Router)
- ✅ NestJS
- ✅ Fastify (alpha)
- 🔜 Remix(计划中)
- 🔜 SvelteKit(计划中)
- 🔜 hapi(计划中)
## 🎖️ 贡献者
感谢所有帮助改进 pompelmi 的出色贡献者!
想做出贡献?请查看我们的贡献指南!
## 💖 赞助商
Pompelmi 是免费且开源的。如果它为您节省了时间或帮助保护了您的用户,请考虑支持其开发!
您的赞助有助于资助:
- 🧬 新的检测引擎集成
- 🧪 扩展的测试覆盖率和 CI 基础设施
- 📚 文档和示例
- 🔒 安全审计和 CVE 响应
感谢所有现任和未来的赞助商,是你们让这个项目得以存活!
## ⭐ Star 历史
## 💬 常见问题
**我需要 YARA 吗?**
不需要。`scanner` 是可插拔的。示例使用了最小扫描器以保持清晰;您可以调用 YARA 引擎或您喜欢的任何其他检测器。
**结果存放在哪里?**
在示例中,防护将扫描数据附加到请求上下文(例如 Express 中的 `req.pompelmi`,Koa 中的 `ctx.pompelmi`)。在 Next.js 中,根据需要将结果包含在您的 JSON 响应中。
**为什么被阻止的文件返回 422?**
使用 **422** 表示策略违规,使其与传输错误区分开来;这是一种常见模式。使用最符合您的 API 指南的状态码。
**是否处理 ZIP 炸弹?**
归档文件在遍历时会应用限制以降低归档炸弹风险。保持您的大小限制保守,并在生产环境中优先使用 `failClosed: true`。
## 🧪 测试与覆盖率
在本地运行带覆盖率的测试:
```
pnpm vitest run --coverage --passWithNoTests
```
徽章跟踪**核心库**(`src/**`)。适配器和引擎目前单独报告,随着其测试套件的增长,将合并到全局覆盖率中。
如果您在 CI 中集成 Codecov,上传 `coverage/lcov.info`,您可以使用此 Codecov 徽章:
```
[](https://codecov.io/gh/pompelmi/pompelmi)
```
## 🤝 贡献
欢迎 PR 和 issue!从这里开始:
```
pnpm -r build
pnpm -r lint
```
有关详细指南,请参阅 [CONTRIBUTING.md](./CONTRIBUTING.md)。
## 🎓 学习资源
### 📚 文档
- [官方文档](https://pompelmi.github.io/pompelmi/) — 完整的 API 参考和指南
- [示例](./examples/) — 真实世界的集成示例
- [安全指南](./SECURITY.md) — 安全最佳实践和披露策略
### 🛠️ 工具与集成
- [GitHub Action](https://github.com/pompelmi/pompelmi/tree/main/.github/actions/pompelmi-scan) — CI/CD 扫描
## 🙏 致谢
pompelmi 站在巨人的肩膀上。特别感谢:
- YARA 项目提供的强大模式匹配
- Node.js 社区提供的优秀工具
- 我们所有的贡献者和用户
↑ 回到顶部
## 📜 许可证
[MIT](./LICENSE) © 2025‑present pompelmi 贡献者