pompelmi 0.33.0 โ†’ 0.34.1

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