pompelmi 0.32.1 → 0.34.0

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 (43) hide show
  1. package/README.md +355 -957
  2. package/dist/pompelmi.audit.cjs +130 -0
  3. package/dist/pompelmi.audit.cjs.map +1 -0
  4. package/dist/pompelmi.audit.esm.js +109 -0
  5. package/dist/pompelmi.audit.esm.js.map +1 -0
  6. package/dist/pompelmi.browser.cjs +1455 -0
  7. package/dist/pompelmi.browser.cjs.map +1 -0
  8. package/dist/pompelmi.browser.esm.js +1429 -0
  9. package/dist/pompelmi.browser.esm.js.map +1 -0
  10. package/dist/pompelmi.cjs +1403 -3118
  11. package/dist/pompelmi.cjs.map +1 -1
  12. package/dist/pompelmi.esm.js +1397 -3116
  13. package/dist/pompelmi.esm.js.map +1 -1
  14. package/dist/pompelmi.hooks.cjs +75 -0
  15. package/dist/pompelmi.hooks.cjs.map +1 -0
  16. package/dist/pompelmi.hooks.esm.js +72 -0
  17. package/dist/pompelmi.hooks.esm.js.map +1 -0
  18. package/dist/pompelmi.policy-packs.cjs +239 -0
  19. package/dist/pompelmi.policy-packs.cjs.map +1 -0
  20. package/dist/pompelmi.policy-packs.esm.js +231 -0
  21. package/dist/pompelmi.policy-packs.esm.js.map +1 -0
  22. package/dist/pompelmi.quarantine.cjs +315 -0
  23. package/dist/pompelmi.quarantine.cjs.map +1 -0
  24. package/dist/pompelmi.quarantine.esm.js +291 -0
  25. package/dist/pompelmi.quarantine.esm.js.map +1 -0
  26. package/dist/pompelmi.react.cjs +1486 -0
  27. package/dist/pompelmi.react.cjs.map +1 -0
  28. package/dist/pompelmi.react.esm.js +1459 -0
  29. package/dist/pompelmi.react.esm.js.map +1 -0
  30. package/dist/types/audit.d.ts +84 -0
  31. package/dist/types/browser-index.d.ts +28 -2
  32. package/dist/types/config.d.ts +3 -2
  33. package/dist/types/hooks.d.ts +89 -0
  34. package/dist/types/index.d.ts +17 -9
  35. package/dist/types/policy-packs.d.ts +98 -0
  36. package/dist/types/quarantine/index.d.ts +18 -0
  37. package/dist/types/quarantine/storage.d.ts +77 -0
  38. package/dist/types/quarantine/types.d.ts +78 -0
  39. package/dist/types/quarantine/workflow.d.ts +97 -0
  40. package/dist/types/react-index.d.ts +13 -0
  41. package/dist/types/scanners/common-heuristics.d.ts +2 -2
  42. package/dist/types/types.d.ts +0 -1
  43. package/package.json +55 -4
package/README.md CHANGED
@@ -1,104 +1,64 @@
1
1
  <div align="center">
2
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
-
20
- </div>
21
-
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://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/"><img alt="Featured on HelpNet Security" src="https://img.shields.io/badge/🔒_FEATURED-HelpNet%20Security-FF6B35?style=for-the-badge"></a>
38
- <a href="https://snyk.io/test/github/pompelmi/pompelmi"><img alt="Secured by Snyk" src="https://img.shields.io/badge/🛡️_SECURED_BY-Snyk-4C4A73?style=for-the-badge&logo=snyk"></a>
39
- <br/>
40
- <a href="https://github.com/sorrycc/awesome-javascript"><img alt="Mentioned in Awesome JavaScript" src="https://awesome.re/mentioned-badge.svg"></a>
41
- <a href="https://github.com/dzharii/awesome-typescript"><img alt="Mentioned in Awesome TypeScript" src="https://awesome.re/mentioned-badge-flat.svg"></a>
42
- <a href="https://github.com/sbilly/awesome-security"><img alt="Mentioned in Awesome Security" src="https://awesome.re/mentioned-badge.svg"></a>
43
- <a href="https://github.com/sindresorhus/awesome-nodejs"><img alt="Mentioned in Awesome Node.js" src="https://awesome.re/mentioned-badge-flat.svg"></a>
44
- <br/><br/>
45
- </p>
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg">
5
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg">
6
+ <img src="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg" alt="pompelmi" width="320" />
7
+ </picture>
46
8
 
47
- <h1 align="center">pompelmi</h1>
9
+ <h1>pompelmi</h1>
48
10
 
49
- <p align="center">
50
- <strong>Secure File Upload Scanning for Node.js</strong>
51
- </p>
52
-
53
- <p align="center">
54
- <em>Privacy-first malware detection with YARA, ZIP bomb protection, and framework adapters</em>
55
- </p>
11
+ <p><strong>Secure file upload scanning for Node.js — private, in-process, zero cloud dependencies.</strong></p>
56
12
 
57
- <p align="center">
58
- Scan files before they hit disk • <strong>Keep user data private</strong> <strong>Zero cloud dependencies</strong>
13
+ <p>
14
+ Scan files <em>before</em> they touch disk &nbsp;•&nbsp;
15
+ No cloud APIs, no daemon &nbsp;•&nbsp;
16
+ TypeScript-first &nbsp;•&nbsp;
17
+ Drop-in framework adapters
59
18
  </p>
60
19
 
61
- ---
62
-
63
- <!-- Badges Section -->
64
- <p align="center">
20
+ <p>
65
21
  <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>
66
22
  <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>
67
23
  <a href="https://github.com/pompelmi/pompelmi/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/pompelmi?color=blue"></a>
68
24
  <img alt="node" src="https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white">
69
- <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>
70
- </p>
71
-
72
- <p align="center">
25
+ <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?branch=main&label=CI&logo=github"></a>
73
26
  <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>
74
27
  <img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white">
75
28
  <img alt="ESM" src="https://img.shields.io/badge/ESM%2FCJS-compatible-yellow">
76
- <a href="https://snyk.io/test/github/pompelmi/pompelmi"><img alt="Known Vulnerabilities" src="https://snyk.io/test/github/pompelmi/pompelmi/badge.svg"></a>
29
+ <a href="https://snyk.io/test/github/pompelmi/pompelmi"><img alt="Snyk" src="https://snyk.io/test/github/pompelmi/pompelmi/badge.svg"></a>
77
30
  <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>
78
31
  </p>
79
32
 
80
- <p align="center">
81
- <a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social"></a>
82
- <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>
83
- <a href="https://github.com/pompelmi/pompelmi/watchers"><img alt="GitHub watchers" src="https://img.shields.io/github/watchers/pompelmi/pompelmi?style=social"></a>
84
- <a href="https://github.com/pompelmi/pompelmi/issues"><img alt="open issues" src="https://img.shields.io/github/issues/pompelmi/pompelmi?color=orange"></a>
85
- <a href="https://github.com/sponsors/pompelmi"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/pompelmi?style=social&label=Sponsor"></a>
33
+ <p>
34
+ <a href="https://pompelmi.github.io/pompelmi/"><strong>📚 Docs</strong></a> &nbsp;•&nbsp;
35
+ <a href="#-installation"><strong>💾 Install</strong></a> &nbsp;•&nbsp;
36
+ <a href="#-quickstart"><strong>⚡ Quickstart</strong></a> &nbsp;•&nbsp;
37
+ <a href="#-framework-adapters"><strong>🧩 Adapters</strong></a> &nbsp;•&nbsp;
38
+ <a href="#-yara"><strong>🧬 YARA</strong></a> &nbsp;•&nbsp;
39
+ <a href="#-github-action"><strong>🤖 CI/CD</strong></a> &nbsp;•&nbsp;
40
+ <a href="./examples/"><strong>💡 Examples</strong></a>
86
41
  </p>
87
42
 
88
- <p align="center">
89
- <strong>
90
- <a href="https://pompelmi.github.io/pompelmi/">📚 Documentation</a> •
91
- <a href="#-installation">💾 Install</a> •
92
- <a href="#-quickstart">⚡ Quickstart</a> •
93
- <a href="#-adapters">🧩 Adapters</a> •
94
- <a href="#-yara-getting-started">🧬 YARA</a> •
95
- <a href="#-github-action">🤖 CI/CD</a>
96
- </strong>
97
- </p>
43
+ </div>
44
+
45
+ ---
46
+
47
+ ## Why pompelmi?
98
48
 
