pompelmi 0.34.6 โ†’ 0.34.7

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.
Files changed (2) hide show
  1. package/README.md +1187 -80
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,118 +1,1077 @@
1
1
  <div align="center">
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>
2
+
3
+ <!-- Language Selector -->
4
+ <p>
5
+ <strong>Read this in other languages:</strong><br/>
6
+ <a href="docs/i18n/README.it.md">๐Ÿ‡ฎ๐Ÿ‡น Italiano</a> โ€ข
7
+ <a href="docs/i18n/README.fr.md">๐Ÿ‡ซ๐Ÿ‡ท Franรงais</a> โ€ข
8
+ <a href="docs/i18n/README.es.md">๐Ÿ‡ช๐Ÿ‡ธ Espaรฑol</a> โ€ข
9
+ <a href="docs/i18n/README.de.md">๐Ÿ‡ฉ๐Ÿ‡ช Deutsch</a> โ€ข
10
+ <a href="docs/i18n/README.ja.md">๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ่ชž</a> โ€ข
11
+ <a href="docs/i18n/README.zh-CN.md">๐Ÿ‡จ๐Ÿ‡ณ ็ฎ€ไฝ“ไธญๆ–‡</a> โ€ข
12
+ <a href="docs/i18n/README.ko.md">๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ์–ด</a> โ€ข
13
+ <a href="docs/i18n/README.pt-BR.md">๐Ÿ‡ง๐Ÿ‡ท Portuguรชs</a> โ€ข
14
+ <a href="docs/i18n/README.ru.md">๐Ÿ‡ท๐Ÿ‡บ ะ ัƒััะบะธะน</a> โ€ข
15
+ <a href="docs/i18n/README.tr.md">๐Ÿ‡น๐Ÿ‡ท Tรผrkรงe</a>
16
+ </p>
17
+
18
+ > ๐Ÿ’ก **Translation Note:** Help improve translations by opening a PR. The English README is the source of truth.
19
+
23
20
  </div>
24
21
 
25
- Pompelmi inspects untrusted files before storage and helps you decide whether to allow, reject, or quarantine them before they reach downstream systems.
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
26
286
 
27
- It is built for upload endpoints that cannot rely on filenames, extensions, or client-provided MIME types alone.
287
+ Optimized for high-throughput scenarios with configurable concurrency, streaming support, and minimal memory overhead. Process thousands of files without breaking a sweat. Scans run in-process with no IPC overhead.
28
288
 
29
- ## Demo
289
+ ### ๐Ÿ” Security Without Compromise
30
290
 
31
- ![Pompelmi demo](assets/malware-detection-node-demo.gif)
291
+ Multi-layered defense including MIME type verification (magic bytes), extension validation, size limits, ZIP bomb protection, and optional YARA integration. Each layer is configurable to match your threat model.
32
292
 
33
- ## Install
293
+ ### ๐ŸŒ Privacy Guaranteed
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
34
386
 
35
387
  ```bash
36
388
  npm install pompelmi
37
389
  ```
38
390
 
39
- Requires Node.js 18+.
391
+ ### Step 2: Create Security Policy
40
392
 
41
- ## Quick Start
393
+ Create a reusable security policy and scanner configuration:
42
394
 
43
395
  ```ts
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
- },
396
+ // lib/security.ts
397
+ import { CommonHeuristicsScanner, createZipBombGuard, composeScanners } from 'pompelmi';
398
+
399
+ export const policy = {
400
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt'],
401
+ allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
402
+ maxFileSizeBytes: 20 * 1024 * 1024, // 20MB
403
+ timeoutMs: 5000,
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 }
52
488
  });
53
489
 
54
- if (!report.ok) {
55
- return res.status(422).json({
56
- error: 'Upload blocked',
57
- verdict: report.verdict,
58
- reasons: report.reasons,
490
+ router.post('/upload',
491
+ upload.any(),
492
+ createKoaUploadGuard({ ...policy, scanner }),
493
+ (ctx) => {
494
+ ctx.body = {
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 || []
59
595
  });
60
596
  }
61
597
  ```
62
598
 
63
- ## What Problem It Solves
599
+ ### Example 3: NestJS Controller
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
64
709
 
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.
710
+ | Framework | Package | Status | Install |
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 |
66
721
 
67
- Pompelmi adds checks at the upload boundary for:
722
+ See the [๐Ÿ“˜ Code Examples](#-code-examples) section above for integration examples.
68
723
 
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
724
+ ---
73
725
 
74
- The goal is simple: inspect first, store later.
726
+ | Framework | Package | Status |
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 |
75
736
 
76
- ## Why This Shape
737
+ ---
77
738
 
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
739
+ ## ๐Ÿ—บ๏ธ Diagrams
81
740
 
82
- ## Ecosystem
741
+ ### Upload scanning flow
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>
83
792
 
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`
793
+ ```mermaid
794
+ sequenceDiagram
795
+ participant U as User
796
+ participant A as App Route (/upload)
797
+ participant P as pompelmi (adapter)
798
+ participant Y as YARA engine
92
799
 
93
- ## Repository Layout
800
+ U->>A: POST multipart/form-data
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>
94
812
 
95
- - `src/` core library
96
- - `packages/` framework adapters and supporting packages
97
- - `examples/` runnable examples
98
- - `tests/` test coverage
99
- - `website/` documentation site
813
+ ### Components (monorepo)
814
+ ```mermaid
815
+ flowchart LR
816
+ subgraph Repo
817
+ core["pompelmi (core)"]
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>
100
862
 
101
- ## Development
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
+ ```
102
1017
 
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):
103
1045
  ```bash
104
- pnpm install
105
- pnpm test
106
- pnpm build
1046
+ curl -F "file=@risky.pdf;type=application/pdf" http://localhost:3000/upload -i
107
1047
  ```
108
1048
 
109
- ## Links
1049
+ You should see an HTTP **422 Unprocessable Entity** (blocked by policy). Clean files return **200 OK**. Preโ€‘filter failures (size/ext/MIME) should return a **4xx**. Adapt these conventions to your app as needed.
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
110
1069
 
111
- - [Documentation](https://pompelmi.github.io/pompelmi/)
112
- - [Examples](./examples)
113
- - [Contributing](./CONTRIBUTING.md)
114
- - [Security](./SECURITY.md)
115
- - [Roadmap](./ROADMAP.md)
1070
+ [![Star History Chart](https://api.star-history.com/svg?repos=pompelmi/pompelmi&type=Date)](https://star-history.com/#pompelmi/pompelmi&Date)
1071
+
1072
+ ---
1073
+
1074
+ ---
116
1075
 
117
1076
  <!-- MENTIONS:START -->
118
1077
 
@@ -143,6 +1102,154 @@ pnpm build
143
1102
 
144
1103
  <!-- MENTIONS:END -->
145
1104
 
146
- ## License
1105
+ ### ๐Ÿค Community & Support
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
+ [![codecov](https://codecov.io/gh/pompelmi/pompelmi/branch/main/graph/badge.svg?flag=core)](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
147
1254
 
148
- [MIT](./LICENSE)
1255
+ [MIT](./LICENSE) ยฉ 2025โ€‘present pompelmi contributors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pompelmi",
3
- "version": "0.34.6",
3
+ "version": "0.34.7",
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",