pompelmi 0.34.7 โ 0.34.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -1187
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1077 +1,118 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
<p>
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
<img src="assets/logo.svg" alt="Pompelmi logo" width="160" />
|
|
3
|
+
<h1>Pompelmi</h1>
|
|
4
|
+
<p>Local-first file upload scanning for Node.js.</p>
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi"></a>
|
|
7
|
+
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci.yml?label=ci"></a>
|
|
8
|
+
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads" src="https://img.shields.io/npm/dm/pompelmi"></a>
|
|
10
|
+
</p>
|
|
11
|
+
<p>
|
|
12
|
+
<a href="https://github.com/sorrycc/awesome-javascript"><img alt="Mentioned in Awesome JavaScript" src="https://img.shields.io/badge/mentioned-Awesome%20JavaScript-f59e0b"></a>
|
|
13
|
+
<a href="https://github.com/dzharii/awesome-typescript"><img alt="Mentioned in Awesome TypeScript" src="https://img.shields.io/badge/mentioned-Awesome%20TypeScript-3178C6"></a>
|
|
14
|
+
<a href="https://nodeweekly.com/issues/594"><img alt="Featured in Node Weekly #594" src="https://img.shields.io/badge/featured-Node%20Weekly%20%23594-339933?logo=node.js&logoColor=white"></a>
|
|
15
|
+
<a href="https://bytes.dev/archives/429"><img alt="Featured in Bytes #429" src="https://img.shields.io/badge/featured-Bytes%20%23429-111111"></a>
|
|
16
|
+
</p>
|
|
17
|
+
<p>
|
|
18
|
+
<a href="https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon"><img alt="Featured in Detection Engineering Weekly #124" src="https://img.shields.io/badge/featured-Detection%20Engineering%20Weekly%20%23124-0A84FF?logo=substack&logoColor=white"></a>
|
|
19
|
+
<a href="https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/"><img alt="Featured on Stack Overflow by Ryan Donovan" src="https://img.shields.io/badge/featured-Stack%20Overflow-F48024?logo=stackoverflow&logoColor=white"></a>
|
|
20
|
+
<a href="https://stackoverflow.blog/newsletter/issue-319-dogfooding-your-sdlc/"><img alt="Featured in The Overflow #319" src="https://img.shields.io/badge/featured-The%20Overflow%20%23319-F48024?logo=stackoverflow&logoColor=white"></a>
|
|
21
|
+
<a href="https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/"><img alt="Featured in Help Net Security" src="https://img.shields.io/badge/featured-Help%20Net%20Security-2563eb"></a>
|
|
22
|
+
</p>
|
|
20
23
|
</div>
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<!-- HERO START -->
|
|
25
|
-
|
|
26
|
-
<p align="center">
|
|
27
|
-
<br/>
|
|
28
|
-
<!-- Responsive logo using picture element -->
|
|
29
|
-
<picture>
|
|
30
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg">
|
|
31
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg">
|
|
32
|
-
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg" alt="pompelmi logo" width="360" />
|
|
33
|
-
</picture>
|
|
34
|
-
<br/>
|
|
35
|
-
<a href="https://www.producthunt.com/products/pompelmi"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1010722&theme=light" alt="pompelmi - Secure File Upload Scanning for Node.js | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
|
36
|
-
<br/>
|
|
37
|
-
<a href="https://github.com/sorrycc/awesome-javascript"><img alt="Mentioned in Awesome JavaScript" src="https://img.shields.io/badge/mentioned-Awesome%20JavaScript-f59e0b"></a>
|
|
38
|
-
<a href="https://github.com/dzharii/awesome-typescript"><img alt="Mentioned in Awesome TypeScript" src="https://img.shields.io/badge/mentioned-Awesome%20TypeScript-3178C6"></a>
|
|
39
|
-
<a href="https://nodeweekly.com/issues/594"><img alt="Featured in Node Weekly #594" src="https://img.shields.io/badge/featured-Node%20Weekly%20%23594-339933?logo=node.js&logoColor=white"></a>
|
|
40
|
-
<a href="https://bytes.dev/archives/429"><img alt="Featured in Bytes #429" src="https://img.shields.io/badge/featured-Bytes%20%23429-111111"></a>
|
|
41
|
-
<br/>
|
|
42
|
-
<a href="https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon"><img alt="Featured in Detection Engineering Weekly #124" src="https://img.shields.io/badge/featured-Detection%20Engineering%20Weekly%20%23124-0A84FF?logo=substack&logoColor=white"></a>
|
|
43
|
-
<a href="https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/"><img alt="Featured in Ryan Donovan's Stack Overflow Q&A" src="https://img.shields.io/badge/featured-Ryan%20Donovan%20Q%26A-F48024?logo=stackoverflow&logoColor=white"></a>
|
|
44
|
-
<a href="https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/"><img alt="Featured in Help Net Security" src="https://img.shields.io/badge/featured-Help%20Net%20Security-2563eb"></a>
|
|
45
|
-
<br/><br/>
|
|
46
|
-
</p>
|
|
47
|
-
|
|
48
|
-
<h1 align="center">pompelmi</h1>
|
|
49
|
-
|
|
50
|
-
<p align="center">
|
|
51
|
-
<strong>Secure File Upload Scanning for Node.js</strong>
|
|
52
|
-
</p>
|
|
53
|
-
|
|
54
|
-
<p align="center">
|
|
55
|
-
<em>Privacy-first malware detection with YARA, ZIP bomb protection, and framework adapters</em>
|
|
56
|
-
</p>
|
|
57
|
-
|
|
58
|
-
<p align="center">
|
|
59
|
-
Scan files before they hit disk โข Keep user data private โข Zero cloud dependencies
|
|
60
|
-
</p>
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
<!-- Badges Section -->
|
|
67
|
-
<p align="center">
|
|
68
|
-
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi?label=version&color=0a7ea4&logo=npm"></a>
|
|
69
|
-
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads" src="https://img.shields.io/npm/dm/pompelmi?label=downloads&color=6E9F18&logo=npm"></a>
|
|
70
|
-
<a href="https://github.com/pompelmi/pompelmi/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/pompelmi?color=blue"></a>
|
|
71
|
-
<img alt="node" src="https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white">
|
|
72
|
-
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml"><img alt="CI Status" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci.yml?branch=main&label=CI&logo=github"></a>
|
|
73
|
-
</p>
|
|
74
|
-
|
|
75
|
-
<p align="center">
|
|
76
|
-
<a href="https://codecov.io/gh/pompelmi/pompelmi"><img alt="codecov" src="https://codecov.io/gh/pompelmi/pompelmi/branch/main/graph/badge.svg?flag=core"/></a>
|
|
77
|
-
<img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white">
|
|
78
|
-
<img alt="ESM" src="https://img.shields.io/badge/ESM%2FCJS-compatible-yellow">
|
|
79
|
-
<a href="https://snyk.io/test/github/pompelmi/pompelmi"><img alt="Known Vulnerabilities" src="https://snyk.io/test/github/pompelmi/pompelmi/badge.svg"></a>
|
|
80
|
-
<a href="https://securityscorecards.dev/viewer/?uri=github.com/pompelmi/pompelmi"><img alt="OpenSSF Scorecard" src="https://api.securityscorecards.dev/projects/github.com/pompelmi/pompelmi/badge"/></a>
|
|
81
|
-
</p>
|
|
82
|
-
|
|
83
|
-
<p align="center">
|
|
84
|
-
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social"></a>
|
|
85
|
-
<a href="https://github.com/pompelmi/pompelmi/network/members"><img alt="GitHub forks" src="https://img.shields.io/github/forks/pompelmi/pompelmi?style=social"></a>
|
|
86
|
-
<a href="https://github.com/pompelmi/pompelmi/watchers"><img alt="GitHub watchers" src="https://img.shields.io/github/watchers/pompelmi/pompelmi?style=social"></a>
|
|
87
|
-
<a href="https://github.com/pompelmi/pompelmi/issues"><img alt="open issues" src="https://img.shields.io/github/issues/pompelmi/pompelmi?color=orange"></a>
|
|
88
|
-
</p>
|
|
89
|
-
|
|
90
|
-
<p align="center">
|
|
91
|
-
<strong>
|
|
92
|
-
<a href="https://pompelmi.github.io/pompelmi/">๐ Documentation</a> โข
|
|
93
|
-
<a href="#-installation">๐พ Install</a> โข
|
|
94
|
-
<a href="#-quick-start">โก Quick Start</a> โข
|
|
95
|
-
<a href="#-adapters">๐งฉ Adapters</a> โข
|
|
96
|
-
<a href="#-yara-getting-started">๐งฌ YARA</a> โข
|
|
97
|
-
<a href="#-github-action">๐ค CI/CD</a>
|
|
98
|
-
</strong>
|
|
99
|
-
</p>
|
|
100
|
-
|
|
101
|
-
<p align="center"><em>Coverage badge reflects core library (<code>src/**</code>); adapters are measured separately.</em></p>
|
|
102
|
-
|
|
103
|
-
<!-- HERO END -->
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## ๐ฌ Demo
|
|
108
|
-
|
|
109
|
-
<p align="center">
|
|
110
|
-
<a href="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/malware-detection-node-demo.gif">
|
|
111
|
-
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/malware-detection-node-demo.gif" alt="Pompelmi demo showing an Express upload being scanned before storage" width="900" />
|
|
112
|
-
</a>
|
|
113
|
-
</p>
|
|
114
|
-
|
|
115
|
-
**Want to try it now?** Check out our [live examples](./examples/) or install and run locally:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
npm i pompelmi @pompelmi/express-middleware
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## โจ Features
|
|
124
|
-
|
|
125
|
-
**pompelmi** provides enterprise-grade file scanning for Node.js applications:
|
|
126
|
-
|
|
127
|
-
- **๐ Privacy-First Architecture** โ All scanning happens in-process. No cloud calls, no data leaks. Your files never leave your infrastructure.
|
|
128
|
-
- **โก Lightning Fast** โ In-process scanning with zero network latency. Configurable concurrency for high-throughput scenarios.
|
|
129
|
-
- **๐งฉ Composable Scanners** โ Mix heuristics + signatures; set `stopOn` and timeouts. Bring your own YARA rules.
|
|
130
|
-
- **๐ฆ Deep ZIP Inspection** โ Traversal/bomb guards, polyglot & macro hints, nested archive scanning with configurable depth limits.
|
|
131
|
-
- **๐ Framework Adapters** โ Drop-in middleware for Express, Koa, Fastify, Next.js, Nuxt/Nitro, and **NestJS** with first-class TypeScript support.
|
|
132
|
-
- **๐ Stream-Based Processing** โ Memory-efficient scanning with configurable buffer limits. Scan large files without loading them entirely into memory.
|
|
133
|
-
- **๐ Polyglot Detection** โ Advanced magic bytes analysis detects mixed-format files and embedded scripts with **30+ file signatures**.
|
|
134
|
-
- **โ๏ธ CLI for CI/CD** โ Standalone command-line tool for scanning files and directories with watch mode and multiple output formats.
|
|
135
|
-
- **๐ TypeScript-First** โ Complete type definitions, modern ESM/CJS builds, minimal surface, tree-shakeable.
|
|
136
|
-
- **โก Zero Core Dependencies** โ Core library has minimal deps for fast installation and reduced supply chain risk.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Table of Contents
|
|
141
|
-
|
|
142
|
-
- [Overview](#overview)
|
|
143
|
-
- [Highlights](#highlights)
|
|
144
|
-
- [Why pompelmi](#why-pompelmi)
|
|
145
|
-
- [How it compares](#how-it-compares)
|
|
146
|
-
- [What Developers Say](#what-developers-say)
|
|
147
|
-
- [What Makes pompelmi Special](#what-makes-pompelmi-special)
|
|
148
|
-
- [Use Cases](#use-cases)
|
|
149
|
-
- [Installation](#installation)
|
|
150
|
-
- [Quick Start](#quick-start)
|
|
151
|
-
- [Minimal Node usage](#minimal-node-usage)
|
|
152
|
-
- [Express](#express)
|
|
153
|
-
- [Koa](#koa)
|
|
154
|
-
- [Next.js (App Router)](#nextjs-app-router)
|
|
155
|
-
- [Adapters](#adapters)
|
|
156
|
-
- [GitHub Action](#github-action)
|
|
157
|
-
- [Configuration](#configuration)
|
|
158
|
-
- [YARA Getting Started](#yara-getting-started)
|
|
159
|
-
- [Security Notes](#security-notes)
|
|
160
|
-
|
|
161
|
-
- [Testing & Development](#testing--development)
|
|
162
|
-
- [FAQ](#faq)
|
|
163
|
-
- [Contributing](#contributing)
|
|
164
|
-
- [License](#license)
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## ๐ Translations
|
|
169
|
-
|
|
170
|
-
pompelmi documentation is available in multiple languages to help developers worldwide:
|
|
171
|
-
|
|
172
|
-
- ๐ฎ๐น **[Italiano (Italian)](docs/i18n/README.it.md)** โ Documentazione completa in italiano
|
|
173
|
-
- ๐ซ๐ท **[Franรงais (French)](docs/i18n/README.fr.md)** โ Documentation complรจte en franรงais
|
|
174
|
-
- ๐ช๐ธ **[Espaรฑol (Spanish)](docs/i18n/README.es.md)** โ Documentaciรณn completa en espaรฑol
|
|
175
|
-
- ๐ฉ๐ช **[Deutsch (German)](docs/i18n/README.de.md)** โ Vollstรคndige Dokumentation auf Deutsch
|
|
176
|
-
- ๐ฏ๐ต **[ๆฅๆฌ่ช (Japanese)](docs/i18n/README.ja.md)** โ ๆฅๆฌ่ชใซใใๅฎๅ
จใชใใญใฅใกใณใ
|
|
177
|
-
- ๐จ๐ณ **[็ฎไฝไธญๆ (Simplified Chinese)](docs/i18n/README.zh-CN.md)** โ ๅฎๆด็็ฎไฝไธญๆๆๆกฃ
|
|
178
|
-
- ๐ฐ๐ท **[ํ๊ตญ์ด (Korean)](docs/i18n/README.ko.md)** โ ์์ ํ ํ๊ตญ์ด ๋ฌธ์
|
|
179
|
-
- ๐ง๐ท **[Portuguรชs (Brasil)](docs/i18n/README.pt-BR.md)** โ Documentaรงรฃo completa em portuguรชs
|
|
180
|
-
- ๐ท๐บ **[ะ ัััะบะธะน (Russian)](docs/i18n/README.ru.md)** โ ะะพะปะฝะฐั ะดะพะบัะผะตะฝัะฐัะธั ะฝะฐ ััััะบะพะผ
|
|
181
|
-
- ๐น๐ท **[Tรผrkรงe (Turkish)](docs/i18n/README.tr.md)** โ Tรผrkรงe tam dokรผmantasyon
|
|
182
|
-
|
|
183
|
-
**Help improve translations:** We welcome contributions to improve and maintain translations. The English README is the authoritative source. To contribute, please open a Pull Request with your improvements.
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## ๐ Overview
|
|
188
|
-
|
|
189
|
-
**pompelmi** scans untrusted file uploads **before** they hit disk. A tiny, TypeScript-first toolkit for Node.js with composable scanners, deep ZIP inspection, and optional signature engines.
|
|
190
|
-
|
|
191
|
-
### ๐ฏ Key Features
|
|
192
|
-
|
|
193
|
-
**๐ Private by design** โ no outbound calls; bytes never leave your process
|
|
194
|
-
|
|
195
|
-
**๐งฉ Composable scanners** โ mix heuristics + signatures; set `stopOn` and timeouts
|
|
196
|
-
|
|
197
|
-
**๐ฆ ZIP hardening** โ traversal/bomb guards, polyglot & macro hints
|
|
198
|
-
|
|
199
|
-
**๐ Drop-in adapters** โ Express, Koa, Fastify, Next.js, Nuxt/Nitro, **NestJS**
|
|
200
|
-
|
|
201
|
-
**๐ Stream-based scanning** โ memory-efficient processing with configurable buffer limits
|
|
202
|
-
|
|
203
|
-
**โ๏ธ CLI for CI/CD** โ standalone command-line tool for scanning files and directories
|
|
204
|
-
|
|
205
|
-
**๐ Polyglot detection** โ advanced magic bytes analysis and embedded script detection
|
|
206
|
-
|
|
207
|
-
**๐ Typed & tiny** โ modern TS, minimal surface, tree-shakeable
|
|
208
|
-
|
|
209
|
-
**โก Zero dependencies** โ core library has minimal deps, fast installation
|
|
210
|
-
|
|
211
|
-
## โจ Highlights
|
|
212
|
-
|
|
213
|
-
**๐ก๏ธ Block risky uploads early** โ classify uploads as _clean_, _suspicious_, or _malicious_ and stop them at the edge.
|
|
214
|
-
|
|
215
|
-
**โ
Real guards** โ extension allowโlist, serverโside MIME sniff (magic bytes), perโfile size caps, and **deep ZIP** traversal with antiโbomb limits.
|
|
216
|
-
|
|
217
|
-
**๐ Builtโin scanners** โ dropโin **CommonHeuristicsScanner** (PDF risky actions, Office macros, PE header) and **Zipโbomb Guard**; add your own or YARA via a tiny `{ scan(bytes) }` contract.
|
|
218
|
-
|
|
219
|
-
**๐ฌ Polyglot & embedded script detection** โ advanced magic bytes analysis detects mixed-format files and embedded scripts with **30+ file signatures**.
|
|
220
|
-
|
|
221
|
-
**๐ Memory-efficient streaming** โ scan large files without loading them entirely into memory with automatic stream routing.
|
|
222
|
-
|
|
223
|
-
**โ๏ธ Compose scanning** โ run multiple scanners in parallel or sequentially with timeouts and shortโcircuiting via `composeScanners()`.
|
|
224
|
-
|
|
225
|
-
**๐๏ธ Framework integrations** โ native modules for **NestJS**, Express, Koa, Next.js, Nuxt/Nitro, and Fastify with first-class TypeScript support.
|
|
226
|
-
|
|
227
|
-
**๐ง Production-ready CLI** โ standalone tool for CI/CD pipelines with watch mode, multiple output formats (JSON, table, minimal).
|
|
228
|
-
|
|
229
|
-
**โ๏ธ Zero cloud** โ scans run inโprocess. Keep bytes private. Perfect for GDPR/HIPAA compliance.
|
|
230
|
-
|
|
231
|
-
**๐จโ๐ป DX first** โ TypeScript types, ESM/CJS builds, tiny API, adapters for popular web frameworks.
|
|
232
|
-
|
|
233
|
-
> **SEO Keywords:** file upload security, malware detection, virus scanner, Node.js security, Express middleware, YARA integration, ZIP bomb protection, file validation, upload sanitization, threat detection, security scanner, antivirus Node.js, file scanning library, TypeScript security, Next.js security, Nuxt security, Nitro security, Koa middleware, server-side validation, file integrity check, malware prevention, secure file upload
|
|
234
|
-
|
|
235
|
-
## ๐ง Why pompelmi?
|
|
236
|
-
|
|
237
|
-
- **Onโdevice, private scanning** โ no outbound calls, no data sharing.
|
|
238
|
-
- **Blocks early** โ runs _before_ you write to disk or persist anything.
|
|
239
|
-
- **Fits your stack** โ dropโin adapters for Express, Koa, Next.js, Nuxt/Nitro (Fastify plugin in alpha).
|
|
240
|
-
- **Defenseโinโdepth** โ ZIP traversal limits, ratio caps, serverโside MIME sniffing, size caps.
|
|
241
|
-
- **Pluggable detection** โ bring your own engine (e.g., YARA) via a tiny `{ scan(bytes) }` contract.
|
|
242
|
-
|
|
243
|
-
### Who is it for?
|
|
244
|
-
|
|
245
|
-
- Teams who canโt send uploads to thirdโparty AV APIs.
|
|
246
|
-
- Apps that need predictable, lowโlatency decisions inline.
|
|
247
|
-
- Developers who want simple, typed building blocks instead of a daemon.
|
|
248
|
-
|
|
249
|
-
## ๐ How it compares
|
|
250
|
-
|
|
251
|
-
| Capability | pompelmi | ClamAV / nodeโclam | Cloud AV APIs |
|
|
252
|
-
| --- | --- | --- | --- |
|
|
253
|
-
| Runs fully inโprocess | โ
| โ (separate daemon) | โ (network calls) |
|
|
254
|
-
| Bytes stay private | โ
| โ
| โ |
|
|
255
|
-
| Deep ZIP limits & MIME sniff | โ
| โ
(archive scan) | โ varies |
|
|
256
|
-
| YARA integration | โ
optional | โ* | โ varies |
|
|
257
|
-
| Framework adapters | โ
Express/Koa/Next.js | โ | โ |
|
|
258
|
-
| Works in CI on artifacts | โ
| โ
| โ varies |
|
|
259
|
-
| Licensing | MIT | GPL (engine) | Proprietary |
|
|
260
|
-
|
|
261
|
-
\* You can run YARA alongside ClamAV, but itโs not builtโin.
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
## ๐ฌ What Developers Say
|
|
265
|
-
|
|
266
|
-
> "pompelmi made it incredibly easy to add malware scanning to our Express API. The TypeScript support is fantastic!"
|
|
267
|
-
> โ Developer using pompelmi in production
|
|
268
|
-
|
|
269
|
-
> "Finally, a file scanning solution that doesn't require sending our users' data to third parties. Perfect for GDPR compliance."
|
|
270
|
-
> โ Security Engineer at a healthcare startup
|
|
271
|
-
|
|
272
|
-
> "The YARA integration is seamless. We went from prototype to production in less than a week."
|
|
273
|
-
> โ DevSecOps Engineer
|
|
274
|
-
|
|
275
|
-
_Want to share your experience? [Open a discussion](https://github.com/pompelmi/pompelmi/discussions)!_
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## ๐ What Makes pompelmi Special?
|
|
280
|
-
|
|
281
|
-
### ๐ฏ Developer Experience
|
|
282
|
-
|
|
283
|
-
Built with developers in mind from day one. Simple API, comprehensive TypeScript types, and excellent documentation mean you can integrate secure file scanning in minutes, not days. Hot module replacement support and detailed error messages make debugging a breeze.
|
|
284
|
-
|
|
285
|
-
### ๐ Performance First
|
|
25
|
+
Pompelmi inspects untrusted files before storage and helps you decide whether to allow, reject, or quarantine them before they reach downstream systems.
|
|
286
26
|
|
|
287
|
-
|
|
27
|
+
It is built for upload endpoints that cannot rely on filenames, extensions, or client-provided MIME types alone.
|
|
288
28
|
|
|
289
|
-
|
|
29
|
+
## Demo
|
|
290
30
|
|
|
291
|
-
|
|
31
|
+

|
|
292
32
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
Your data never leaves your infrastructure. No telemetry, no cloud dependencies, no third-party API calls. Perfect for regulated industries (healthcare, finance, government) and privacy-conscious applications.
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## ๐ก Use Cases
|
|
300
|
-
|
|
301
|
-
pompelmi is trusted across diverse industries and use cases:
|
|
302
|
-
|
|
303
|
-
### ๐ฅ Healthcare (HIPAA Compliance)
|
|
304
|
-
|
|
305
|
-
Scan patient document uploads without sending PHI to third-party services. Keep medical records and imaging files secure on your infrastructure.
|
|
306
|
-
|
|
307
|
-
### ๐ฆ Financial Services (PCI DSS)
|
|
308
|
-
|
|
309
|
-
Validate customer document uploads (ID verification, tax forms) without exposing sensitive financial data to external APIs.
|
|
310
|
-
|
|
311
|
-
### ๐ Education Platforms
|
|
312
|
-
|
|
313
|
-
Protect learning management systems from malicious file uploads while maintaining student privacy.
|
|
314
|
-
|
|
315
|
-
### ๐ข Enterprise Document Management
|
|
316
|
-
|
|
317
|
-
Scan files at ingestion time for corporate file sharing platforms, wikis, and collaboration tools.
|
|
318
|
-
|
|
319
|
-
### ๐จ Media & Creative Platforms
|
|
320
|
-
|
|
321
|
-
Validate user-generated content uploads (images, videos, documents) before processing and storage.
|
|
322
|
-
|
|
323
|
-
---
|
|
324
|
-
|
|
325
|
-
---
|
|
326
|
-
|
|
327
|
-
## ๐ฆ Installation
|
|
328
|
-
|
|
329
|
-
**pompelmi** is a privacy-first Node.js library for local file scanning.
|
|
330
|
-
|
|
331
|
-
**Requirements:**
|
|
332
|
-
- Node.js 18+
|
|
333
|
-
- Optional: ClamAV binaries (for signature-based scanning)
|
|
334
|
-
- Optional: YARA libraries (for custom rules)
|
|
335
|
-
|
|
336
|
-
<table>
|
|
337
|
-
<tr>
|
|
338
|
-
<td><b>npm</b></td>
|
|
339
|
-
<td><code>npm install pompelmi</code></td>
|
|
340
|
-
</tr>
|
|
341
|
-
<tr>
|
|
342
|
-
<td><b>pnpm</b></td>
|
|
343
|
-
<td><code>pnpm add pompelmi</code></td>
|
|
344
|
-
</tr>
|
|
345
|
-
<tr>
|
|
346
|
-
<td><b>yarn</b></td>
|
|
347
|
-
<td><code>yarn add pompelmi</code></td>
|
|
348
|
-
</tr>
|
|
349
|
-
<tr>
|
|
350
|
-
<td><b>bun</b></td>
|
|
351
|
-
<td><code>bun add pompelmi</code></td>
|
|
352
|
-
</tr>
|
|
353
|
-
</table>
|
|
354
|
-
|
|
355
|
-
#### ๐ฆ Framework Adapters
|
|
356
|
-
|
|
357
|
-
```bash
|
|
358
|
-
# Express
|
|
359
|
-
npm i @pompelmi/express-middleware
|
|
360
|
-
|
|
361
|
-
# Koa
|
|
362
|
-
npm i @pompelmi/koa-middleware
|
|
363
|
-
|
|
364
|
-
# Next.js
|
|
365
|
-
npm i @pompelmi/next-upload
|
|
366
|
-
|
|
367
|
-
# NestJS
|
|
368
|
-
npm i @pompelmi/nestjs-integration
|
|
369
|
-
|
|
370
|
-
# Fastify (alpha)
|
|
371
|
-
npm i @pompelmi/fastify-plugin
|
|
372
|
-
|
|
373
|
-
# Standalone CLI
|
|
374
|
-
npm i -g @pompelmi/cli
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
> **Note:** Core library works standalone. Install adapters only if using specific frameworks.
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## ๐ Getting Started
|
|
382
|
-
|
|
383
|
-
Get secure file scanning running in under 5 minutes with pompelmi's zero-config defaults.
|
|
384
|
-
|
|
385
|
-
### Step 1: Install
|
|
33
|
+
## Install
|
|
386
34
|
|
|
387
35
|
```bash
|
|
388
36
|
npm install pompelmi
|
|
389
37
|
```
|
|
390
38
|
|
|
391
|
-
|
|
39
|
+
Requires Node.js 18+.
|
|
392
40
|
|
|
393
|
-
|
|
41
|
+
## Quick Start
|
|
394
42
|
|
|
395
43
|
```ts
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
concurrency: 4,
|
|
405
|
-
failClosed: true, // Block uploads on scanner errors
|
|
406
|
-
onScanEvent: (event: unknown) => console.log('[scan]', event)
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
export const scanner = composeScanners(
|
|
410
|
-
[
|
|
411
|
-
['zipGuard', createZipBombGuard({
|
|
412
|
-
maxEntries: 512,
|
|
413
|
-
maxTotalUncompressedBytes: 100 * 1024 * 1024,
|
|
414
|
-
maxCompressionRatio: 12
|
|
415
|
-
})],
|
|
416
|
-
['heuristics', CommonHeuristicsScanner],
|
|
417
|
-
// Add your own scanners or YARA rules here
|
|
418
|
-
],
|
|
419
|
-
{
|
|
420
|
-
parallel: false,
|
|
421
|
-
stopOn: 'suspicious',
|
|
422
|
-
timeoutMsPerScanner: 1500,
|
|
423
|
-
tagSourceName: true
|
|
424
|
-
}
|
|
425
|
-
);
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Step 3: Choose Your Integration
|
|
429
|
-
|
|
430
|
-
Pick the integration that matches your framework:
|
|
431
|
-
|
|
432
|
-
#### Express
|
|
433
|
-
|
|
434
|
-
```ts
|
|
435
|
-
import express from 'express';
|
|
436
|
-
import multer from 'multer';
|
|
437
|
-
import { createUploadGuard } from '@pompelmi/express-middleware';
|
|
438
|
-
import { policy, scanner } from './lib/security';
|
|
439
|
-
|
|
440
|
-
const app = express();
|
|
441
|
-
const upload = multer({
|
|
442
|
-
storage: multer.memoryStorage(),
|
|
443
|
-
limits: { fileSize: policy.maxFileSizeBytes }
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
app.post('/upload',
|
|
447
|
-
upload.any(),
|
|
448
|
-
createUploadGuard({ ...policy, scanner }),
|
|
449
|
-
(req, res) => {
|
|
450
|
-
// File is safe - proceed with your logic
|
|
451
|
-
res.json({
|
|
452
|
-
success: true,
|
|
453
|
-
verdict: (req as any).pompelmi?.verdict || 'clean'
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
app.listen(3000, () => console.log('๐ Server running on http://localhost:3000'));
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
#### Next.js App Router
|
|
462
|
-
|
|
463
|
-
```ts
|
|
464
|
-
// app/api/upload/route.ts
|
|
465
|
-
import { createNextUploadHandler } from '@pompelmi/next-upload';
|
|
466
|
-
import { policy, scanner } from '@/lib/security';
|
|
467
|
-
|
|
468
|
-
export const runtime = 'nodejs';
|
|
469
|
-
export const dynamic = 'force-dynamic';
|
|
470
|
-
|
|
471
|
-
export const POST = createNextUploadHandler({ ...policy, scanner });
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
#### Koa
|
|
475
|
-
|
|
476
|
-
```ts
|
|
477
|
-
import Koa from 'koa';
|
|
478
|
-
import Router from '@koa/router';
|
|
479
|
-
import multer from '@koa/multer';
|
|
480
|
-
import { createKoaUploadGuard } from '@pompelmi/koa-middleware';
|
|
481
|
-
import { policy, scanner } from './lib/security';
|
|
482
|
-
|
|
483
|
-
const app = new Koa();
|
|
484
|
-
const router = new Router();
|
|
485
|
-
const upload = multer({
|
|
486
|
-
storage: multer.memoryStorage(),
|
|
487
|
-
limits: { fileSize: policy.maxFileSizeBytes }
|
|
44
|
+
import { scanBytes } from 'pompelmi';
|
|
45
|
+
|
|
46
|
+
const report = await scanBytes(file.buffer, {
|
|
47
|
+
ctx: {
|
|
48
|
+
filename: file.originalname,
|
|
49
|
+
mimeType: file.mimetype,
|
|
50
|
+
size: file.size,
|
|
51
|
+
},
|
|
488
52
|
});
|
|
489
53
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
success: true,
|
|
496
|
-
verdict: (ctx as any).pompelmi?.verdict || 'clean'
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
app.use(router.routes()).use(router.allowedMethods());
|
|
502
|
-
app.listen(3003, () => console.log('๐ Server running on http://localhost:3003'));
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
#### Standalone / Programmatic
|
|
506
|
-
|
|
507
|
-
```ts
|
|
508
|
-
import { scanFile } from 'pompelmi';
|
|
509
|
-
|
|
510
|
-
const result = await scanFile('path/to/file.zip');
|
|
511
|
-
console.log(result.verdict); // "clean" | "suspicious" | "malicious"
|
|
512
|
-
|
|
513
|
-
if (result.verdict === 'malicious') {
|
|
514
|
-
console.error('โ ๏ธ Malicious file detected!');
|
|
515
|
-
console.error(result.reasons);
|
|
516
|
-
}
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### Step 4: Test It
|
|
520
|
-
|
|
521
|
-
Upload a test file to verify everything works:
|
|
522
|
-
|
|
523
|
-
```bash
|
|
524
|
-
curl -X POST http://localhost:3000/upload \
|
|
525
|
-
-F "file=@test.pdf"
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
โ
**Done!** Your app now has secure file upload scanning.
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## ๐ Code Examples
|
|
533
|
-
|
|
534
|
-
### Example 1: Express with Custom Error Handling
|
|
535
|
-
|
|
536
|
-
```ts
|
|
537
|
-
import express from 'express';
|
|
538
|
-
import multer from 'multer';
|
|
539
|
-
import { createUploadGuard } from '@pompelmi/express-middleware';
|
|
540
|
-
import { policy, scanner } from './lib/security';
|
|
541
|
-
|
|
542
|
-
const app = express();
|
|
543
|
-
const upload = multer({ storage: multer.memoryStorage() });
|
|
544
|
-
|
|
545
|
-
app.post('/upload',
|
|
546
|
-
upload.single('file'),
|
|
547
|
-
createUploadGuard({ ...policy, scanner }),
|
|
548
|
-
(req, res) => {
|
|
549
|
-
const scanResult = (req as any).pompelmi;
|
|
550
|
-
|
|
551
|
-
if (scanResult?.verdict === 'malicious') {
|
|
552
|
-
return res.status(422).json({
|
|
553
|
-
error: 'Malicious file detected',
|
|
554
|
-
reasons: scanResult.reasons
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
if (scanResult?.verdict === 'suspicious') {
|
|
559
|
-
// Log for review but allow upload
|
|
560
|
-
console.warn('Suspicious file uploaded:', req.file?.originalname);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Process clean file
|
|
564
|
-
res.json({ success: true, fileName: req.file?.originalname });
|
|
565
|
-
}
|
|
566
|
-
);
|
|
567
|
-
|
|
568
|
-
app.listen(3000);
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### Example 2: Next.js Route Handler with Custom Response
|
|
572
|
-
|
|
573
|
-
```ts
|
|
574
|
-
// app/api/scan/route.ts
|
|
575
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
576
|
-
import { scanBuffer } from 'pompelmi';
|
|
577
|
-
import { scanner } from '@/lib/security';
|
|
578
|
-
|
|
579
|
-
export async function POST(req: NextRequest) {
|
|
580
|
-
const formData = await req.formData();
|
|
581
|
-
const file = formData.get('file') as File;
|
|
582
|
-
|
|
583
|
-
if (!file) {
|
|
584
|
-
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const buffer = Buffer.from(await file.arrayBuffer());
|
|
588
|
-
const result = await scanner.scan(buffer);
|
|
589
|
-
|
|
590
|
-
return NextResponse.json({
|
|
591
|
-
fileName: file.name,
|
|
592
|
-
verdict: result.verdict,
|
|
593
|
-
safe: result.verdict === 'clean',
|
|
594
|
-
reasons: result.reasons || []
|
|
54
|
+
if (!report.ok) {
|
|
55
|
+
return res.status(422).json({
|
|
56
|
+
error: 'Upload blocked',
|
|
57
|
+
verdict: report.verdict,
|
|
58
|
+
reasons: report.reasons,
|
|
595
59
|
});
|
|
596
60
|
}
|
|
597
61
|
```
|
|
598
62
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
```ts
|
|
602
|
-
// app.module.ts
|
|
603
|
-
import { Module } from '@nestjs/common';
|
|
604
|
-
import { PompelmiModule } from '@pompelmi/nestjs-integration';
|
|
605
|
-
import { CommonHeuristicsScanner } from 'pompelmi';
|
|
606
|
-
|
|
607
|
-
@Module({
|
|
608
|
-
imports: [
|
|
609
|
-
PompelmiModule.forRoot({
|
|
610
|
-
includeExtensions: ['pdf', 'zip', 'png', 'jpg'],
|
|
611
|
-
allowedMimeTypes: ['application/pdf', 'application/zip', 'image/png', 'image/jpeg'],
|
|
612
|
-
maxFileSizeBytes: 10 * 1024 * 1024,
|
|
613
|
-
scanners: [CommonHeuristicsScanner],
|
|
614
|
-
}),
|
|
615
|
-
],
|
|
616
|
-
})
|
|
617
|
-
export class AppModule {}
|
|
618
|
-
|
|
619
|
-
// upload.controller.ts
|
|
620
|
-
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
|
621
|
-
import { FileInterceptor } from '@nestjs/platform-express';
|
|
622
|
-
import { PompelmiInterceptor, PompelmiResult } from '@pompelmi/nestjs-integration';
|
|
623
|
-
|
|
624
|
-
@Controller('upload')
|
|
625
|
-
export class UploadController {
|
|
626
|
-
@Post()
|
|
627
|
-
@UseInterceptors(FileInterceptor('file'), PompelmiInterceptor)
|
|
628
|
-
async uploadFile(@UploadedFile() file: Express.Multer.File & { pompelmi?: PompelmiResult }) {
|
|
629
|
-
if (file.pompelmi?.verdict === 'malicious') {
|
|
630
|
-
throw new BadRequestException('Malicious file detected');
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
return {
|
|
634
|
-
success: true,
|
|
635
|
-
verdict: file.pompelmi?.verdict,
|
|
636
|
-
fileName: file.originalname
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
> ๐ **More examples:** Check the [examples/](./examples/) directory for complete working demos including Express, Koa, Next.js, Nuxt/Nitro, and more.
|
|
643
|
-
|
|
644
|
-
---
|
|
645
|
-
|
|
646
|
-
## ๐ค GitHub Action
|
|
647
|
-
|
|
648
|
-
Run **pompelmi** in CI to scan repository files or built artifacts.
|
|
649
|
-
|
|
650
|
-
**Minimal usage**
|
|
651
|
-
```yaml
|
|
652
|
-
name: Security scan (pompelmi)
|
|
653
|
-
on: [push, pull_request]
|
|
654
|
-
|
|
655
|
-
jobs:
|
|
656
|
-
scan:
|
|
657
|
-
runs-on: ubuntu-latest
|
|
658
|
-
steps:
|
|
659
|
-
- uses: actions/checkout@v4
|
|
660
|
-
|
|
661
|
-
- name: Scan repository with pompelmi
|
|
662
|
-
uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
|
|
663
|
-
with:
|
|
664
|
-
path: .
|
|
665
|
-
deep_zip: true
|
|
666
|
-
fail_on_detect: true
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
**Scan a single artifact**
|
|
670
|
-
```yaml
|
|
671
|
-
- uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
|
|
672
|
-
with:
|
|
673
|
-
artifact: build.zip
|
|
674
|
-
deep_zip: true
|
|
675
|
-
fail_on_detect: true
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
**Inputs**
|
|
679
|
-
| Input | Default | Description |
|
|
680
|
-
| --- | --- | --- |
|
|
681
|
-
| `path` | `.` | Directory to scan. |
|
|
682
|
-
| `artifact` | `""` | Single file/archive to scan. |
|
|
683
|
-
| `yara_rules` | `""` | Glob path to YARA rules (e.g. `rules/*.yar`). |
|
|
684
|
-
| `deep_zip` | `true` | Enable deep nested-archive inspection. |
|
|
685
|
-
| `max_depth` | `3` | Max nested-archive depth. |
|
|
686
|
-
| `fail_on_detect` | `true` | Fail the job if detections occur. |
|
|
687
|
-
|
|
688
|
-
> The Action lives in this repo at `.github/actions/pompelmi-scan`. When published to the Marketplace, consumers can copy the snippets above as-is.
|
|
689
|
-
|
|
690
|
-
---
|
|
691
|
-
|
|
692
|
-
## ๐งฉ Adapters
|
|
693
|
-
|
|
694
|
-
Use the adapter that matches your web framework. All adapters share the same policy options and scanning contract.
|
|
695
|
-
|
|
696
|
-
<p align="center">
|
|
697
|
-
<img src="https://img.shields.io/badge/Express-โ-000000?style=flat-square&logo=express" alt="Express">
|
|
698
|
-
<img src="https://img.shields.io/badge/Koa-โ-33333D?style=flat-square&logo=koa" alt="Koa">
|
|
699
|
-
<img src="https://img.shields.io/badge/Next.js-โ-000000?style=flat-square&logo=next.js" alt="Next.js">
|
|
700
|
-
<img src="https://img.shields.io/badge/Nuxt-โ-00DC82?style=flat-square&logo=nuxt.js" alt="Nuxt">
|
|
701
|
-
<img src="https://img.shields.io/badge/NestJS-โ-E0234E?style=flat-square&logo=nestjs" alt="NestJS">
|
|
702
|
-
<img src="https://img.shields.io/badge/Fastify-alpha-000000?style=flat-square&logo=fastify" alt="Fastify">
|
|
703
|
-
<img src="https://img.shields.io/badge/Remix-planned-000000?style=flat-square&logo=remix" alt="Remix">
|
|
704
|
-
<img src="https://img.shields.io/badge/hapi-planned-F26D00?style=flat-square" alt="hapi">
|
|
705
|
-
<img src="https://img.shields.io/badge/SvelteKit-planned-FF3E00?style=flat-square&logo=svelte" alt="SvelteKit">
|
|
706
|
-
</p>
|
|
707
|
-
|
|
708
|
-
### Available Adapters
|
|
63
|
+
## What Problem It Solves
|
|
709
64
|
|
|
710
|
-
|
|
711
|
-
|-----------|---------|--------|---------|
|
|
712
|
-
| **Express** | `@pompelmi/express-middleware` | โ
Stable | `npm i @pompelmi/express-middleware` |
|
|
713
|
-
| **Koa** | `@pompelmi/koa-middleware` | โ
Stable | `npm i @pompelmi/koa-middleware` |
|
|
714
|
-
| **Next.js** | `@pompelmi/next-upload` | โ
Stable | `npm i @pompelmi/next-upload` |
|
|
715
|
-
| **Nuxt/Nitro** | `pompelmi` (local) or remote API | โ
Docs | [See guide](https://pompelmi.github.io/pompelmi/how-to/nuxt-nitro/) |
|
|
716
|
-
| **NestJS** | `@pompelmi/nestjs-integration` | โ
Stable | `npm i @pompelmi/nestjs-integration` |
|
|
717
|
-
| **Fastify** | `@pompelmi/fastify-plugin` | ๐ถ Alpha | `npm i @pompelmi/fastify-plugin` |
|
|
718
|
-
| **Remix** | - | ๐ Planned | Coming soon |
|
|
719
|
-
| **SvelteKit** | - | ๐ Planned | Coming soon |
|
|
720
|
-
| **hapi** | - | ๐ Planned | Coming soon |
|
|
65
|
+
Upload endpoints are part of your attack surface. A renamed executable, a risky PDF, or a hostile archive can look harmless until it is stored, unpacked, served, or parsed by another system.
|
|
721
66
|
|
|
722
|
-
|
|
67
|
+
Pompelmi adds checks at the upload boundary for:
|
|
723
68
|
|
|
724
|
-
|
|
69
|
+
- MIME spoofing and magic-byte mismatches
|
|
70
|
+
- Archive abuse such as ZIP bombs, traversal, and deep nesting
|
|
71
|
+
- Polyglot files and risky document structures
|
|
72
|
+
- Optional YARA-based signature matching
|
|
725
73
|
|
|
726
|
-
|
|
727
|
-
| --- | --- | --- |
|
|
728
|
-
| Express | `@pompelmi/express-middleware` | โ
alpha |
|
|
729
|
-
| Koa | `@pompelmi/koa-middleware` | โ
alpha |
|
|
730
|
-
| Next.js (App Router) | `@pompelmi/next-upload` | โ
alpha |
|
|
731
|
-
| Fastify | `@pompelmi/fastify-plugin` | ๐ง alpha |
|
|
732
|
-
| NestJS | nestjs | ๐ planned |
|
|
733
|
-
| Remix | remix | ๐ planned |
|
|
734
|
-
| hapi | hapi plugin | ๐ planned |
|
|
735
|
-
| SvelteKit | sveltekit | ๐ planned |
|
|
74
|
+
The goal is simple: inspect first, store later.
|
|
736
75
|
|
|
737
|
-
|
|
76
|
+
## Why This Shape
|
|
738
77
|
|
|
739
|
-
|
|
78
|
+
- Plain Markdown, readable in GitHub and in a terminal
|
|
79
|
+
- Fast path first: install, example, then deeper links
|
|
80
|
+
- Minimal top-level detail, with docs and examples for everything else
|
|
740
81
|
|
|
741
|
-
|
|
742
|
-
```mermaid
|
|
743
|
-
flowchart TD
|
|
744
|
-
A["Client uploads file(s)"] --> B["Web App Route"]
|
|
745
|
-
B --> C{"Pre-filters<br/>(ext, size, MIME)"}
|
|
746
|
-
C -- fail --> X["HTTP 4xx"]
|
|
747
|
-
C -- pass --> D{"Is ZIP?"}
|
|
748
|
-
D -- yes --> E["Iterate entries<br/>(limits & scan)"]
|
|
749
|
-
E --> F{"Verdict?"}
|
|
750
|
-
D -- no --> F{"Scan bytes"}
|
|
751
|
-
F -- malicious/suspicious --> Y["HTTP 422 blocked"]
|
|
752
|
-
F -- clean --> Z["HTTP 200 ok + results"]
|
|
753
|
-
```
|
|
754
|
-
<details>
|
|
755
|
-
<summary>Mermaid source</summary>
|
|
756
|
-
|
|
757
|
-
```mermaid
|
|
758
|
-
flowchart TD
|
|
759
|
-
A["Client uploads file(s)"] --> B["Web App Route"]
|
|
760
|
-
B --> C{"Pre-filters<br/>(ext, size, MIME)"}
|
|
761
|
-
C -- fail --> X["HTTP 4xx"]
|
|
762
|
-
C -- pass --> D{"Is ZIP?"}
|
|
763
|
-
D -- yes --> E["Iterate entries<br/>(limits & scan)"]
|
|
764
|
-
E --> F{"Verdict?"}
|
|
765
|
-
D -- no --> F{"Scan bytes"}
|
|
766
|
-
F -- malicious/suspicious --> Y["HTTP 422 blocked"]
|
|
767
|
-
F -- clean --> Z["HTTP 200 ok + results"]
|
|
768
|
-
```
|
|
769
|
-
</details>
|
|
770
|
-
|
|
771
|
-
### Sequence (App โ pompelmi โ YARA)
|
|
772
|
-
```mermaid
|
|
773
|
-
sequenceDiagram
|
|
774
|
-
participant U as User
|
|
775
|
-
participant A as App Route (/upload)
|
|
776
|
-
participant P as pompelmi (adapter)
|
|
777
|
-
participant Y as YARA engine
|
|
778
|
-
|
|
779
|
-
U->>A: POST multipart/form-data
|
|
780
|
-
A->>P: guard(files, policies)
|
|
781
|
-
P->>P: MIME sniff + size + ext checks
|
|
782
|
-
alt ZIP archive
|
|
783
|
-
P->>P: unpack entries with limits
|
|
784
|
-
end
|
|
785
|
-
P->>Y: scan(bytes)
|
|
786
|
-
Y-->>P: matches[]
|
|
787
|
-
P-->>A: verdict (clean/suspicious/malicious)
|
|
788
|
-
A-->>U: 200 or 4xx/422 with reason
|
|
789
|
-
```
|
|
790
|
-
<details>
|
|
791
|
-
<summary>Mermaid source</summary>
|
|
82
|
+
## Ecosystem
|
|
792
83
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
84
|
+
- `pompelmi`
|
|
85
|
+
- `@pompelmi/express-middleware`
|
|
86
|
+
- `@pompelmi/koa-middleware`
|
|
87
|
+
- `@pompelmi/next-upload`
|
|
88
|
+
- `@pompelmi/nestjs-integration`
|
|
89
|
+
- `@pompelmi/fastify-plugin`
|
|
90
|
+
- `@pompelmi/ui-react`
|
|
91
|
+
- `@pompelmi/cli`
|
|
799
92
|
|
|
800
|
-
|
|
801
|
-
A->>P: guard(files, policies)
|
|
802
|
-
P->>P: MIME sniff + size + ext checks
|
|
803
|
-
alt ZIP archive
|
|
804
|
-
P->>P: unpack entries with limits
|
|
805
|
-
end
|
|
806
|
-
P->>Y: scan(bytes)
|
|
807
|
-
Y-->>P: matches[]
|
|
808
|
-
P-->>A: verdict (clean/suspicious/malicious)
|
|
809
|
-
A-->>U: 200 or 4xx/422 with reason
|
|
810
|
-
```
|
|
811
|
-
</details>
|
|
93
|
+
## Repository Layout
|
|
812
94
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
express["@pompelmi/express-middleware"]
|
|
819
|
-
koa["@pompelmi/koa-middleware"]
|
|
820
|
-
next["@pompelmi/next-upload"]
|
|
821
|
-
fastify(("fastify-plugin ยท planned"))
|
|
822
|
-
nest(("nestjs ยท planned"))
|
|
823
|
-
remix(("remix ยท planned"))
|
|
824
|
-
hapi(("hapi-plugin ยท planned"))
|
|
825
|
-
svelte(("sveltekit ยท planned"))
|
|
826
|
-
end
|
|
827
|
-
core --> express
|
|
828
|
-
core --> koa
|
|
829
|
-
core --> next
|
|
830
|
-
core -.-> fastify
|
|
831
|
-
core -.-> nest
|
|
832
|
-
core -.-> remix
|
|
833
|
-
core -.-> hapi
|
|
834
|
-
core -.-> svelte
|
|
835
|
-
```
|
|
836
|
-
<details>
|
|
837
|
-
<summary>Mermaid source</summary>
|
|
838
|
-
|
|
839
|
-
```mermaid
|
|
840
|
-
flowchart LR
|
|
841
|
-
subgraph Repo
|
|
842
|
-
core["pompelmi (core)"]
|
|
843
|
-
express["@pompelmi/express-middleware"]
|
|
844
|
-
koa["@pompelmi/koa-middleware"]
|
|
845
|
-
next["@pompelmi/next-upload"]
|
|
846
|
-
fastify(("fastify-plugin ยท planned"))
|
|
847
|
-
nest(("nestjs ยท planned"))
|
|
848
|
-
remix(("remix ยท planned"))
|
|
849
|
-
hapi(("hapi-plugin ยท planned"))
|
|
850
|
-
svelte(("sveltekit ยท planned"))
|
|
851
|
-
end
|
|
852
|
-
core --> express
|
|
853
|
-
core --> koa
|
|
854
|
-
core --> next
|
|
855
|
-
core -.-> fastify
|
|
856
|
-
core -.-> nest
|
|
857
|
-
core -.-> remix
|
|
858
|
-
core -.-> hapi
|
|
859
|
-
core -.-> svelte
|
|
860
|
-
```
|
|
861
|
-
</details>
|
|
95
|
+
- `src/` core library
|
|
96
|
+
- `packages/` framework adapters and supporting packages
|
|
97
|
+
- `examples/` runnable examples
|
|
98
|
+
- `tests/` test coverage
|
|
99
|
+
- `website/` documentation site
|
|
862
100
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
## โ๏ธ Configuration
|
|
866
|
-
|
|
867
|
-
All adapters accept a common set of options:
|
|
868
|
-
|
|
869
|
-
| Option | Type (TS) | Purpose |
|
|
870
|
-
| --- | --- | --- |
|
|
871
|
-
| `scanner` | `{ scan(bytes: Uint8Array): Promise<Match[]> }` | Your scanning engine. Return `[]` when clean; nonโempty to flag. |
|
|
872
|
-
| `includeExtensions` | `string[]` | Allowโlist of file extensions. Evaluated caseโinsensitively. |
|
|
873
|
-
| `allowedMimeTypes` | `string[]` | Allowโlist of MIME types after magicโbyte sniffing. |
|
|
874
|
-
| `maxFileSizeBytes` | `number` | Perโfile size cap. Oversize files are rejected early. |
|
|
875
|
-
| `timeoutMs` | `number` | Perโfile scan timeout; guards against stuck scanners. |
|
|
876
|
-
| `concurrency` | `number` | How many files to scan in parallel. |
|
|
877
|
-
| `failClosed` | `boolean` | If `true`, errors/timeouts block the upload. |
|
|
878
|
-
| `onScanEvent` | `(event: unknown) => void` | Optional telemetry hook for logging/metrics. |
|
|
879
|
-
|
|
880
|
-
**Common recipes**
|
|
881
|
-
|
|
882
|
-
Allow only images up to 5โฏMB:
|
|
883
|
-
|
|
884
|
-
```ts
|
|
885
|
-
includeExtensions: ['png','jpg','jpeg','webp'],
|
|
886
|
-
allowedMimeTypes: ['image/png','image/jpeg','image/webp'],
|
|
887
|
-
maxFileSizeBytes: 5 * 1024 * 1024,
|
|
888
|
-
failClosed: true,
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
---
|
|
892
|
-
|
|
893
|
-
## โ
Production checklist
|
|
894
|
-
|
|
895
|
-
- [ ] **Limit file size** aggressively (`maxFileSizeBytes`).
|
|
896
|
-
- [ ] **Restrict extensions & MIME** to what your app truly needs.
|
|
897
|
-
- [ ] **Set `failClosed: true` in production** to block on timeouts/errors.
|
|
898
|
-
- [ ] **Handle ZIPs carefully** (enable deep ZIP, keep nesting low, cap entry sizes).
|
|
899
|
-
- [ ] **Compose scanners** with `composeScanners()` and enable `stopOn` to fail fast on early detections.
|
|
900
|
-
- [ ] **Log scan events** (`onScanEvent`) and monitor for spikes.
|
|
901
|
-
- [ ] **Run scans in a separate process/container** for defenseโinโdepth when possible.
|
|
902
|
-
- [ ] **Sanitize file names and paths** if you persist uploads.
|
|
903
|
-
- [ ] **Prefer memory storage + postโprocessing**; avoid writing untrusted bytes before policy passes.
|
|
904
|
-
- [ ] **Add CI scanning** with the GitHub Action to catch bad files in repos/artifacts.
|
|
905
|
-
|
|
906
|
-
---
|
|
907
|
-
|
|
908
|
-
## ๐งฌ YARA Getting Started
|
|
909
|
-
|
|
910
|
-
YARA lets you detect suspicious or malicious content using patternโmatching rules.
|
|
911
|
-
**pompelmi** treats YARA matches as signals that you can map to your own verdicts
|
|
912
|
-
(e.g., mark highโconfidence rules as `malicious`, heuristics as `suspicious`).
|
|
913
|
-
|
|
914
|
-
> **Status:** Optional. You can run without YARA. If you adopt it, keep your rules small, timeโbound, and tuned to your threat model.
|
|
915
|
-
|
|
916
|
-
### Starter rules
|
|
917
|
-
|
|
918
|
-
Below are three example rules you can adapt:
|
|
919
|
-
|
|
920
|
-
`rules/starter/eicar.yar`
|
|
921
|
-
```yar
|
|
922
|
-
rule EICAR_Test_File
|
|
923
|
-
{
|
|
924
|
-
meta:
|
|
925
|
-
description = "EICAR antivirus test string (safe)"
|
|
926
|
-
reference = "https://www.eicar.org"
|
|
927
|
-
confidence = "high"
|
|
928
|
-
verdict = "malicious"
|
|
929
|
-
strings:
|
|
930
|
-
$eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
|
|
931
|
-
condition:
|
|
932
|
-
$eicar
|
|
933
|
-
}
|
|
934
|
-
```
|
|
935
|
-
|
|
936
|
-
`rules/starter/pdf_js.yar`
|
|
937
|
-
```yar
|
|
938
|
-
rule PDF_JavaScript_Embedded
|
|
939
|
-
{
|
|
940
|
-
meta:
|
|
941
|
-
description = "PDF contains embedded JavaScript (heuristic)"
|
|
942
|
-
confidence = "medium"
|
|
943
|
-
verdict = "suspicious"
|
|
944
|
-
strings:
|
|
945
|
-
$magic = { 25 50 44 46 } // "%PDF"
|
|
946
|
-
$js1 = "/JavaScript" ascii
|
|
947
|
-
$js2 = "/JS" ascii
|
|
948
|
-
$open = "/OpenAction" ascii
|
|
949
|
-
$aa = "/AA" ascii
|
|
950
|
-
condition:
|
|
951
|
-
uint32(0) == 0x25504446 and ( $js1 or $js2 ) and ( $open or $aa )
|
|
952
|
-
}
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
`rules/starter/office_macros.yar`
|
|
956
|
-
```yar
|
|
957
|
-
rule Office_Macro_Suspicious_Words
|
|
958
|
-
{
|
|
959
|
-
meta:
|
|
960
|
-
description = "Heuristic: suspicious VBA macro keywords"
|
|
961
|
-
confidence = "medium"
|
|
962
|
-
verdict = "suspicious"
|
|
963
|
-
strings:
|
|
964
|
-
$s1 = /Auto(Open|Close)/ nocase
|
|
965
|
-
$s2 = "Document_Open" nocase ascii
|
|
966
|
-
$s3 = "CreateObject(" nocase ascii
|
|
967
|
-
$s4 = "WScript.Shell" nocase ascii
|
|
968
|
-
$s5 = "Shell(" nocase ascii
|
|
969
|
-
$s6 = "Sub Workbook_Open()" nocase ascii
|
|
970
|
-
condition:
|
|
971
|
-
2 of ($s*)
|
|
972
|
-
}
|
|
973
|
-
```
|
|
974
|
-
|
|
975
|
-
> These are **examples**. Expect some false positives; tune to your app.
|
|
976
|
-
|
|
977
|
-
### Minimal integration (adapter contract)
|
|
978
|
-
|
|
979
|
-
If you use a YARA binding (e.g., `@automattic/yara`), wrap it behind the `scanner` contract:
|
|
980
|
-
|
|
981
|
-
```ts
|
|
982
|
-
// Example YARA scanner adapter (pseudoโcode)
|
|
983
|
-
import * as Y from '@automattic/yara';
|
|
984
|
-
|
|
985
|
-
// Compile your rules from disk at boot (recommended)
|
|
986
|
-
// const sources = await fs.readFile('rules/starter/*.yar', 'utf8');
|
|
987
|
-
// const compiled = await Y.compile(sources);
|
|
988
|
-
|
|
989
|
-
export const YourYaraScanner = {
|
|
990
|
-
async scan(bytes: Uint8Array) {
|
|
991
|
-
// const matches = await compiled.scan(bytes, { timeout: 1500 });
|
|
992
|
-
const matches = []; // plug your engine here
|
|
993
|
-
// Map to the structure your app expects; return [] when clean.
|
|
994
|
-
return matches.map((m: any) => ({
|
|
995
|
-
rule: m.rule,
|
|
996
|
-
meta: m.meta ?? {},
|
|
997
|
-
tags: m.tags ?? [],
|
|
998
|
-
}));
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
```
|
|
1002
|
-
|
|
1003
|
-
Then include it in your composed scanner:
|
|
1004
|
-
|
|
1005
|
-
```ts
|
|
1006
|
-
import { composeScanners, CommonHeuristicsScanner } from 'pompelmi';
|
|
1007
|
-
// import { YourYaraScanner } from './yara-scanner';
|
|
1008
|
-
|
|
1009
|
-
export const scanner = composeScanners(
|
|
1010
|
-
[
|
|
1011
|
-
['heuristics', CommonHeuristicsScanner],
|
|
1012
|
-
// ['yara', YourYaraScanner],
|
|
1013
|
-
],
|
|
1014
|
-
{ parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
|
|
1015
|
-
);
|
|
1016
|
-
```
|
|
101
|
+
## Development
|
|
1017
102
|
|
|
1018
|
-
### Policy suggestion (mapping matches โ verdict)
|
|
1019
|
-
|
|
1020
|
-
- **malicious**: highโconfidence rules (e.g., `EICAR_Test_File`)
|
|
1021
|
-
- **suspicious**: heuristic rules (e.g., PDF JavaScript, macro keywords)
|
|
1022
|
-
- **clean**: no matches
|
|
1023
|
-
|
|
1024
|
-
Combine YARA with MIME sniffing, ZIP safety limits, and strict size/time caps.
|
|
1025
|
-
|
|
1026
|
-
## ๐งช Quick test (no EICAR)
|
|
1027
|
-
|
|
1028
|
-
Use the examples above, then send a **minimal PDF** that contains risky tokens (this triggers the builtโin heuristics).
|
|
1029
|
-
|
|
1030
|
-
**1) Create a tiny PDF with risky actions**
|
|
1031
|
-
|
|
1032
|
-
Linux:
|
|
1033
|
-
```bash
|
|
1034
|
-
printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%EOF\n' > risky.pdf
|
|
1035
|
-
```
|
|
1036
|
-
|
|
1037
|
-
macOS:
|
|
1038
|
-
```bash
|
|
1039
|
-
printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%EOF\n' > risky.pdf
|
|
1040
|
-
```
|
|
1041
|
-
|
|
1042
|
-
**2) Send it to your endpoint**
|
|
1043
|
-
|
|
1044
|
-
Express (default from the Quickโstart):
|
|
1045
103
|
```bash
|
|
1046
|
-
|
|
104
|
+
pnpm install
|
|
105
|
+
pnpm test
|
|
106
|
+
pnpm build
|
|
1047
107
|
```
|
|
1048
108
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
---
|
|
1052
|
-
|
|
1053
|
-
## ๐ Security notes
|
|
1054
|
-
|
|
1055
|
-
- The library **reads** bytes; it never executes files.
|
|
1056
|
-
- YARA detections depend on the **rules you provide**; expect some false positives/negatives.
|
|
1057
|
-
- ZIP scanning applies limits (entries, perโentry size, total uncompressed, nesting) to reduce archiveโbomb risk.
|
|
1058
|
-
- Prefer running scans in a **dedicated process/container** for defenseโinโdepth.
|
|
1059
|
-
|
|
1060
|
-
---
|
|
1061
|
-
|
|
1062
|
-
## Releases & security
|
|
1063
|
-
|
|
1064
|
-
- **Changelog / releases:** see [GitHub Releases](https://github.com/pompelmi/pompelmi/releases).
|
|
1065
|
-
- **Security disclosures:** please use [GitHub Security Advisories](https://github.com/pompelmi/pompelmi/security/advisories). Weโll coordinate a fix before public disclosure.
|
|
1066
|
-
- **Production users:** open a [Discussion](https://github.com/pompelmi/pompelmi/discussions) to share requirements or request adapters.
|
|
1067
|
-
|
|
1068
|
-
## โญ Star history
|
|
109
|
+
## Links
|
|
1069
110
|
|
|
1070
|
-
[
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
111
|
+
- [Documentation](https://pompelmi.github.io/pompelmi/)
|
|
112
|
+
- [Examples](./examples)
|
|
113
|
+
- [Contributing](./CONTRIBUTING.md)
|
|
114
|
+
- [Security](./SECURITY.md)
|
|
115
|
+
- [Roadmap](./ROADMAP.md)
|
|
1075
116
|
|
|
1076
117
|
<!-- MENTIONS:START -->
|
|
1077
118
|
|
|
@@ -1102,154 +143,6 @@ You should see an HTTP **422 Unprocessable Entity** (blocked by policy). Clean f
|
|
|
1102
143
|
|
|
1103
144
|
<!-- MENTIONS:END -->
|
|
1104
145
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
**Need help? We're here for you!**
|
|
1108
|
-
|
|
1109
|
-
- ๐ **[Documentation](https://pompelmi.github.io/pompelmi/)** โ Complete API reference, guides, and tutorials
|
|
1110
|
-
- ๐ฌ **[GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)** โ Ask questions, share ideas, get community support
|
|
1111
|
-
- ๐ **[Issue Tracker](https://github.com/pompelmi/pompelmi/issues)** โ Report bugs, request features
|
|
1112
|
-
- ๐ **[Security Policy](https://github.com/pompelmi/pompelmi/security)** โ Report security vulnerabilities privately
|
|
1113
|
-
- ๐ผ **Commercial Support** โ For enterprise support and consulting, contact the maintainers
|
|
1114
|
-
|
|
1115
|
-
**Supported Frameworks:**
|
|
1116
|
-
- โ
Express
|
|
1117
|
-
- โ
Koa
|
|
1118
|
-
- โ
Next.js (App & Pages Router)
|
|
1119
|
-
- โ
NestJS
|
|
1120
|
-
- โ
Fastify (alpha)
|
|
1121
|
-
- ๐ Remix (planned)
|
|
1122
|
-
- ๐ SvelteKit (planned)
|
|
1123
|
-
- ๐ hapi (planned)
|
|
1124
|
-
|
|
1125
|
-
---
|
|
1126
|
-
|
|
1127
|
-
## ๐ Star History
|
|
1128
|
-
|
|
1129
|
-
<p align="center">
|
|
1130
|
-
<a href="https://star-history.com/#pompelmi/pompelmi&Date">
|
|
1131
|
-
<img src="https://api.star-history.com/svg?repos=pompelmi/pompelmi&type=Date" alt="Star History Chart" />
|
|
1132
|
-
</a>
|
|
1133
|
-
</p>
|
|
1134
|
-
|
|
1135
|
-
---
|
|
1136
|
-
|
|
1137
|
-
## ๐๏ธ Contributors
|
|
1138
|
-
|
|
1139
|
-
Thanks to all the amazing contributors who have helped make pompelmi better!
|
|
1140
|
-
|
|
1141
|
-
<p align="center">
|
|
1142
|
-
<a href="https://github.com/pompelmi/pompelmi/graphs/contributors">
|
|
1143
|
-
<img src="https://contrib.rocks/image?repo=pompelmi/pompelmi" alt="Contributors" />
|
|
1144
|
-
</a>
|
|
1145
|
-
</p>
|
|
1146
|
-
|
|
1147
|
-
<p align="center">
|
|
1148
|
-
<em>Want to contribute? Check out our <a href="./CONTRIBUTING.md">Contributing Guide</a>!</em>
|
|
1149
|
-
</p>
|
|
1150
|
-
|
|
1151
|
-
---
|
|
1152
|
-
|
|
1153
|
-
## ๐ฌ FAQ
|
|
1154
|
-
|
|
1155
|
-
**Do I need YARA?**
|
|
1156
|
-
No. `scanner` is pluggable. The examples use a minimal scanner for clarity; you can call out to a YARA engine or any other detector you prefer.
|
|
1157
|
-
|
|
1158
|
-
**Where do the results live?**
|
|
1159
|
-
In the examples, the guard attaches scan data to the request context (e.g. `req.pompelmi` in Express, `ctx.pompelmi` in Koa). In Next.js, include the results in your JSON response as you see fit.
|
|
1160
|
-
|
|
1161
|
-
**Why 422 for blocked files?**
|
|
1162
|
-
Using **422** to signal a policy violation keeps it distinct from transport errors; itโs a common pattern. Use the codes that best match your API guidelines.
|
|
1163
|
-
|
|
1164
|
-
**Are ZIP bombs handled?**
|
|
1165
|
-
Archives are traversed with limits to reduce archiveโbomb risk. Keep your size limits conservative and prefer `failClosed: true` in production.
|
|
1166
|
-
|
|
1167
|
-
---
|
|
1168
|
-
|
|
1169
|
-
## ๐งช Tests & Coverage
|
|
1170
|
-
|
|
1171
|
-
Run tests locally with coverage:
|
|
1172
|
-
|
|
1173
|
-
```bash
|
|
1174
|
-
pnpm vitest run --coverage --passWithNoTests
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
The badge tracks the **core library** (`src/**`). Adapters and engines are reported separately for now and will be folded into global coverage as their suites grow.
|
|
1178
|
-
|
|
1179
|
-
If you integrate Codecov in CI, upload `coverage/lcov.info` and you can use this Codecov badge:
|
|
1180
|
-
|
|
1181
|
-
```md
|
|
1182
|
-
[](https://codecov.io/gh/pompelmi/pompelmi)
|
|
1183
|
-
```
|
|
1184
|
-
|
|
1185
|
-
## ๐ค Contributing
|
|
1186
|
-
|
|
1187
|
-
PRs and issues welcome! Start with:
|
|
1188
|
-
|
|
1189
|
-
```bash
|
|
1190
|
-
pnpm -r build
|
|
1191
|
-
pnpm -r lint
|
|
1192
|
-
```
|
|
1193
|
-
|
|
1194
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines.
|
|
1195
|
-
|
|
1196
|
-
---
|
|
1197
|
-
|
|
1198
|
-
## ๐ Learning Resources
|
|
1199
|
-
|
|
1200
|
-
### ๐ Documentation
|
|
1201
|
-
|
|
1202
|
-
- [Official Docs](https://pompelmi.github.io/pompelmi/) โ Complete API reference and guides
|
|
1203
|
-
- [Examples](./examples/) โ Real-world integration examples
|
|
1204
|
-
- [Security Guide](./SECURITY.md) โ Security best practices and disclosure policy
|
|
1205
|
-
|
|
1206
|
-
### ๐ฅ Tutorials & Articles
|
|
1207
|
-
|
|
1208
|
-
- **File Upload Security in Node.js** โ Best practices guide (coming soon)
|
|
1209
|
-
- **Integrating YARA with pompelmi** โ Advanced detection setup (coming soon)
|
|
1210
|
-
- **Zero-Trust File Uploads** โ Architecture patterns (coming soon)
|
|
1211
|
-
|
|
1212
|
-
### ๐ ๏ธ Tools & Integrations
|
|
1213
|
-
|
|
1214
|
-
- [GitHub Action](https://github.com/pompelmi/pompelmi/tree/main/.github/actions/pompelmi-scan) โ CI/CD scanning
|
|
1215
|
-
- [Docker Images](https://hub.docker.com/r/pompelmi/pompelmi) โ Containerized scanning (coming soon)
|
|
1216
|
-
- [Cloud Functions](https://github.com/pompelmi/cloud-functions) โ Serverless examples (coming soon)
|
|
1217
|
-
|
|
1218
|
-
---
|
|
1219
|
-
|
|
1220
|
-
## ๐ Project Stats
|
|
1221
|
-
|
|
1222
|
-
<p align="center">
|
|
1223
|
-
<img src="https://repobeats.axiom.co/api/embed/YOUR_EMBED_ID.svg" alt="Repobeats analytics" />
|
|
1224
|
-
</p>
|
|
1225
|
-
|
|
1226
|
-
---
|
|
1227
|
-
|
|
1228
|
-
## ๐ Acknowledgments
|
|
1229
|
-
|
|
1230
|
-
pompelmi stands on the shoulders of giants. Special thanks to:
|
|
1231
|
-
|
|
1232
|
-
- The YARA project for powerful pattern matching
|
|
1233
|
-
- The Node.js community for excellent tooling
|
|
1234
|
-
- All our contributors and users
|
|
1235
|
-
|
|
1236
|
-
---
|
|
1237
|
-
|
|
1238
|
-
## ๐ Support
|
|
1239
|
-
|
|
1240
|
-
Need help? We're here for you!
|
|
1241
|
-
|
|
1242
|
-
- ๐ [Documentation](https://pompelmi.github.io/pompelmi/)
|
|
1243
|
-
- ๐ฌ [GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)
|
|
1244
|
-
- ๐ [Issue Tracker](https://github.com/pompelmi/pompelmi/issues)
|
|
1245
|
-
- ๐ [Security](https://github.com/pompelmi/pompelmi/security) (for vulnerabilities)
|
|
1246
|
-
|
|
1247
|
-
For commercial support and consulting, contact the maintainers.
|
|
1248
|
-
|
|
1249
|
-
---
|
|
1250
|
-
|
|
1251
|
-
<p align="right"><a href="#pompelmi">โ Back to top</a></p>
|
|
1252
|
-
|
|
1253
|
-
## ๐ License
|
|
146
|
+
## License
|
|
1254
147
|
|
|
1255
|
-
[MIT](./LICENSE)
|
|
148
|
+
[MIT](./LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pompelmi",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.8",
|
|
4
4
|
"description": "Secure file uploads for Node.js. Scan untrusted files before storage with in-process, local-first checks for MIME spoofing, archive bombs, risky document structures, and optional YARA.",
|
|
5
5
|
"main": "./dist/pompelmi.cjs",
|
|
6
6
|
"module": "./dist/pompelmi.esm.js",
|