99
- <p align="center"><em>Coverage badge reflects core library (<code>src/**</code>); adapters are measured separately.</em></p>
49
+ Most upload handlers check the file extension and content-type header — and stop there. Real threats arrive as ZIP bombs, polyglot files, macro-embedded documents, and files with spoofed MIME types.
100
50
 
101
- <!-- HERO END -->
51
+ **pompelmi scans file bytes in-process, before anything is written to disk or stored**, blocking threats at the earliest possible point — with no cloud API and no daemon.
52
+
53
+ | | pompelmi | ClamAV | Cloud AV APIs |
54
+ |---|---|---|---|
55
+ | **Setup** | `npm install` | Daemon + config | API keys + integration |
56
+ | **Privacy** | ✅ In-process — data stays local | ✅ Local (separate daemon) | ❌ Files sent externally |
57
+ | **Latency** | ✅ Zero (no IPC, no network) | IPC overhead | Network round-trip |
58
+ | **Cost** | Free (MIT) | Free (GPL) | Per-scan billing |
59
+ | **Framework adapters** | ✅ Express, Koa, Next.js, NestJS, Fastify | ❌ | ❌ |
60
+ | **TypeScript** | ✅ First-class | community types | varies |
61
+ | **YARA** | ✅ Built-in | manual setup | limited |
102
62
 
103
63
  ---
104
64
 
@@ -108,13 +68,13 @@
108
68
  npm install pompelmi
109
69
  ```
110
70
 
111
- > Node.js 18+ required. No daemon, no cloud API keys, no configuration files needed to get started.
71
+ > Node.js 18+. No daemon, no config files, no API keys required.
112
72
 
113
73
  ---
114
74
 
115
75
  ## ⚡ Quickstart
116
76
 
117
- Scan a file and act on the result in three lines:
77
+ Scan a file and get a verdict in three lines:
118
78
 
119
79
  ```ts
120
80
  import { scanFile } from 'pompelmi';
@@ -123,13 +83,11 @@ const result = await scanFile('path/to/upload.pdf');
123
83
  // result.verdict → "clean" | "suspicious" | "malicious"
124
84
 
125
85
  if (result.verdict !== 'clean') {
126
- console.error('Blocked:', result.verdict, result.reasons);
127
- } else {
128
- console.log('Safe to process.');
86
+ throw new Error(`Blocked: ${result.verdict} — ${result.reasons}`);
129
87
  }
130
88
  ```
131
89
 
132
- That's it. No server required, no framework dependency — works standalone in any Node.js script or service.
90
+ Works standalone in any Node.js context no framework required.
133
91
 
134
92
  ---
135
93
 
@@ -137,373 +95,82 @@ That's it. No server required, no framework dependency — works standalone in a
137
95
 
138
96
  ![Pompelmi Demo](./assets/malware-detection-node-demo.gif)
139
97
 
140
- **Want to try it now?** Check out our [live examples](./examples/) or install and run locally:
98
+ **Try it now:** browse the [examples/](./examples/) directory or run a sample locally:
141
99
 
142
100
  ```bash
143
- npm i pompelmi @pompelmi/express-middleware
101
+ npx tsx examples/scan-one-file.ts
144
102
  ```
145
103
 
146
104
  ---
147
105
 
148
- ## Features
149
-
150
- **pompelmi** provides enterprise-grade file scanning for Node.js applications:
151
-
152
- - **🔒 Privacy-First Architecture** — All scanning happens in-process. **No cloud calls, no data leaks.** Your files never leave your infrastructure.
153
- - **⚡ Lightning Fast** — In-process scanning with **zero network latency**. Configurable concurrency for high-throughput scenarios.
154
- - **🧩 Composable Scanners** — Mix heuristics + signatures; set `stopOn` and timeouts. Bring your own YARA rules.
155
- - **📦 Deep ZIP Inspection** — Traversal/bomb guards, polyglot & macro hints, nested archive scanning with configurable depth limits.
156
- - **🔌 Framework Adapters** — Drop-in middleware for Express, Koa, Fastify, Next.js, Nuxt/Nitro, and **NestJS** with first-class TypeScript support.
157
- - **🌊 Stream-Based Processing** — Memory-efficient scanning with configurable buffer limits. Scan large files without loading them entirely into memory.
158
- - **🔍 Polyglot Detection** — Advanced magic bytes analysis detects mixed-format files and embedded scripts with **30+ file signatures**.
159
- - **⚙️ CLI for CI/CD** — Standalone command-line tool for scanning files and directories with watch mode and multiple output formats.
160
- - **📘 TypeScript-First** — Complete type definitions, modern ESM/CJS builds, minimal surface, tree-shakeable.
161
- - **⚡ Zero Core Dependencies** — Core library has minimal deps for fast installation and reduced supply chain risk.
162
-
163
- ---
106
+ ## Why developers choose pompelmi
164
107
 
165
- ## Table of Contents
166
-
167
- - [Installation](#-installation)
168
- - [Quickstart](#-quickstart)
169
- - [Demo](#-demo)
170
- - [Features](#-features)
171
- - [Why pompelmi?](#-why-pompelmi)
172
- - [Use Cases](#-use-cases)
173
- - [Getting Started](#-getting-started)
174
- - [Code Examples](#-code-examples)
175
- - [Adapters](#-adapters)
176
- - [GitHub Action](#-github-action)
177
- - [Diagrams](#️-diagrams)
178
- - [Configuration](#️-configuration)
179
- - [Production Checklist](#-production-checklist)
180
- - [YARA Getting Started](#-yara-getting-started)
181
- - [Security Notes](#-security-notes)
182
- - [Releases & Security](#-releases--security)
183
- - [Community & Recognition](#-community--recognition)
184
- - [FAQ](#-faq)
185
- - [Tests & Coverage](#-tests--coverage)
186
- - [Contributing](#-contributing)
187
- - [License](#-license)
108
+ - **Privacy-first** all scanning is in-process; no bytes leave your infrastructure, ever.
109
+ - **No daemon, no sidecar** — install like any npm package and start scanning immediately.
110
+ - **Blocks early** — runs before you write to disk, persist to storage, or pass files to other services.
111
+ - **Defense-in-depth** — magic-byte MIME sniffing, extension allow-lists, size caps, ZIP bomb guards, polyglot detection.
112
+ - **Composable** — chain heuristics, YARA rules, and custom scanners with `composeScanners`. Set `stopOn` and per-scanner timeouts.
113
+ - **Framework-friendly** — drop-in middleware for Express, Koa, Next.js, NestJS, Nuxt/Nitro, and Fastify.
114
+ - **TypeScript-first** — complete types, modern ESM/CJS builds, tree-shakeable, minimal core dependencies.
115
+ - **CI/CD ready** — GitHub Action to scan files and artifacts in pipelines.
188
116
 
189
117
  ---
190
118
 
191
- ## 🌍 Translations
192
-
193
- pompelmi documentation is available in multiple languages to help developers worldwide:
194
-
195
- - 🇮🇹 **[Italiano (Italian)](docs/i18n/README.it.md)** — Documentazione completa in italiano
196
- - 🇫🇷 **[Français (French)](docs/i18n/README.fr.md)** — Documentation complète en français
197
- - 🇪🇸 **[Español (Spanish)](docs/i18n/README.es.md)** — Documentación completa en español
198
- - 🇩🇪 **[Deutsch (German)](docs/i18n/README.de.md)** — Vollständige Dokumentation auf Deutsch
199
- - 🇯🇵 **[日本語 (Japanese)](docs/i18n/README.ja.md)** — 日本語による完全なドキュメント
200
- - 🇨🇳 **[简体中文 (Simplified Chinese)](docs/i18n/README.zh-CN.md)** — 完整的简体中文文档
201
- - 🇰🇷 **[한국어 (Korean)](docs/i18n/README.ko.md)** — 완전한 한국어 문서
202
- - 🇧🇷 **[Português (Brasil)](docs/i18n/README.pt-BR.md)** — Documentação completa em português
203
- - 🇷🇺 **[Русский (Russian)](docs/i18n/README.ru.md)** — Полная документация на русском
204
- - 🇹🇷 **[Türkçe (Turkish)](docs/i18n/README.tr.md)** — Türkçe tam dokümantasyon
119
+ ## 🧩 Framework adapters
205
120
 
206
- **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.
207
-
208
- ---
121
+ All adapters share the same policy options and scanning contract. Install only what you need.
209
122
 
210
- ## 🧠 Why pompelmi?
123
+ | Framework | Package | Status |
124
+ |---|---|---|
125
+ | **Express** | `@pompelmi/express-middleware` | ✅ Stable |
126
+ | **Next.js** | `@pompelmi/next-upload` | ✅ Stable |
127
+ | **Koa** | `@pompelmi/koa-middleware` | ✅ Stable |
128
+ | **NestJS** | `@pompelmi/nestjs-integration` | ✅ Stable |
129
+ | **Nuxt / Nitro** | built-in `pompelmi` | ✅ [Guide](https://pompelmi.github.io/pompelmi/how-to/nuxt-nitro/) |
130
+ | **Fastify** | `@pompelmi/fastify-plugin` | 🔶 Alpha |
131
+ | **Remix / SvelteKit / hapi** | — | 🔜 Planned |
211
132
 
212
- pompelmi delivers **Privacy-First** malware detection with **Zero Cloud Dependencies** — keeping your data secure and your latency zero.
213
-
214
- ### Why Choose Pompelmi?
215
-
216
- - **On‑device, private scanning** – no outbound calls, no data sharing.
217
- - **Blocks early** runs _before_ you write to disk or persist anything.
218
- - **Fits your stack** drop‑in adapters for Express, Koa, Next.js, Nuxt/Nitro (Fastify plugin in alpha).
219
- - **Defense‑in‑depth** – ZIP traversal limits, ratio caps, server‑side MIME sniffing, size caps.
220
- - **Pluggable detection** – bring your own engine (e.g., YARA) via a tiny `{ scan(bytes) }` contract.
221
-
222
- ### Who is it for?
223
-
224
- - Teams who can’t send uploads to third‑party AV APIs.
225
- - Apps that need predictable, low‑latency decisions inline.
226
- - Developers who want simple, typed building blocks instead of a daemon.
227
-
228
- ### Comparison Table
229
-
230
- | Feature | **Pompelmi** | ClamAV | Cloud APIs (VirusTotal, etc.) |
231
- |---------|-------------|---------|-------------------------------|
232
- | **Setup Time** | ⚡ Seconds (`npm install`) | ⏱️ Complex (daemon setup) | ⏱️ API keys + integration |
233
- | **Privacy** | ✅ **In-process** (data never leaves) | ✅ Local (separate daemon) | ❌ **External** (data sent to cloud) |
234
- | **Latency** | ⚡ **Zero** (no network calls) | 🔄 IPC overhead | 🌐 **High** (network roundtrip) |
235
- | **Cost** | 💰 **Free** (MIT license) | 💰 Free (GPL) | 💸 **Pay-per-scan** |
236
- | **Framework Integration** | ✅ Express, Koa, Next.js, NestJS | ❌ Manual integration | ❌ Manual integration |
237
- | **TypeScript Support** | ✅ First-class | ❌ Community types | ❓ Varies |
238
- | **YARA Integration** | ✅ Built-in | ⚙️ Manual setup | ❓ Limited |
239
-
240
- ### 🎯 Developer Experience
241
-
242
- 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.
243
-
244
- ### 🚀 Performance First
245
-
246
- Optimized for high-throughput scenarios with configurable concurrency, streaming support, and minimal memory overhead. Scans run in-process with no IPC overhead.
247
-
248
- ### 🔐 Security Without Compromise
249
-
250
- 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.
251
-
252
- ### 🌍 Privacy Guaranteed
253
-
254
- 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.
255
-
256
- ---
257
-
258
- ## 💡 Use Cases
259
-
260
- pompelmi is trusted across diverse industries and use cases:
261
-
262
- ### 🏥 Healthcare (HIPAA Compliance)
263
-
264
- Scan patient document uploads without sending PHI to third-party services. Keep medical records and imaging files secure on your infrastructure.
265
-
266
- ### 🏦 Financial Services (PCI DSS)
267
-
268
- Validate customer document uploads (ID verification, tax forms) without exposing sensitive financial data to external APIs.
269
-
270
- ### 🎓 Education Platforms
271
-
272
- Protect learning management systems from malicious file uploads while maintaining student privacy.
273
-
274
- ### 🏢 Enterprise Document Management
275
-
276
- Scan files at ingestion time for corporate file sharing platforms, wikis, and collaboration tools.
277
-
278
- ### 🎨 Media & Creative Platforms
279
-
280
- Validate user-generated content uploads (images, videos, documents) before processing and storage.
281
-
282
- ---
283
-
284
- ## 🚀 Getting Started
285
-
286
- Get secure file scanning running in under 5 minutes with pompelmi's zero-config defaults.
287
-
288
- ### Step 1: Create Security Policy
289
-
290
- Create a reusable security policy and scanner configuration.
291
-
292
- > **`composeScanners` API** — two supported forms:
293
- > - **Named-scanner array** *(recommended)*: `composeScanners([["name", scanner], ...], opts?)` — supports `parallel`, `stopOn`, `timeoutMsPerScanner`, and `tagSourceName` options.
294
- > - **Variadic** *(backward-compatible)*: `composeScanners(scannerA, scannerB, ...)` — runs scanners sequentially, no options.
295
-
296
- ```ts
297
- // lib/security.ts
298
- import { CommonHeuristicsScanner, createZipBombGuard, composeScanners } from 'pompelmi';
299
- // Optional: import types for explicit annotation
300
- // import type { NamedScanner, ComposeScannerOptions } from 'pompelmi';
301
-
302
- export const policy = {
303
- includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt'],
304
- allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
305
- maxFileSizeBytes: 20 * 1024 * 1024, // 20MB
306
- timeoutMs: 5000,
307
- concurrency: 4,
308
- failClosed: true, // Block uploads on scanner errors
309
- onScanEvent: (event: unknown) => console.log('[scan]', event)
310
- };
311
-
312
- export const scanner = composeScanners(
313
- [
314
- ['zipGuard', createZipBombGuard({
315
- maxEntries: 512,
316
- maxTotalUncompressedBytes: 100 * 1024 * 1024,
317
- maxCompressionRatio: 12
318
- })],
319
- ['heuristics', CommonHeuristicsScanner],
320
- // Add your own scanners or YARA rules here
321
- ],
322
- {
323
- parallel: false,
324
- stopOn: 'suspicious',
325
- timeoutMsPerScanner: 1500,
326
- tagSourceName: true
327
- }
328
- );
133
+ ```bash
134
+ npm i @pompelmi/express-middleware # Express
135
+ npm i @pompelmi/next-upload # Next.js
136
+ npm i @pompelmi/koa-middleware # Koa
137
+ npm i @pompelmi/nestjs-integration # NestJS
138
+ npm i @pompelmi/fastify-plugin # Fastify (alpha)
139
+ npm i -g @pompelmi/cli # CLI / CI/CD
329
140
  ```
330
141
 
331
- ### Step 2: Choose Your Integration
332
-
333
- Pick the integration that matches your framework:
334
-
335
- #### Express
142
+ ### Express
336
143
 
337
144
  ```ts
338
145
  import express from 'express';
339
146
  import multer from 'multer';
340
147
  import { createUploadGuard } from '@pompelmi/express-middleware';
341
- import { policy, scanner } from './lib/security';
148
+ import { scanner, policy } from './lib/security';
342
149
 
343
150
  const app = express();
344
- const upload = multer({
345
- storage: multer.memoryStorage(),
346
- limits: { fileSize: policy.maxFileSizeBytes }
347
- });
348
-
349
- app.post('/upload',
350
- upload.any(),
351
- createUploadGuard({ ...policy, scanner }),
352
- (req, res) => {
353
- // File is safe - proceed with your logic
354
- res.json({
355
- success: true,
356
- verdict: (req as any).pompelmi?.verdict || 'clean'
357
- });
358
- }
151
+ app.post(
152
+ '/upload',
153
+ multer({ storage: multer.memoryStorage() }).any(),
154
+ createUploadGuard({ ...policy, scanner }),
155
+ (req, res) => res.json({ verdict: (req as any).pompelmi?.verdict })
359
156
  );
360
-
361
- app.listen(3000, () => console.log('🚀 Server running on http://localhost:3000'));
362
157
  ```
363
158
 
364
- #### Next.js App Router
159
+ ### Next.js App Router
365
160
 
366
161
  ```ts
367
162
  // app/api/upload/route.ts
368
163
  import { createNextUploadHandler } from '@pompelmi/next-upload';
369
- import { policy, scanner } from '@/lib/security';
164
+ import { scanner, policy } from '@/lib/security';
370
165
 
371
166
  export const runtime = 'nodejs';
372
- export const dynamic = 'force-dynamic';
373
-
374
167
  export const POST = createNextUploadHandler({ ...policy, scanner });
375
168
  ```
376
169
 
377
- #### Koa
378
-
379
- ```ts
380
- import Koa from 'koa';
381
- import Router from '@koa/router';
382
- import multer from '@koa/multer';
383
- import { createKoaUploadGuard } from '@pompelmi/koa-middleware';
384
- import { policy, scanner } from './lib/security';
385
-
386
- const app = new Koa();
387
- const router = new Router();
388
- const upload = multer({
389
- storage: multer.memoryStorage(),
390
- limits: { fileSize: policy.maxFileSizeBytes }
391
- });
392
-
393
- router.post('/upload',
394
- upload.any(),
395
- createKoaUploadGuard({ ...policy, scanner }),
396
- (ctx) => {
397
- ctx.body = {
398
- success: true,
399
- verdict: (ctx as any).pompelmi?.verdict || 'clean'
400
- };
401
- }
402
- );
403
-
404
- app.use(router.routes()).use(router.allowedMethods());
405
- app.listen(3003, () => console.log('🚀 Server running on http://localhost:3003'));
406
- ```
407
-
408
- #### Standalone / Programmatic
409
-
410
- ```ts
411
- import { scanFile } from 'pompelmi';
412
-
413
- const result = await scanFile('path/to/file.zip');
414
- console.log(result.verdict); // "clean" | "suspicious" | "malicious"
415
-
416
- if (result.verdict === 'malicious') {
417
- console.error('⚠️ Malicious file detected!');
418
- console.error(result.reasons);
419
- }
420
- ```
421
-
422
- ### Step 3: Test It
423
-
424
- Upload a test file to verify everything works:
425
-
426
- ```bash
427
- curl -X POST http://localhost:3000/upload \
428
- -F "file=@test.pdf"
429
- ```
430
-
431
- ✅ **Done!** Your app now has secure file upload scanning.
432
-
433
- ---
434
-
435
- ## 📘 Code Examples
436
-
437
- ### Example 1: Express with Custom Error Handling
438
-
439
- ```ts
440
- import express from 'express';
441
- import multer from 'multer';
442
- import { createUploadGuard } from '@pompelmi/express-middleware';
443
- import { policy, scanner } from './lib/security';
444
-
445
- const app = express();
446
- const upload = multer({ storage: multer.memoryStorage() });
447
-
448
- app.post('/upload',
449
- upload.single('file'),
450
- createUploadGuard({ ...policy, scanner }),
451
- (req, res) => {
452
- const scanResult = (req as any).pompelmi;
453
-
454
- if (scanResult?.verdict === 'malicious') {
455
- return res.status(422).json({
456
- error: 'Malicious file detected',
457
- reasons: scanResult.reasons
458
- });
459
- }
460
-
461
- if (scanResult?.verdict === 'suspicious') {
462
- // Log for review but allow upload
463
- console.warn('Suspicious file uploaded:', req.file?.originalname);
464
- }
465
-
466
- // Process clean file
467
- res.json({ success: true, fileName: req.file?.originalname });
468
- }
469
- );
470
-
471
- app.listen(3000);
472
- ```
473
-
474
- ### Example 2: Next.js Route Handler with Custom Response
475
-
476
- ```ts
477
- // app/api/scan/route.ts
478
- import { NextRequest, NextResponse } from 'next/server';
479
- import { scanBuffer } from 'pompelmi';
480
- import { scanner } from '@/lib/security';
481
-
482
- export async function POST(req: NextRequest) {
483
- const formData = await req.formData();
484
- const file = formData.get('file') as File;
485
-
486
- if (!file) {
487
- return NextResponse.json({ error: 'No file provided' }, { status: 400 });
488
- }
489
-
490
- const buffer = Buffer.from(await file.arrayBuffer());
491
- const result = await scanner.scan(buffer);
492
-
493
- return NextResponse.json({
494
- fileName: file.name,
495
- verdict: result.verdict,
496
- safe: result.verdict === 'clean',
497
- reasons: result.reasons || []
498
- });
499
- }
500
- ```
501
-
502
- ### Example 3: NestJS Controller
170
+ ### NestJS
503
171
 
504
172
  ```ts
505
173
  // app.module.ts
506
- import { Module } from '@nestjs/common';
507
174
  import { PompelmiModule } from '@pompelmi/nestjs-integration';
508
175
  import { CommonHeuristicsScanner } from 'pompelmi';
509
176
 
@@ -511,544 +178,348 @@ import { CommonHeuristicsScanner } from 'pompelmi';
511
178
  imports: [
512
179
  PompelmiModule.forRoot({
513
180
  includeExtensions: ['pdf', 'zip', 'png', 'jpg'],
514
- allowedMimeTypes: ['application/pdf', 'application/zip', 'image/png', 'image/jpeg'],
515
181
  maxFileSizeBytes: 10 * 1024 * 1024,
516
182
  scanners: [CommonHeuristicsScanner],
517
183
  }),
518
184
  ],
519
185
  })
520
186
  export class AppModule {}
521
-
522
- // upload.controller.ts
523
- import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
524
- import { FileInterceptor } from '@nestjs/platform-express';
525
- import { PompelmiInterceptor, PompelmiResult } from '@pompelmi/nestjs-integration';
526
-
527
- @Controller('upload')
528
- export class UploadController {
529
- @Post()
530
- @UseInterceptors(FileInterceptor('file'), PompelmiInterceptor)
531
- async uploadFile(@UploadedFile() file: Express.Multer.File & { pompelmi?: PompelmiResult }) {
532
- if (file.pompelmi?.verdict === 'malicious') {
533
- throw new BadRequestException('Malicious file detected');
534
- }
535
-
536
- return {
537
- success: true,
538
- verdict: file.pompelmi?.verdict,
539
- fileName: file.originalname
540
- };
541
- }
542
- }
543
187
  ```
544
188
 
545
- > 📖 **More examples:** Check the [examples/](./examples/) directory for complete working demos including Express, Koa, Next.js, Nuxt/Nitro, and more.
189
+ > 📖 **More examples:** Check the [examples/](./examples/) directory for complete working demos including Koa, Nuxt/Nitro, standalone, and more.
190
+
191
+ 👉 **[View all adapter docs →](https://pompelmi.github.io/pompelmi/)** &nbsp;&nbsp; **[Browse all examples →](./examples/)**
546
192
 
547
193
  ---
548
194
 
549
- ## 🤖 GitHub Action
195
+ ## 🧱 Composing scanners
550
196
 
551
- Run **pompelmi** in CI to scan repository files or built artifacts.
197
+ Build a layered scanner with heuristics, ZIP bomb protection, and optional YARA:
552
198
 
553
- **Minimal usage**
554
- ```yaml
555
- name: Security scan (pompelmi)
556
- on: [push, pull_request]
557
-
558
- jobs:
559
- scan:
560
- runs-on: ubuntu-latest
561
- steps:
562
- - uses: actions/checkout@v4
563
-
564
- - name: Scan repository with pompelmi
565
- uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
566
- with:
567
- path: .
568
- deep_zip: true
569
- fail_on_detect: true
570
- ```
199
+ ```ts
200
+ import { CommonHeuristicsScanner, createZipBombGuard, composeScanners } from 'pompelmi';
571
201
 
572
- **Scan a single artifact**
573
- ```yaml
574
- - uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
575
- with:
576
- artifact: build.zip
577
- deep_zip: true
578
- fail_on_detect: true
202
+ export const scanner = composeScanners(
203
+ [
204
+ ['zipGuard', createZipBombGuard({ maxEntries: 512, maxCompressionRatio: 12 })],
205
+ ['heuristics', CommonHeuristicsScanner],
206
+ // ['yara', YourYaraScanner],
207
+ ],
208
+ { parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
209
+ );
579
210
  ```
580
211
 
581
- **Inputs**
582
- | Input | Default | Description |
583
- | --- | --- | --- |
584
- | `path` | `.` | Directory to scan. |
585
- | `artifact` | `""` | Single file/archive to scan. |
586
- | `yara_rules` | `""` | Glob path to YARA rules (e.g. `rules/*.yar`). |
587
- | `deep_zip` | `true` | Enable deep nested-archive inspection. |
588
- | `max_depth` | `3` | Max nested-archive depth. |
589
- | `fail_on_detect` | `true` | Fail the job if detections occur. |
212
+ `composeScanners` supports two call forms:
213
+ - **Named array** *(recommended)*: `composeScanners([['name', scanner], ...], opts?)`
214
+ - **Variadic** *(backward-compatible)*: `composeScanners(scannerA, scannerB, ...)`
590
215
 
591
- > The Action lives in this repo at `.github/actions/pompelmi-scan`. When published to the Marketplace, consumers can copy the snippets above as-is.
216
+ ### Upload flow
217
+
218
+ ```mermaid
219
+ flowchart TD
220
+ A["Client uploads file(s)"] --> B["Web App Route"]
221
+ B --> C{"Pre-filters (ext, size, MIME)"}
222
+ C -- fail --> X["HTTP 4xx"]
223
+ C -- pass --> D{"Is ZIP?"}
224
+ D -- yes --> E["Iterate entries (limits & scan)"]
225
+ E --> F{"Verdict?"}
226
+ D -- no --> F{"Scan bytes"}
227
+ F -- malicious/suspicious --> Y["HTTP 422 blocked"]
228
+ F -- clean --> Z["HTTP 200 ok + results"]
229
+ ```
592
230
 
593
231
  ---
594
232
 
595
- ## 🧩 Adapters
233
+ ## ⚙️ Configuration
596
234
 
597
- Use the adapter that matches your web framework. All adapters share the same policy options and scanning contract.
235
+ All adapters accept the same options:
598
236
 
599
- <p align="center">
600
- <img src="https://img.shields.io/badge/Express-✓-000000?style=flat-square&logo=express" alt="Express">
601
- <img src="https://img.shields.io/badge/Koa-✓-33333D?style=flat-square&logo=koa" alt="Koa">
602
- <img src="https://img.shields.io/badge/Next.js-✓-000000?style=flat-square&logo=next.js" alt="Next.js">
603
- <img src="https://img.shields.io/badge/Nuxt-✓-00DC82?style=flat-square&logo=nuxt.js" alt="Nuxt">
604
- <img src="https://img.shields.io/badge/NestJS-✓-E0234E?style=flat-square&logo=nestjs" alt="NestJS">
605
- <img src="https://img.shields.io/badge/Fastify-alpha-000000?style=flat-square&logo=fastify" alt="Fastify">
606
- <img src="https://img.shields.io/badge/Remix-planned-000000?style=flat-square&logo=remix" alt="Remix">
607
- <img src="https://img.shields.io/badge/hapi-planned-F26D00?style=flat-square" alt="hapi">
608
- <img src="https://img.shields.io/badge/SvelteKit-planned-FF3E00?style=flat-square&logo=svelte" alt="SvelteKit">
609
- </p>
237
+ | Option | Type | Description |
238
+ |---|---|---|
239
+ | `scanner` | `{ scan(bytes: Uint8Array): Promise<Match[]> }` | Your scanning engine. Return `[]` for clean. |
240
+ | `includeExtensions` | `string[]` | Allowed file extensions (case-insensitive). |
241
+ | `allowedMimeTypes` | `string[]` | Allowed MIME types after magic-byte sniffing. |
242
+ | `maxFileSizeBytes` | `number` | Per-file size cap; oversized files are rejected early. |
243
+ | `timeoutMs` | `number` | Per-file scan timeout. |
244
+ | `concurrency` | `number` | Max files scanned in parallel. |
245
+ | `failClosed` | `boolean` | Block uploads on scanner errors or timeouts. |
246
+ | `onScanEvent` | `(event) => void` | Hook for logging and metrics. |
610
247
 
611
- ### Available Adapters
248
+ **Example images only, 5 MB max:**
612
249
 
613
- | Framework | Package | Status | Install |
614
- |-----------|---------|--------|---------|
615
- | **Express** | `@pompelmi/express-middleware` | ✅ Stable | `npm i @pompelmi/express-middleware` |
616
- | **Koa** | `@pompelmi/koa-middleware` | ✅ Stable | `npm i @pompelmi/koa-middleware` |
617
- | **Next.js** | `@pompelmi/next-upload` | ✅ Stable | `npm i @pompelmi/next-upload` |
618
- | **Nuxt/Nitro** | `pompelmi` (local) or remote API | ✅ Docs | [See guide](https://pompelmi.github.io/pompelmi/how-to/nuxt-nitro/) |
619
- | **NestJS** | `@pompelmi/nestjs-integration` | ✅ Stable | `npm i @pompelmi/nestjs-integration` |
620
- | **Fastify** | `@pompelmi/fastify-plugin` | 🔶 Alpha | `npm i @pompelmi/fastify-plugin` |
621
- | **Remix** | - | 🔜 Planned | Coming soon |
622
- | **SvelteKit** | - | 🔜 Planned | Coming soon |
623
- | **hapi** | - | 🔜 Planned | Coming soon |
250
+ ```ts
251
+ {
252
+ includeExtensions: ['png', 'jpg', 'jpeg', 'webp'],
253
+ allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
254
+ maxFileSizeBytes: 5 * 1024 * 1024,
255
+ failClosed: true,
256
+ }
257
+ ```
624
258
 
625
- ```bash
626
- # Express
627
- npm i @pompelmi/express-middleware
259
+ ---
628
260
 
629
- # Koa
630
- npm i @pompelmi/koa-middleware
261
+ ## 📦 Import entrypoints
631
262
 
632
- # Next.js
633
- npm i @pompelmi/next-upload
263
+ pompelmi ships multiple named entrypoints so you only bundle what you need:
634
264
 
635
- # NestJS
636
- npm i @pompelmi/nestjs-integration
265
+ | Entrypoint | Import | Environment | What it includes |
266
+ |---|---|---|---|
267
+ | **Default (Node.js)** | `import ... from 'pompelmi'` | Node.js | Full API — HIPAA, cache, threat-intel, ZIP streaming, YARA |
268
+ | **Browser-safe** | `import ... from 'pompelmi/browser'` | Browser / bundler | Core scan API, scanners, policy — no Node.js built-ins |
269
+ | **React** | `import ... from 'pompelmi/react'` | Browser / React | All browser-safe + `useFileScanner` hook (peer: react ≥18) |
270
+ | **Quarantine** | `import ... from 'pompelmi/quarantine'` | Node.js | Quarantine lifecycle — hold/review/promote/delete |
271
+ | **Hooks** | `import ... from 'pompelmi/hooks'` | Both | `onScanStart`, `onScanComplete`, `onThreatDetected`, `onQuarantine` |
272
+ | **Audit** | `import ... from 'pompelmi/audit'` | Node.js | Structured NDJSON audit trail for compliance/SIEM |
273
+ | **Policy packs** | `import ... from 'pompelmi/policy-packs'` | Both | Named pre-configured policies (`documents-only`, `images-only`, …) |
637
274
 
638
- # Fastify (alpha)
639
- npm i @pompelmi/fastify-plugin
275
+ ---
640
276
 
641
- # Standalone CLI
642
- npm i -g @pompelmi/cli
643
- ```
277
+ ## 🔒 Policy packs
644
278
 
645
- > **Note:** Core library works standalone. Install adapters only if using a specific framework.
279
+ Named, pre-configured policies for common upload scenarios:
646
280
 
647
- See the [📘 Code Examples](#-code-examples) section above for integration examples.
281
+ ```ts
282
+ import { POLICY_PACKS, getPolicyPack } from 'pompelmi/policy-packs';
648
283
 
649
- 👉 **[View adapter documentation →](https://pompelmi.github.io/pompelmi/)** | **[Browse all examples →](./examples/)**
284
+ // Use a built-in pack:
285
+ const policy = POLICY_PACKS['strict-public-upload'];
650
286
 
651
- ---
287
+ // Or retrieve by name:
288
+ const policy = getPolicyPack('documents-only');
289
+ ```
652
290
 
653
- ## 🗺️ Diagrams
291
+ | Pack | Extensions | Max size | Best for |
292
+ |---|---|---|---|
293
+ | `documents-only` | PDF, Word, Excel, PowerPoint, CSV, TXT, MD | 25 MB | Document portals, data import |
294
+ | `images-only` | JPEG, PNG, GIF, WebP, AVIF, TIFF | 10 MB | Avatars, product images (SVG excluded) |
295
+ | `strict-public-upload` | JPEG, PNG, WebP, PDF only | 5 MB | Anonymous/untrusted upload surfaces |
296
+ | `conservative-default` | ZIP, images, PDF, CSV, DOCX, XLSX | 10 MB | General hardened default |
297
+ | `archives` | ZIP, tar, gz, 7z, rar | 100 MB | Archive endpoints (pair with `createZipBombGuard`) |
654
298
 
655
- ### Upload scanning flow
656
- ```mermaid
657
- flowchart TD
658
- A["Client uploads file(s)"] --> B["Web App Route"]
659
- B --> C{"Pre-filters<br/>(ext, size, MIME)"}
660
- C -- fail --> X["HTTP 4xx"]
661
- C -- pass --> D{"Is ZIP?"}
662
- D -- yes --> E["Iterate entries<br/>(limits & scan)"]
663
- E --> F{"Verdict?"}
664
- D -- no --> F{"Scan bytes"}
665
- F -- malicious/suspicious --> Y["HTTP 422 blocked"]
666
- F -- clean --> Z["HTTP 200 ok + results"]
667
- ```
668
- <details>
669
- <summary>Mermaid source</summary>
299
+ All packs are built on `definePolicy` and are fully overridable.
670
300
 
671
- ```mermaid
672
- flowchart TD
673
- A["Client uploads file(s)"] --> B["Web App Route"]
674
- B --> C{"Pre-filters<br/>(ext, size, MIME)"}
675
- C -- fail --> X["HTTP 4xx"]
676
- C -- pass --> D{"Is ZIP?"}
677
- D -- yes --> E["Iterate entries<br/>(limits & scan)"]
678
- E --> F{"Verdict?"}
679
- D -- no --> F{"Scan bytes"}
680
- F -- malicious/suspicious --> Y["HTTP 422 blocked"]
681
- F -- clean --> Z["HTTP 200 ok + results"]
682
- ```
683
- </details>
301
+ ---
684
302
 
685
- ### Sequence (App ↔ pompelmi ↔ YARA)
686
- ```mermaid
687
- sequenceDiagram
688
- participant U as User
689
- participant A as App Route (/upload)
690
- participant P as pompelmi (adapter)
691
- participant Y as YARA engine
692
-
693
- U->>A: POST multipart/form-data
694
- A->>P: guard(files, policies)
695
- P->>P: MIME sniff + size + ext checks
696
- alt ZIP archive
697
- P->>P: unpack entries with limits
698
- end
699
- P->>Y: scan(bytes)
700
- Y-->>P: matches[]
701
- P-->>A: verdict (clean/suspicious/malicious)
702
- A-->>U: 200 or 4xx/422 with reason
703
- ```
704
- <details>
705
- <summary>Mermaid source</summary>
303
+ ## 🗄️ Quarantine workflow
706
304
 
707
- ```mermaid
708
- sequenceDiagram
709
- participant U as User
710
- participant A as App Route (/upload)
711
- participant P as pompelmi (adapter)
712
- participant Y as YARA engine
713
-
714
- U->>A: POST multipart/form-data
715
- A->>P: guard(files, policies)
716
- P->>P: MIME sniff + size + ext checks
717
- alt ZIP archive
718
- P->>P: unpack entries with limits
719
- end
720
- P->>Y: scan(bytes)
721
- Y-->>P: matches[]
722
- P-->>A: verdict (clean/suspicious/malicious)
723
- A-->>U: 200 or 4xx/422 with reason
724
- ```
725
- </details>
305
+ Hold suspicious files for manual review before accepting or permanently deleting them.
726
306
 
727
- ### Components (monorepo)
728
- ```mermaid
729
- flowchart LR
730
- subgraph Repo
731
- core["pompelmi (core)"]
732
- express["@pompelmi/express-middleware"]
733
- koa["@pompelmi/koa-middleware"]
734
- next["@pompelmi/next-upload"]
735
- fastify(("fastify-plugin · planned"))
736
- nest(("nestjs · planned"))
737
- remix(("remix · planned"))
738
- hapi(("hapi-plugin · planned"))
739
- svelte(("sveltekit · planned"))
740
- end
741
- core --> express
742
- core --> koa
743
- core --> next
744
- core -.-> fastify
745
- core -.-> nest
746
- core -.-> remix
747
- core -.-> hapi
748
- core -.-> svelte
749
- ```
750
- <details>
751
- <summary>Mermaid source</summary>
307
+ ```ts
308
+ import { scanBytes } from 'pompelmi';
309
+ import { QuarantineManager, FilesystemQuarantineStorage } from 'pompelmi/quarantine';
752
310
 
753
- ```mermaid
754
- flowchart LR
755
- subgraph Repo
756
- core["pompelmi (core)"]
757
- express["@pompelmi/express-middleware"]
758
- koa["@pompelmi/koa-middleware"]
759
- next["@pompelmi/next-upload"]
760
- fastify(("fastify-plugin · planned"))
761
- nest(("nestjs · planned"))
762
- remix(("remix · planned"))
763
- hapi(("hapi-plugin · planned"))
764
- svelte(("sveltekit · planned"))
765
- end
766
- core --> express
767
- core --> koa
768
- core --> next
769
- core -.-> fastify
770
- core -.-> nest
771
- core -.-> remix
772
- core -.-> hapi
773
- core -.-> svelte
774
- ```
775
- </details>
311
+ // One-time setup — store quarantined files locally.
312
+ const quarantine = new QuarantineManager({
313
+ storage: new FilesystemQuarantineStorage({ dir: './quarantine' }),
314
+ });
776
315
 
777
- ---
316
+ // In your upload handler:
317
+ const report = await scanBytes(fileBytes, { ctx: { filename: 'upload.pdf' } });
778
318
 
779
- ## ⚙️ Configuration
319
+ if (report.verdict !== 'clean') {
320
+ const entry = await quarantine.quarantine(fileBytes, report, {
321
+ originalName: 'upload.pdf',
322
+ sizeBytes: fileBytes.length,
323
+ uploadedBy: req.user?.id,
324
+ });
325
+ return res.status(202).json({ quarantineId: entry.id });
326
+ }
327
+ ```
780
328
 
781
- All adapters accept a common set of options:
329
+ **Review API:**
782
330
 
783
- | Option | Type (TS) | Purpose |
784
- | --- | --- | --- |
785
- | `scanner` | `{ scan(bytes: Uint8Array): Promise<Match[]> }` | Your scanning engine. Return `[]` when clean; non‑empty to flag. |
786
- | `includeExtensions` | `string[]` | Allow‑list of file extensions. Evaluated case‑insensitively. |
787
- | `allowedMimeTypes` | `string[]` | Allow‑list of MIME types after magic‑byte sniffing. |
788
- | `maxFileSizeBytes` | `number` | Per‑file size cap. Oversize files are rejected early. |
789
- | `timeoutMs` | `number` | Per‑file scan timeout; guards against stuck scanners. |
790
- | `concurrency` | `number` | How many files to scan in parallel. |
791
- | `failClosed` | `boolean` | If `true`, errors/timeouts block the upload. |
792
- | `onScanEvent` | `(event: unknown) => void` | Optional telemetry hook for logging/metrics. |
331
+ ```ts
332
+ // List pending entries:
333
+ const pending = await quarantine.listPending();
793
334
 
794
- **Common recipes**
335
+ // Approve (promote to storage):
336
+ await quarantine.resolve(entryId, { decision: 'promote', reviewedBy: 'ops-team' });
795
337
 
796
- Allow only images up to 5 MB:
338
+ // Delete permanently:
339
+ await quarantine.resolve(entryId, { decision: 'delete', reviewedBy: 'ops-team', reviewNote: 'Confirmed malware' });
797
340
 
798
- ```ts
799
- includeExtensions: ['png','jpg','jpeg','webp'],
800
- allowedMimeTypes: ['image/png','image/jpeg','image/webp'],
801
- maxFileSizeBytes: 5 * 1024 * 1024,
802
- failClosed: true,
341
+ // Generate an audit report:
342
+ const report = await quarantine.report({ status: 'pending' });
803
343
  ```
804
344
 
345
+ The `QuarantineStorage` interface is pluggable — implement it for S3, GCS, a database, or any other backend. `FilesystemQuarantineStorage` is the local reference implementation.
346
+
805
347
  ---
806
348
 
807
- ## Production checklist
349
+ ## 🪝 Scan hooks
808
350
 
809
- - [ ] **Limit file size** aggressively (`maxFileSizeBytes`).
810
- - [ ] **Restrict extensions & MIME** to what your app truly needs.
811
- - [ ] **Set `failClosed: true` in production** to block on timeouts/errors.
812
- - [ ] **Handle ZIPs carefully** (enable deep ZIP, keep nesting low, cap entry sizes).
813
- - [ ] **Compose scanners** with `composeScanners()` and enable `stopOn` to fail fast on early detections.
814
- - [ ] **Log scan events** (`onScanEvent`) and monitor for spikes.
815
- - [ ] **Run scans in a separate process/container** for defense‑in‑depth when possible.
816
- - [ ] **Sanitize file names and paths** if you persist uploads.
817
- - [ ] **Prefer memory storage + post‑processing**; avoid writing untrusted bytes before policy passes.
818
- - [ ] **Add CI scanning** with the GitHub Action to catch bad files in repos/artifacts.
351
+ Observe the scan lifecycle without modifying the pipeline:
352
+
353
+ ```ts
354
+ import { scanBytes } from 'pompelmi';
355
+ import { createScanHooks, withHooks } from 'pompelmi/hooks';
356
+
357
+ const hooks = createScanHooks({
358
+ onScanComplete(ctx, report) {
359
+ metrics.increment('scans.total');
360
+ metrics.histogram('scan.duration_ms', report.durationMs ?? 0);
361
+ },
362
+ onThreatDetected(ctx, report) {
363
+ alerting.notify({ file: ctx.filename, verdict: report.verdict });
364
+ },
365
+ onScanError(ctx, error) {
366
+ logger.error({ file: ctx.filename, error });
367
+ },
368
+ });
369
+
370
+ // Wrap your scan function once, then use it everywhere:
371
+ const scan = withHooks(scanBytes, hooks);
372
+ const report = await scan(fileBytes, { ctx: { filename: 'upload.zip' } });
373
+ ```
819
374
 
820
375
  ---
821
376
 
822
- ## 🧬 YARA Getting Started
377
+ ## 🔍 Audit trail
823
378
 
824
- YARA lets you detect suspicious or malicious content using pattern‑matching rules.
825
- **pompelmi** treats YARA matches as signals that you can map to your own verdicts
826
- (e.g., mark high‑confidence rules as `malicious`, heuristics as `suspicious`).
379
+ Write a structured NDJSON audit record for every scan and quarantine event:
827
380
 
828
- > **Status:** Optional. You can run without YARA. If you adopt it, keep your rules small, time‑bound, and tuned to your threat model.
381
+ ```ts
382
+ import { AuditTrail } from 'pompelmi/audit';
829
383
 
830
- ### Starter rules
384
+ const audit = new AuditTrail({
385
+ output: { dest: 'file', path: './audit.jsonl' },
386
+ });
831
387
 
832
- Below are three example rules you can adapt:
388
+ // After each scan:
389
+ audit.logScanComplete(report, { filename: 'upload.pdf', uploadedBy: req.user?.id });
833
390
 
834
- `rules/starter/eicar.yar`
835
- ```yar
836
- rule EICAR_Test_File
837
- {
838
- meta:
839
- description = "EICAR antivirus test string (safe)"
840
- reference = "https://www.eicar.org"
841
- confidence = "high"
842
- verdict = "malicious"
843
- strings:
844
- $eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
845
- condition:
846
- $eicar
847
- }
848
- ```
391
+ // After quarantine:
392
+ audit.logQuarantine(entry);
849
393
 
850
- `rules/starter/pdf_js.yar`
851
- ```yar
852
- rule PDF_JavaScript_Embedded
853
- {
854
- meta:
855
- description = "PDF contains embedded JavaScript (heuristic)"
856
- confidence = "medium"
857
- verdict = "suspicious"
858
- strings:
859
- $magic = { 25 50 44 46 } // "%PDF"
860
- $js1 = "/JavaScript" ascii
861
- $js2 = "/JS" ascii
862
- $open = "/OpenAction" ascii
863
- $aa = "/AA" ascii
864
- condition:
865
- uint32(0) == 0x25504446 and ( $js1 or $js2 ) and ( $open or $aa )
866
- }
394
+ // After resolution:
395
+ audit.logQuarantineResolved(entry);
867
396
  ```
868
397
 
869
- `rules/starter/office_macros.yar`
870
- ```yar
871
- rule Office_Macro_Suspicious_Words
872
- {
873
- meta:
874
- description = "Heuristic: suspicious VBA macro keywords"
875
- confidence = "medium"
876
- verdict = "suspicious"
877
- strings:
878
- $s1 = /Auto(Open|Close)/ nocase
879
- $s2 = "Document_Open" nocase ascii
880
- $s3 = "CreateObject(" nocase ascii
881
- $s4 = "WScript.Shell" nocase ascii
882
- $s5 = "Shell(" nocase ascii
883
- $s6 = "Sub Workbook_Open()" nocase ascii
884
- condition:
885
- 2 of ($s*)
886
- }
887
- ```
398
+ Each record is a single JSON line with `timestamp`, `event`, `verdict`, `matchCount`, `durationMs`, `sha256`, and more — ready for your SIEM or compliance tools.
399
+
400
+ ---
888
401
 
889
- > These are **examples**. Expect some false positives; tune to your app.
402
+ ## Production checklist
403
+ - [ ] Set `maxFileSizeBytes` — reject oversized files before scanning.
404
+ - [ ] Restrict `includeExtensions` and `allowedMimeTypes` to what your app truly needs (or use a [policy pack](#-policy-packs)).
405
+ - [ ] Set `failClosed: true` to block uploads on timeouts or scanner errors.
406
+ - [ ] Enable deep ZIP inspection; keep nesting depth low.
407
+ - [ ] Use `composeScanners` with `stopOn` to fail fast on early detections.
408
+ - [ ] Log scan events with [scan hooks](#-scan-hooks) and monitor for anomaly spikes.
409
+ - [ ] Wire up the [quarantine workflow](#-quarantine-workflow) for suspicious files rather than silently dropping them.
410
+ - [ ] Write an [audit trail](#-audit-trail) for compliance and incident response.
411
+ - [ ] Consider running scans in a separate process or container for defense-in-depth.
412
+ - [ ] Sanitize file names and paths before persisting uploads.
413
+ - [ ] Keep files in memory until policy passes — avoid writing untrusted bytes to disk first.
890
414
 
891
- ### Minimal integration (adapter contract)
415
+ ---
892
416
 
893
- If you use a YARA binding (e.g., `@automattic/yara`), wrap it behind the `scanner` contract:
417
+ ## 🧬 YARA
894
418
 
895
- ```ts
896
- // Example YARA scanner adapter (pseudo‑code)
897
- import * as Y from '@automattic/yara';
419
+ YARA lets you write custom pattern-matching rules and use them as a scanner engine. pompelmi treats YARA matches as signals you map to verdicts (`suspicious`, `malicious`).
420
+
421
+ > **Optional.** pompelmi works without YARA. Add it when you need custom detection rules.
898
422
 
899
- // Compile your rules from disk at boot (recommended)
900
- // const sources = await fs.readFile('rules/starter/*.yar', 'utf8');
901
- // const compiled = await Y.compile(sources);
423
+ ### Minimal adapter
902
424
 
903
- export const YourYaraScanner = {
425
+ ```ts
426
+ export const MyYaraScanner = {
904
427
  async scan(bytes: Uint8Array) {
905
- // const matches = await compiled.scan(bytes, { timeout: 1500 });
906
- const matches = []; // plug your engine here
907
- // Map to the structure your app expects; return [] when clean.
908
- return matches.map((m: any) => ({
909
- rule: m.rule,
910
- meta: m.meta ?? {},
911
- tags: m.tags ?? [],
912
- }));
428
+ const matches = await compiledRules.scan(bytes, { timeout: 1500 });
429
+ return matches.map(m => ({ rule: m.rule, meta: m.meta ?? {}, tags: m.tags ?? [] }));
913
430
  }
914
431
  };
915
432
  ```
916
433
 
917
- Then include it in your composed scanner:
434
+ Plug it into your composed scanner:
918
435
 
919
436
  ```ts
920
437
  import { composeScanners, CommonHeuristicsScanner } from 'pompelmi';
921
- // import { YourYaraScanner } from './yara-scanner';
922
438
 
923
439
  export const scanner = composeScanners(
924
440
  [
925
441
  ['heuristics', CommonHeuristicsScanner],
926
- // ['yara', YourYaraScanner],
442
+ ['yara', MyYaraScanner],
927
443
  ],
928
444
  { parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
929
445
  );
930
446
  ```
931
447
 
932
- ### Policy suggestion (mapping matches verdict)
448
+ Starter rules for common threats (EICAR, PDF-embedded JS, Office macros) are in [`rules/starter/`](./rules/).
933
449
 
934
- - **malicious**: high‑confidence rules (e.g., `EICAR_Test_File`)
935
- - **suspicious**: heuristic rules (e.g., PDF JavaScript, macro keywords)
936
- - **clean**: no matches
450
+ **Suggested verdict mapping:**
451
+ - `malicious` high-confidence rules (e.g., `EICAR_Test_File`)
452
+ - `suspicious` heuristic rules (e.g., PDF JavaScript, macro keywords)
453
+ - `clean` — no matches
937
454
 
938
- Combine YARA with MIME sniffing, ZIP safety limits, and strict size/time caps.
455
+ ### Quick smoke test
939
456
 
940
- ## 🧪 Quick test (no EICAR)
457
+ ```bash
458
+ # Create a minimal PDF with risky embedded actions
459
+ printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%%%EOF\n' > risky.pdf
941
460
 
942
- Use the examples above, then send a **minimal PDF** that contains risky tokens (this triggers the built‑in heuristics).
461
+ # Send it to your endpoint expect HTTP 422
462
+ curl -F "file=@risky.pdf;type=application/pdf" http://localhost:3000/upload -i
463
+ ```
943
464
 
944
- **1) Create a tiny PDF with risky actions**
465
+ 👉 **[Full YARA guide in docs →](https://pompelmi.github.io/pompelmi/)**
945
466
 
946
- Linux:
947
- ```bash
948
- printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%EOF\n' > risky.pdf
949
- ```
467
+ ---
950
468
 
951
- macOS:
952
- ```bash
953
- printf '%%PDF-1.7\n1 0 obj\n<< /OpenAction 1 0 R /AA << /JavaScript (alert(1)) >> >>\nendobj\n%%EOF\n' > risky.pdf
954
- ```
469
+ ## 🤖 GitHub Action
955
470
 
956
- **2) Send it to your endpoint**
471
+ Scan files or build artifacts in CI with a single step:
957
472
 
958
- Express (default from the Quick‑start):
959
- ```bash
960
- curl -F "file=@risky.pdf;type=application/pdf" http://localhost:3000/upload -i
473
+ ```yaml
474
+ - uses: pompelmi/pompelmi/.github/actions/pompelmi-scan@v1
475
+ with:
476
+ path: .
477
+ deep_zip: true
478
+ fail_on_detect: true
961
479
  ```
962
480
 
963
- 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.
481
+ | Input | Default | Description |
482
+ |---|---|---|
483
+ | `path` | `.` | Directory to scan. |
484
+ | `artifact` | `""` | Single file or archive to scan. |
485
+ | `yara_rules` | `""` | Glob path to `.yar` rule files. |
486
+ | `deep_zip` | `true` | Traverse nested archives. |
487
+ | `max_depth` | `3` | Max nesting depth. |
488
+ | `fail_on_detect` | `true` | Fail the job on any detection. |
964
489
 
965
490
  ---
966
491
 
967
- ## 🔒 Security notes
492
+ ## 💡 Use cases
968
493
 
969
- - The library **reads** bytes; it never executes files.
970
- - YARA detections depend on the **rules you provide**; expect some false positives/negatives.
971
- - ZIP scanning applies limits (entries, per‑entry size, total uncompressed, nesting) to reduce archive‑bomb risk.
972
- - Prefer running scans in a **dedicated process/container** for defense‑in‑depth.
494
+ - **Document upload portals** verify PDFs, DOCX files, and archives before storage.
495
+ - **User-generated content platforms** block malicious images, scripts, or embedded payloads.
496
+ - **Internal tooling and wikis** protect collaboration tools from lateral-movement attacks.
497
+ - **Privacy-sensitive environments** healthcare, legal, and finance platforms where files must stay on-prem.
498
+ - **CI/CD pipelines** — catch malicious artifacts before they enter your build or release chain.
973
499
 
974
500
  ---
975
501
 
976
- ## Releases & Security
502
+ ## 🔒 Security
977
503
 
978
- - **Changelog / releases:** see [GitHub Releases](https://github.com/pompelmi/pompelmi/releases).
979
- - **Security disclosures:** please use [GitHub Security Advisories](https://github.com/pompelmi/pompelmi/security/advisories). We’ll coordinate a fix before public disclosure.
980
- - **Production users:** open a [Discussion](https://github.com/pompelmi/pompelmi/discussions) to share requirements or request adapters.
504
+ - pompelmi **reads** bytes it never executes uploaded files.
505
+ - ZIP scanning enforces entry count, per-entry size, total uncompressed size, and nesting depth limits to guard against archive bombs.
506
+ - YARA detection quality depends on the rules you provide; tune them to your threat model.
507
+ - For defense-in-depth, consider running scans in a separate process or container.
508
+ - **Changelog / releases:** [GitHub Releases](https://github.com/pompelmi/pompelmi/releases).
509
+ - **Vulnerability disclosure:** [GitHub Security Advisories](https://github.com/pompelmi/pompelmi/security/advisories). We coordinate a fix before public disclosure.
981
510
 
982
511
  ---
983
512
 
984
- ## 🏆 Community & Recognition
985
-
986
- pompelmi has been featured in **leading security and developer publications** and is trusted by teams worldwide for secure file upload handling.
987
-
988
- ### 🌟 Featured In High-Authority Publications
513
+ ## 🏆 Recognition
989
514
 
990
- <table>
991
- <tr>
992
- <td align="center" width="200">
993
- <a href="https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/">
994
- <img src="https://img.shields.io/badge/🔒-HelpNet%20Security-FF6B35?style=for-the-badge" alt="HelpNet Security"/>
995
- </a>
996
- <br/>
997
- <strong>HelpNet Security</strong>
998
- <br/>
999
- <em>Leading Cybersecurity News</em>
1000
- </td>
1001
- <td align="center" width="200">
1002
- <a href="https://snyk.io/test/github/pompelmi/pompelmi">
1003
- <img src="https://img.shields.io/badge/🛡️-Snyk-4C4A73?style=for-the-badge&logo=snyk" alt="Snyk"/>
1004
- </a>
1005
- <br/>
1006
- <strong>Snyk</strong>
1007
- <br/>
1008
- <em>Security Verified</em>
1009
- </td>
1010
- <td align="center" width="200">
1011
- <a href="https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon">
1012
- <img src="https://img.shields.io/badge/📡-Detection%20Engineering-0A84FF?style=for-the-badge&logo=substack" alt="Detection Engineering"/>
1013
- </a>
1014
- <br/>
1015
- <strong>Detection Engineering Weekly</strong>
1016
- <br/>
1017
- <em>Issue #124</em>
1018
- </td>
1019
- </tr>
1020
- <tr>
1021
- <td align="center" width="200">
1022
- <a href="https://nodeweekly.com/issues/594">
1023
- <img src="https://img.shields.io/badge/⚡-Node%20Weekly-FF6600?style=for-the-badge&logo=node.js" alt="Node Weekly"/>
1024
- </a>
1025
- <br/>
1026
- <strong>Node Weekly</strong>
1027
- <br/>
1028
- <em>Issue #594</em>
1029
- </td>
1030
- <td align="center" width="200">
1031
- <a href="https://bytes.dev/archives/429">
1032
- <img src="https://img.shields.io/badge/📬-Bytes-111111?style=for-the-badge" alt="Bytes"/>
1033
- </a>
1034
- <br/>
1035
- <strong>Bytes Newsletter</strong>
1036
- <br/>
1037
- <em>Issue #429</em>
1038
- </td>
1039
- <td align="center" width="200">
1040
- <a href="https://app.daily.dev/posts/pompelmi">
1041
- <img src="https://img.shields.io/badge/📰-daily.dev-CE3DF3?style=for-the-badge&logo=dailydotdev" alt="daily.dev"/>
1042
- </a>
1043
- <br/>
1044
- <strong>daily.dev</strong>
1045
- <br/>
1046
- <em>Featured Article</em>
1047
- </td>
1048
- </tr>
1049
- </table>
515
+ Featured in:
1050
516
 
1051
- ### 🎖️ Mentioned In Awesome Lists
517
+ - [HelpNet Security](https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/)
518
+ - [Stack Overflow Blog](https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/)
519
+ - [Node Weekly #594](https://nodeweekly.com/issues/594)
520
+ - [Bytes Newsletter #429](https://bytes.dev/archives/429)
521
+ - [Detection Engineering Weekly #124](https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon)
522
+ - [daily.dev](https://app.daily.dev/posts/pompelmi)
1052
523
 
1053
524
  <p align="center">
1054
525
  <a href="https://github.com/sorrycc/awesome-javascript"><img src="https://awesome.re/mentioned-badge.svg" alt="Awesome JavaScript"/></a>
@@ -1060,154 +531,81 @@ pompelmi has been featured in **leading security and developer publications** an
1060
531
  <!-- MENTIONS:START -->
1061
532
  <!-- MENTIONS:END -->
1062
533
 
1063
- ### 💬 What Developers Say
1064
-
1065
- > "pompelmi made it incredibly easy to add malware scanning to our Express API. The TypeScript support is fantastic!"
1066
- > — Developer using pompelmi in production
1067
-
1068
- > "Finally, a file scanning solution that doesn't require sending our users' data to third parties. Perfect for GDPR compliance."
1069
- > — Security Engineer at a healthcare startup
1070
-
1071
- > "The YARA integration is seamless. We went from prototype to production in less than a week."
1072
- > — DevSecOps Engineer
1073
-
1074
- _Want to share your experience? [Open a discussion](https://github.com/pompelmi/pompelmi/discussions)!_
1075
-
1076
- ### 🤝 Community & Support
1077
-
1078
- **Need help? We're here for you!**
1079
-
1080
- - 📖 **[Documentation](https://pompelmi.github.io/pompelmi/)** — Complete API reference, guides, and tutorials
1081
- - 💬 **[GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)** — Ask questions, share ideas, get community support
1082
- - 🐛 **[Issue Tracker](https://github.com/pompelmi/pompelmi/issues)** — Report bugs, request features
1083
- - 🔒 **[Security Policy](https://github.com/pompelmi/pompelmi/security)** — Report security vulnerabilities privately
1084
- - 💼 **Commercial Support** — For enterprise support and consulting, contact the maintainers
1085
- - 💖 **[Sponsor pompelmi](https://github.com/sponsors/pompelmi)** — Support ongoing development via GitHub Sponsors
1086
-
1087
- **Supported Frameworks:**
1088
- - ✅ Express
1089
- - ✅ Koa
1090
- - ✅ Next.js (App & Pages Router)
1091
- - ✅ NestJS
1092
- - ✅ Fastify (alpha)
1093
- - 🔜 Remix (planned)
1094
- - 🔜 SvelteKit (planned)
1095
- - 🔜 hapi (planned)
1096
-
1097
534
  ---
1098
535
 
1099
- ## 🎖️ Contributors
1100
-
1101
- Thanks to all the amazing contributors who have helped make pompelmi better!
1102
-
1103
- <p align="center">
1104
- <a href="https://github.com/pompelmi/pompelmi/graphs/contributors">
1105
- <img src="https://contrib.rocks/image?repo=pompelmi/pompelmi" alt="Contributors" />
1106
- </a>
1107
- </p>
536
+ ## 💬 FAQ
1108
537
 
1109
- <p align="center">
1110
- <em>Want to contribute? Check out our <a href="./CONTRIBUTING.md">Contributing Guide</a>!</em>
1111
- </p>
538
+ **Does pompelmi send files to third parties?**
539
+ No. All scanning runs in-process inside your Node.js application. No bytes leave your infrastructure.
1112
540
 
1113
- ---
541
+ **Does it require a daemon or external service?**
542
+ No. Install it like any npm package — no daemon, no sidecar, no config files to write.
1114
543
 
1115
- ## 💖 Sponsors
544
+ **Can I use YARA rules?**
545
+ Yes. Wrap your YARA engine behind the `{ scan(bytes) }` interface and pass it to `composeScanners`. Starter rules are in [`rules/starter/`](./rules/).
1116
546
 
1117
- Pompelmi is free and open-source. If it saves you time or helps protect your users, consider supporting its development!
547
+ **Does it work with my framework?**
548
+ Stable adapters exist for Express, Koa, Next.js, and NestJS. A Fastify plugin is in alpha. The core library works standalone with any Node.js server.
1118
549
 
1119
- <p align="center">
1120
- <a href="https://github.com/sponsors/pompelmi">
1121
- <img src="https://img.shields.io/badge/Sponsor-pompelmi-EA4AAA?style=for-the-badge&logo=githubsponsors&logoColor=white" alt="Sponsor pompelmi on GitHub" />
1122
- </a>
1123
- </p>
550
+ **Why 422 for blocked files?**
551
+ It's a common convention that keeps policy violations distinct from transport errors. Use whatever HTTP status code fits your API contract.
1124
552
 
1125
- Your sponsorship helps fund:
1126
- - 🧬 New detection engine integrations
1127
- - 🧪 Expanded test coverage and CI infrastructure
1128
- - 📚 Documentation and examples
1129
- - 🔒 Security audits and CVE response
553
+ **Are ZIP bombs handled?**
554
+ Yes. Archive scanning enforces limits on entry count, per-entry size, total uncompressed size, and nesting depth. Use `failClosed: true` in production.
1130
555
 
1131
- Thank you to all current and future sponsors for keeping this project alive!
556
+ **Is commercial support available?**
557
+ Yes. Limited async support for integration help, configuration review, and troubleshooting is available from the maintainer. Email [pompelmideveloper@yahoo.com](mailto:pompelmideveloper@yahoo.com).
1132
558
 
1133
559
  ---
1134
560
 
1135
- ## Star History
561
+ ## 💼 Commercial support
1136
562
 
1137
- <p align="center">
1138
- <a href="https://star-history.com/#pompelmi/pompelmi&Date">
1139
- <img src="https://api.star-history.com/svg?repos=pompelmi/pompelmi&type=Date" alt="Star History Chart" width="600" />
1140
- </a>
1141
- </p>
1142
-
1143
- ---
1144
-
1145
- ## 💬 FAQ
563
+ Limited commercial support is available on a **private, asynchronous, best-effort basis** from the maintainer. This may include:
1146
564
 
1147
- **Do I need YARA?**
1148
- 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.
565
+ - Integration assistance
566
+ - Configuration and policy review
567
+ - Prioritized troubleshooting
568
+ - Upload security guidance
1149
569
 
1150
- **Where do the results live?**
1151
- 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.
570
+ Support is in writing only — no live calls or real-time support.
1152
571
 
1153
- **Why 422 for blocked files?**
1154
- 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.
572
+ **To inquire**, email [pompelmideveloper@yahoo.com](mailto:pompelmideveloper@yahoo.com) with your framework, Node.js version, pompelmi version, and a short description of your goal or issue.
1155
573
 
1156
- **Are ZIP bombs handled?**
1157
- Archives are traversed with limits to reduce archive‑bomb risk. Keep your size limits conservative and prefer `failClosed: true` in production.
574
+ > Community support (GitHub Issues and Discussions) remains free and open. For vulnerability disclosure, see [SECURITY.md](./SECURITY.md).
1158
575
 
1159
576
  ---
1160
577
 
1161
- ## 🧪 Tests & Coverage
1162
-
1163
- Run tests locally with coverage:
1164
-
1165
- ```bash
1166
- pnpm vitest run --coverage --passWithNoTests
1167
- ```
1168
-
1169
- 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.
1170
-
1171
- If you integrate Codecov in CI, upload `coverage/lcov.info` and you can use this Codecov badge:
1172
-
1173
- ```md
1174
- [![codecov](https://codecov.io/gh/pompelmi/pompelmi/branch/main/graph/badge.svg?flag=core)](https://codecov.io/gh/pompelmi/pompelmi)
1175
- ```
1176
-
1177
578
  ## 🤝 Contributing
1178
579
 
1179
- PRs and issues welcome! Start with:
580
+ PRs and issues are welcome.
1180
581
 
1181
582
  ```bash
1182
583
  pnpm -r build
1183
584
  pnpm -r lint
585
+ pnpm vitest run --coverage --passWithNoTests
1184
586
  ```
1185
587
 
1186
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines.
1187
-
1188
- ---
1189
-
1190
- ## 🎓 Learning Resources
1191
-
1192
- ### 📚 Documentation
1193
-
1194
- - [Official Docs](https://pompelmi.github.io/pompelmi/) — Complete API reference and guides
1195
- - [Examples](./examples/) — Real-world integration examples
1196
- - [Security Guide](./SECURITY.md) — Security best practices and disclosure policy
588
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for full guidelines.
1197
589
 
1198
- ### 🛠️ Tools & Integrations
590
+ <p align="center">
591
+ <a href="https://github.com/pompelmi/pompelmi/graphs/contributors">
592
+ <img src="https://contrib.rocks/image?repo=pompelmi/pompelmi" alt="Contributors" />
593
+ </a>
594
+ </p>
1199
595
 
1200
- - [GitHub Action](https://github.com/pompelmi/pompelmi/tree/main/.github/actions/pompelmi-scan) — CI/CD scanning
596
+ <p align="center">
597
+ <a href="https://github.com/sponsors/pompelmi">
598
+ <img src="https://img.shields.io/badge/Sponsor-pompelmi-EA4AAA?style=for-the-badge&logo=githubsponsors&logoColor=white" alt="Sponsor pompelmi" />
599
+ </a>
600
+ </p>
1201
601
 
1202
602
  ---
1203
603
 
1204
- ## 🙏 Acknowledgments
604
+ ## 🌍 Translations
1205
605
 
1206
- pompelmi stands on the shoulders of giants. Special thanks to:
606
+ [🇮🇹 Italian](docs/i18n/README.it.md) [🇫🇷 French](docs/i18n/README.fr.md) [🇪🇸 Spanish](docs/i18n/README.es.md) [🇩🇪 German](docs/i18n/README.de.md) • [🇯🇵 Japanese](docs/i18n/README.ja.md) • [🇨🇳 Chinese](docs/i18n/README.zh-CN.md) • [🇰🇷 Korean](docs/i18n/README.ko.md) • [🇧🇷 Portuguese](docs/i18n/README.pt-BR.md) • [🇷🇺 Russian](docs/i18n/README.ru.md) • [🇹🇷 Turkish](docs/i18n/README.tr.md)
1207
607
 
1208
- - The YARA project for powerful pattern matching
1209
- - The Node.js community for excellent tooling
1210
- - All our contributors and users
608
+ The English README is the authoritative source. Contributions to translations are welcome via PR.
1211
609
 
1212
610
  ---
1213
611
 
@@ -1215,4 +613,4 @@ pompelmi stands on the shoulders of giants. Special thanks to:
1215
613
 
1216
614
  ## 📜 License
1217
615
 
1218
- [MIT](./LICENSE) © 2025present pompelmi contributors
616
+ [MIT](./LICENSE) © 2025present pompelmi contributors