pompelmi 0.34.1 → 0.34.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -607
- package/package.json +17 -35
package/README.md
CHANGED
|
@@ -1,677 +1,193 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
3
|
<picture>
|
|
4
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/
|
|
5
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/
|
|
6
|
-
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/logo.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/logo.svg">
|
|
6
|
+
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/logo.svg" alt="Pompelmi logo" width="220" />
|
|
7
7
|
</picture>
|
|
8
8
|
|
|
9
|
-
<h1>
|
|
9
|
+
<h1>Pompelmi</h1>
|
|
10
10
|
|
|
11
|
-
<p><strong>
|
|
11
|
+
<p><strong>Stop malicious uploads before they hit storage.</strong></p>
|
|
12
|
+
|
|
13
|
+
<p>Secure file upload scanning for Node.js with MIME sniffing, ZIP bomb protection, polyglot detection, risky document checks, and optional YARA.</p>
|
|
12
14
|
|
|
13
15
|
<p>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Drop-in framework adapters
|
|
16
|
+
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="Star on GitHub" src="https://img.shields.io/badge/Star%20on%20GitHub-181717?style=for-the-badge&logo=github&logoColor=white"></a>
|
|
17
|
+
<a href="#quick-start"><img alt="Start in 60 seconds" src="https://img.shields.io/badge/Start%20in-60%20seconds-059669?style=for-the-badge"></a>
|
|
18
|
+
<a href="https://pompelmi.github.io/pompelmi/"><img alt="Read the docs" src="https://img.shields.io/badge/Read%20the-Docs-2563eb?style=for-the-badge"></a>
|
|
18
19
|
</p>
|
|
19
20
|
|
|
21
|
+
<p><strong>Private by design</strong> • <strong>No cloud API</strong> • <strong>No daemon</strong> • <strong>Express / Next.js / Koa / NestJS / Fastify</strong></p>
|
|
22
|
+
|
|
20
23
|
<p>
|
|
21
|
-
<a href="https://
|
|
22
|
-
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads" src="https://img.shields.io/npm/dm/pompelmi?
|
|
23
|
-
<a href="https://
|
|
24
|
-
<img alt="
|
|
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>
|
|
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>
|
|
27
|
-
<img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white">
|
|
28
|
-
<img alt="ESM" src="https://img.shields.io/badge/ESM%2FCJS-compatible-yellow">
|
|
29
|
-
<a href="https://snyk.io/test/github/pompelmi/pompelmi"><img alt="Snyk" src="https://snyk.io/test/github/pompelmi/pompelmi/badge.svg"></a>
|
|
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>
|
|
24
|
+
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=for-the-badge&logo=github&label=stars"></a>
|
|
25
|
+
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads per month" src="https://img.shields.io/npm/dm/pompelmi?style=for-the-badge&logo=npm&label=downloads%2Fmonth"></a>
|
|
26
|
+
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi?style=for-the-badge&logo=npm&label=npm"></a>
|
|
27
|
+
<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?style=for-the-badge&branch=main&label=CI"></a>
|
|
31
28
|
</p>
|
|
32
29
|
|
|
33
30
|
<p>
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
<a href="#-yara"><strong>🧬 YARA</strong></a> •
|
|
39
|
-
<a href="#-github-action"><strong>🤖 CI/CD</strong></a> •
|
|
40
|
-
<a href="./examples/"><strong>💡 Examples</strong></a>
|
|
31
|
+
<img alt="Node 18+" src="https://img.shields.io/badge/node-%3E%3D18-339933?style=for-the-badge&logo=node.js&logoColor=white">
|
|
32
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/types-TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white">
|
|
33
|
+
<img alt="MIT license" src="https://img.shields.io/badge/license-MIT-0f172a?style=for-the-badge">
|
|
34
|
+
<img alt="Zero cloud dependency" src="https://img.shields.io/badge/cloud-none-059669?style=for-the-badge">
|
|
41
35
|
</p>
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 |
|
|
37
|
+
<p>
|
|
38
|
+
<strong>
|
|
39
|
+
<a href="https://pompelmi.github.io/pompelmi/">Docs</a> •
|
|
40
|
+
<a href="#quick-start">Quick start</a> •
|
|
41
|
+
<a href="./examples">Examples</a> •
|
|
42
|
+
<a href="#frameworks">Frameworks</a> •
|
|
43
|
+
<a href="#star-history">Star history</a>
|
|
44
|
+
</strong>
|
|
45
|
+
</p>
|
|
62
46
|
|
|
63
|
-
|
|
47
|
+
<p>If Pompelmi saves you from building upload security plumbing from scratch, give it a star. It helps the project grow and helps more Node.js teams find it.</p>
|
|
64
48
|
|
|
65
|
-
|
|
49
|
+
</div>
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
<p align="center">
|
|
52
|
+
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/readme-banner.png" alt="Pompelmi banner" width="100%">
|
|
53
|
+
</p>
|
|
70
54
|
|
|
71
|
-
|
|
55
|
+
## Featured In
|
|
72
56
|
|
|
73
|
-
|
|
57
|
+
Picked up by developer newsletters, security media, and curated awesome lists.
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
<p align="center">
|
|
60
|
+
<a href="https://www.producthunt.com/products/pompelmi"><img alt="Featured on Product Hunt" src="https://img.shields.io/badge/featured-Product%20Hunt-da552f?style=for-the-badge&logo=producthunt&logoColor=white"></a>
|
|
61
|
+
<a href="https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/"><img alt="Featured on Help Net Security" src="https://img.shields.io/badge/featured-Help%20Net%20Security-ff6b35?style=for-the-badge"></a>
|
|
62
|
+
<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>
|
|
63
|
+
<a href="https://nodeweekly.com/issues/594"><img alt="Featured in Node Weekly issue 594" src="https://img.shields.io/badge/featured-Node%20Weekly%20%23594-43853d?style=for-the-badge&logo=node.js&logoColor=white"></a>
|
|
64
|
+
<a href="https://bytes.dev/archives/429"><img alt="Featured in Bytes issue 429" src="https://img.shields.io/badge/featured-Bytes%20%23429-111111?style=for-the-badge"></a>
|
|
65
|
+
<a href="https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon"><img alt="Featured in Detection Engineering Weekly issue 124" src="https://img.shields.io/badge/featured-Detection%20Engineering%20Weekly-2563eb?style=for-the-badge&logo=substack&logoColor=white"></a>
|
|
66
|
+
</p>
|
|
76
67
|
|
|
77
|
-
|
|
68
|
+
<p align="center">
|
|
69
|
+
<a href="https://github.com/sorrycc/awesome-javascript"><img alt="Included in Awesome JavaScript" src="https://img.shields.io/badge/awesome-Awesome%20JavaScript-f7df1e?style=for-the-badge&logo=javascript&logoColor=black"></a>
|
|
70
|
+
<a href="https://github.com/dzharii/awesome-typescript"><img alt="Included in Awesome TypeScript" src="https://img.shields.io/badge/awesome-Awesome%20TypeScript-3178c6?style=for-the-badge&logo=typescript&logoColor=white"></a>
|
|
71
|
+
</p>
|
|
78
72
|
|
|
79
|
-
|
|
80
|
-
import { scanFile } from 'pompelmi';
|
|
73
|
+
## Why It Exists
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
// result.verdict → "clean" | "suspicious" | "malicious"
|
|
75
|
+
Your file upload endpoint is part of your attack surface.
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
throw new Error(`Blocked: ${result.verdict} — ${result.reasons}`);
|
|
87
|
-
}
|
|
88
|
-
```
|
|
77
|
+
Most upload handlers still trust filenames, extensions, or client-provided MIME types. That leaves room for renamed files, archive bombs, polyglots, active PDFs, and other risky payloads to land in storage before your app notices.
|
|
89
78
|
|
|
90
|
-
|
|
79
|
+
Without Pompelmi:
|
|
91
80
|
|
|
92
|
-
|
|
81
|
+
`upload -> trust filename/MIME -> store -> parse or serve later`
|
|
93
82
|
|
|
94
|
-
|
|
83
|
+
With Pompelmi:
|
|
95
84
|
|
|
96
|
-
|
|
85
|
+
`upload -> inspect bytes + structure -> allow | quarantine | reject -> store/process`
|
|
97
86
|
|
|
98
|
-
|
|
87
|
+
Why teams choose it:
|
|
99
88
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
- In-process and local-first. No cloud API, no daemon, no data egress.
|
|
90
|
+
- Built for the stuff attackers actually abuse: spoofed MIME, nested archives, ZIP bombs, traversal, polyglots, macro hints, and active documents.
|
|
91
|
+
- Typed and composable APIs with `scanBytes`, `scanFile`, policy packs, hooks, and optional YARA.
|
|
92
|
+
- Drop-in packages for Express, Next.js, Koa, NestJS, Fastify, CLI workflows, and React UI.
|
|
103
93
|
|
|
104
|
-
|
|
94
|
+
## Why People Star It
|
|
105
95
|
|
|
106
|
-
|
|
96
|
+
- It solves a real pain point fast: secure uploads are usually more dangerous and more annoying than they look.
|
|
97
|
+
- It is easy to adopt: `npm install`, add a policy, block risky files before storage.
|
|
98
|
+
- It stays privacy-first: no cloud dependency, no file egress, no extra scanning service to operate.
|
|
99
|
+
- It feels production-minded: structured verdicts, clear reasons, adapters, examples, docs, and active release history.
|
|
107
100
|
|
|
108
|
-
|
|
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.
|
|
101
|
+
## Demo
|
|
116
102
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
All adapters share the same policy options and scanning contract. Install only what you need.
|
|
103
|
+
<p align="center">
|
|
104
|
+
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/main/assets/malware-detection-node-demo.gif" alt="Pompelmi demo showing a risky upload being blocked" width="920">
|
|
105
|
+
</p>
|
|
122
106
|
|
|
123
|
-
|
|
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 |
|
|
107
|
+
## Quick Start
|
|
132
108
|
|
|
133
109
|
```bash
|
|
134
|
-
npm
|
|
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
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Express
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
import express from 'express';
|
|
146
|
-
import multer from 'multer';
|
|
147
|
-
import { createUploadGuard } from '@pompelmi/express-middleware';
|
|
148
|
-
import { scanner, policy } from './lib/security';
|
|
149
|
-
|
|
150
|
-
const app = express();
|
|
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 })
|
|
156
|
-
);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Next.js App Router
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
// app/api/upload/route.ts
|
|
163
|
-
import { createNextUploadHandler } from '@pompelmi/next-upload';
|
|
164
|
-
import { scanner, policy } from '@/lib/security';
|
|
165
|
-
|
|
166
|
-
export const runtime = 'nodejs';
|
|
167
|
-
export const POST = createNextUploadHandler({ ...policy, scanner });
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### NestJS
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
// app.module.ts
|
|
174
|
-
import { PompelmiModule } from '@pompelmi/nestjs-integration';
|
|
175
|
-
import { CommonHeuristicsScanner } from 'pompelmi';
|
|
176
|
-
|
|
177
|
-
@Module({
|
|
178
|
-
imports: [
|
|
179
|
-
PompelmiModule.forRoot({
|
|
180
|
-
includeExtensions: ['pdf', 'zip', 'png', 'jpg'],
|
|
181
|
-
maxFileSizeBytes: 10 * 1024 * 1024,
|
|
182
|
-
scanners: [CommonHeuristicsScanner],
|
|
183
|
-
}),
|
|
184
|
-
],
|
|
185
|
-
})
|
|
186
|
-
export class AppModule {}
|
|
110
|
+
npm install pompelmi
|
|
187
111
|
```
|
|
188
112
|
|
|
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/)** **[Browse all examples →](./examples/)**
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## 🧱 Composing scanners
|
|
196
|
-
|
|
197
|
-
Build a layered scanner with heuristics, ZIP bomb protection, and optional YARA:
|
|
198
|
-
|
|
199
113
|
```ts
|
|
200
|
-
import {
|
|
201
|
-
|
|
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
|
-
);
|
|
210
|
-
```
|
|
114
|
+
import { scanBytes, STRICT_PUBLIC_UPLOAD } from 'pompelmi';
|
|
211
115
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
```
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## ⚙️ Configuration
|
|
234
|
-
|
|
235
|
-
All adapters accept the same options:
|
|
236
|
-
|
|
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. |
|
|
247
|
-
|
|
248
|
-
**Example — images only, 5 MB max:**
|
|
249
|
-
|
|
250
|
-
```ts
|
|
251
|
-
{
|
|
252
|
-
includeExtensions: ['png', 'jpg', 'jpeg', 'webp'],
|
|
253
|
-
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
|
|
254
|
-
maxFileSizeBytes: 5 * 1024 * 1024,
|
|
116
|
+
const report = await scanBytes(file.buffer, {
|
|
117
|
+
filename: file.originalname,
|
|
118
|
+
mimeType: file.mimetype,
|
|
119
|
+
policy: STRICT_PUBLIC_UPLOAD,
|
|
255
120
|
failClosed: true,
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
## 📦 Import entrypoints
|
|
262
|
-
|
|
263
|
-
pompelmi ships multiple named entrypoints so you only bundle what you need:
|
|
264
|
-
|
|
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`, …) |
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## 🔒 Policy packs
|
|
278
|
-
|
|
279
|
-
Named, pre-configured policies for common upload scenarios:
|
|
280
|
-
|
|
281
|
-
```ts
|
|
282
|
-
import { POLICY_PACKS, getPolicyPack } from 'pompelmi/policy-packs';
|
|
283
|
-
|
|
284
|
-
// Use a built-in pack:
|
|
285
|
-
const policy = POLICY_PACKS['strict-public-upload'];
|
|
286
|
-
|
|
287
|
-
// Or retrieve by name:
|
|
288
|
-
const policy = getPolicyPack('documents-only');
|
|
289
|
-
```
|
|
290
|
-
|
|
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`) |
|
|
298
|
-
|
|
299
|
-
All packs are built on `definePolicy` and are fully overridable.
|
|
300
|
-
|
|
301
|
-
---
|
|
302
|
-
|
|
303
|
-
## 🗄️ Quarantine workflow
|
|
304
|
-
|
|
305
|
-
Hold suspicious files for manual review before accepting or permanently deleting them.
|
|
306
|
-
|
|
307
|
-
```ts
|
|
308
|
-
import { scanBytes } from 'pompelmi';
|
|
309
|
-
import { QuarantineManager, FilesystemQuarantineStorage } from 'pompelmi/quarantine';
|
|
310
|
-
|
|
311
|
-
// One-time setup — store quarantined files locally.
|
|
312
|
-
const quarantine = new QuarantineManager({
|
|
313
|
-
storage: new FilesystemQuarantineStorage({ dir: './quarantine' }),
|
|
314
121
|
});
|
|
315
122
|
|
|
316
|
-
// In your upload handler:
|
|
317
|
-
const report = await scanBytes(fileBytes, { ctx: { filename: 'upload.pdf' } });
|
|
318
|
-
|
|
319
123
|
if (report.verdict !== 'clean') {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
uploadedBy: req.user?.id,
|
|
124
|
+
return res.status(422).json({
|
|
125
|
+
error: 'Upload blocked',
|
|
126
|
+
reasons: report.reasons,
|
|
324
127
|
});
|
|
325
|
-
return res.status(202).json({ quarantineId: entry.id });
|
|
326
128
|
}
|
|
327
129
|
```
|
|
328
130
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
```ts
|
|
332
|
-
// List pending entries:
|
|
333
|
-
const pending = await quarantine.listPending();
|
|
334
|
-
|
|
335
|
-
// Approve (promote to storage):
|
|
336
|
-
await quarantine.resolve(entryId, { decision: 'promote', reviewedBy: 'ops-team' });
|
|
337
|
-
|
|
338
|
-
// Delete permanently:
|
|
339
|
-
await quarantine.resolve(entryId, { decision: 'delete', reviewedBy: 'ops-team', reviewNote: 'Confirmed malware' });
|
|
340
|
-
|
|
341
|
-
// Generate an audit report:
|
|
342
|
-
const report = await quarantine.report({ status: 'pending' });
|
|
343
|
-
```
|
|
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
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
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
|
-
});
|
|
131
|
+
Start here next:
|
|
369
132
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
133
|
+
- [Getting started](https://pompelmi.github.io/pompelmi/getting-started/)
|
|
134
|
+
- [Express guide](https://pompelmi.github.io/pompelmi/how-to/express/)
|
|
135
|
+
- [Examples](./examples)
|
|
136
|
+
- [Threat model and architecture](https://pompelmi.github.io/pompelmi/explaination/architecture/)
|
|
374
137
|
|
|
375
|
-
|
|
138
|
+
## What It Checks
|
|
376
139
|
|
|
377
|
-
|
|
140
|
+
- Extension, size, and declared MIME policy checks.
|
|
141
|
+
- Magic-byte validation for renamed or disguised files.
|
|
142
|
+
- Archive protections for ZIP bombs, traversal, and nesting depth.
|
|
143
|
+
- Heuristics for risky structures such as executables, polyglots, script-bearing documents, and macro hints.
|
|
144
|
+
- Optional YARA matching when you need signature-based rules.
|
|
378
145
|
|
|
379
|
-
|
|
146
|
+
## Frameworks
|
|
380
147
|
|
|
381
|
-
|
|
382
|
-
|
|
148
|
+
| Use case | Package or guide |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| Core library | `pompelmi` |
|
|
151
|
+
| Express | `@pompelmi/express-middleware` |
|
|
152
|
+
| Next.js | `@pompelmi/next-upload` |
|
|
153
|
+
| Koa | `@pompelmi/koa-middleware` |
|
|
154
|
+
| NestJS | `@pompelmi/nestjs-integration` |
|
|
155
|
+
| Fastify | `@pompelmi/fastify-plugin` |
|
|
156
|
+
| React UI | `@pompelmi/ui-react` |
|
|
157
|
+
| CLI | `@pompelmi/cli` |
|
|
383
158
|
|
|
384
|
-
|
|
385
|
-
output: { dest: 'file', path: './audit.jsonl' },
|
|
386
|
-
});
|
|
159
|
+
## Why Not Just MIME Checks, Cloud AV, or ClamAV?
|
|
387
160
|
|
|
388
|
-
|
|
389
|
-
|
|
161
|
+
| Approach | Where it helps | Where it falls short |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| File extensions or MIME checks only | Cheap allowlists and quick policy enforcement | Renamed files and client-provided MIME values are easy to fake. |
|
|
164
|
+
| DIY upload validation | Full control over your own rules | Easy to miss archive handling, polyglots, structured verdicts, and consistent fail-closed behavior. |
|
|
165
|
+
| Cloud scanning APIs | Outsourced detection and managed infrastructure | Adds data egress, network latency, and a hard dependency on an external service. |
|
|
166
|
+
| ClamAV or daemon-based setups | Familiar signature scanning and existing operations model | Adds process overhead and still benefits from an application-layer upload gate before storage. |
|
|
390
167
|
|
|
391
|
-
|
|
392
|
-
audit.logQuarantine(entry);
|
|
168
|
+
Pompelmi focuses on the upload gate itself. It can complement YARA or ClamAV. It is not presented as a full antivirus replacement.
|
|
393
169
|
|
|
394
|
-
|
|
395
|
-
audit.logQuarantineResolved(entry);
|
|
396
|
-
```
|
|
170
|
+
## Star History
|
|
397
171
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
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.
|
|
414
|
-
|
|
415
|
-
---
|
|
416
|
-
|
|
417
|
-
## 🧬 YARA
|
|
418
|
-
|
|
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.
|
|
422
|
-
|
|
423
|
-
### Minimal adapter
|
|
424
|
-
|
|
425
|
-
```ts
|
|
426
|
-
export const MyYaraScanner = {
|
|
427
|
-
async scan(bytes: Uint8Array) {
|
|
428
|
-
const matches = await compiledRules.scan(bytes, { timeout: 1500 });
|
|
429
|
-
return matches.map(m => ({ rule: m.rule, meta: m.meta ?? {}, tags: m.tags ?? [] }));
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
Plug it into your composed scanner:
|
|
435
|
-
|
|
436
|
-
```ts
|
|
437
|
-
import { composeScanners, CommonHeuristicsScanner } from 'pompelmi';
|
|
438
|
-
|
|
439
|
-
export const scanner = composeScanners(
|
|
440
|
-
[
|
|
441
|
-
['heuristics', CommonHeuristicsScanner],
|
|
442
|
-
['yara', MyYaraScanner],
|
|
443
|
-
],
|
|
444
|
-
{ parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
|
|
445
|
-
);
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
Starter rules for common threats (EICAR, PDF-embedded JS, Office macros) are in [`rules/starter/`](./rules/).
|
|
449
|
-
|
|
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
|
|
454
|
-
|
|
455
|
-
### Quick smoke test
|
|
456
|
-
|
|
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
|
|
460
|
-
|
|
461
|
-
# Send it to your endpoint — expect HTTP 422
|
|
462
|
-
curl -F "file=@risky.pdf;type=application/pdf" http://localhost:3000/upload -i
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
👉 **[Full YARA guide in docs →](https://pompelmi.github.io/pompelmi/)**
|
|
466
|
-
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## 🤖 GitHub Action
|
|
470
|
-
|
|
471
|
-
Scan files or build artifacts in CI with a single step:
|
|
472
|
-
|
|
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
|
-
```
|
|
480
|
-
|
|
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. |
|
|
489
|
-
|
|
490
|
-
---
|
|
491
|
-
|
|
492
|
-
## 💡 Use cases
|
|
493
|
-
|
|
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.
|
|
499
|
-
|
|
500
|
-
---
|
|
501
|
-
|
|
502
|
-
## 🏢 Pompelmi Enterprise
|
|
503
|
-
|
|
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.
|
|
505
|
-
|
|
506
|
-
### What Enterprise adds
|
|
507
|
-
|
|
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** | — | ✅ |
|
|
521
|
-
|
|
522
|
-
### Who it's for
|
|
523
|
-
|
|
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.
|
|
527
|
-
|
|
528
|
-
### Drop-in integration (30 seconds)
|
|
529
|
-
|
|
530
|
-
```bash
|
|
531
|
-
npm install @pompelmi/enterprise
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
```ts
|
|
535
|
-
import Pompelmi from 'pompelmi';
|
|
536
|
-
import { PompelmiEnterprise } from '@pompelmi/enterprise';
|
|
537
|
-
|
|
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
|
-
});
|
|
543
|
-
|
|
544
|
-
const scanner = new Pompelmi();
|
|
545
|
-
enterprise.injectInto(scanner); // loads premium YARA rules + hooks all scan events
|
|
546
|
-
|
|
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
|
-
```
|
|
552
|
-
|
|
553
|
-
<div align="center">
|
|
554
|
-
|
|
555
|
-
[](https://buy.polar.sh/polar_cl_sTQdCkfdsz6D0lyLRIKKB7MJCnmBm6mfsOmTr2l2fqn)
|
|
556
|
-
|
|
557
|
-
**[View full feature comparison and pricing →](https://pompelmi.github.io/pompelmi/enterprise)**
|
|
558
|
-
|
|
559
|
-
</div>
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
## 🔒 Security
|
|
564
|
-
|
|
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.
|
|
571
|
-
|
|
572
|
-
---
|
|
573
|
-
|
|
574
|
-
## 🏆 Recognition
|
|
575
|
-
|
|
576
|
-
Featured in:
|
|
577
|
-
|
|
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)
|
|
584
|
-
|
|
585
|
-
<p align="center">
|
|
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>
|
|
590
|
-
</p>
|
|
591
|
-
|
|
592
|
-
<!-- MENTIONS:START -->
|
|
593
|
-
<!-- MENTIONS:END -->
|
|
594
|
-
|
|
595
|
-
---
|
|
596
|
-
|
|
597
|
-
## 💬 FAQ
|
|
598
|
-
|
|
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.
|
|
601
|
-
|
|
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.
|
|
604
|
-
|
|
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/).
|
|
607
|
-
|
|
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.
|
|
610
|
-
|
|
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.
|
|
613
|
-
|
|
614
|
-
**Are ZIP bombs handled?**
|
|
615
|
-
Yes. Archive scanning enforces limits on entry count, per-entry size, total uncompressed size, and nesting depth. Use `failClosed: true` in production.
|
|
616
|
-
|
|
617
|
-
**Is commercial support available?**
|
|
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).
|
|
619
|
-
|
|
620
|
-
---
|
|
621
|
-
|
|
622
|
-
## 💼 Commercial support
|
|
623
|
-
|
|
624
|
-
Limited commercial support is available on a **private, asynchronous, best-effort basis** from the maintainer. This may include:
|
|
625
|
-
|
|
626
|
-
- Integration assistance
|
|
627
|
-
- Configuration and policy review
|
|
628
|
-
- Prioritized troubleshooting
|
|
629
|
-
- Upload security guidance
|
|
630
|
-
|
|
631
|
-
Support is in writing only — no live calls or real-time support.
|
|
632
|
-
|
|
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.
|
|
634
|
-
|
|
635
|
-
> Community support (GitHub Issues and Discussions) remains free and open. For vulnerability disclosure, see [SECURITY.md](./SECURITY.md).
|
|
636
|
-
|
|
637
|
-
---
|
|
638
|
-
|
|
639
|
-
## 🤝 Contributing
|
|
640
|
-
|
|
641
|
-
PRs and issues are welcome.
|
|
642
|
-
|
|
643
|
-
```bash
|
|
644
|
-
pnpm -r build
|
|
645
|
-
pnpm -r lint
|
|
646
|
-
pnpm vitest run --coverage --passWithNoTests
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for full guidelines.
|
|
650
|
-
|
|
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>
|
|
172
|
+
If you want to follow the project as it grows, star the repo and keep an eye on releases.
|
|
656
173
|
|
|
657
174
|
<p align="center">
|
|
658
|
-
<a href="https://
|
|
659
|
-
<img src="https://
|
|
175
|
+
<a href="https://star-history.com/#pompelmi/pompelmi&Date">
|
|
176
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=pompelmi/pompelmi&type=Date">
|
|
660
177
|
</a>
|
|
661
178
|
</p>
|
|
662
179
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
## 🌍 Translations
|
|
666
|
-
|
|
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)
|
|
668
|
-
|
|
669
|
-
The English README is the authoritative source. Contributions to translations are welcome via PR.
|
|
180
|
+
## Community
|
|
670
181
|
|
|
671
|
-
|
|
182
|
+
- [Contributing guide](./CONTRIBUTING.md)
|
|
183
|
+
- [Roadmap](./ROADMAP.md)
|
|
184
|
+
- [GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)
|
|
185
|
+
- [GitHub Issues](https://github.com/pompelmi/pompelmi/issues)
|
|
186
|
+
- [Changelog](./CHANGELOG.md)
|
|
187
|
+
- [Security policy](./SECURITY.md)
|
|
672
188
|
|
|
673
|
-
|
|
189
|
+
Questions, docs fixes, examples, tests, and real-world integration feedback are all useful here.
|
|
674
190
|
|
|
675
|
-
##
|
|
191
|
+
## License
|
|
676
192
|
|
|
677
|
-
[
|
|
193
|
+
MIT. See [LICENSE](./LICENSE). Also: [Docs](https://pompelmi.github.io/pompelmi/), [GitHub](https://github.com/pompelmi/pompelmi), [npm](https://www.npmjs.com/package/pompelmi).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pompelmi",
|
|
3
|
-
"version": "0.34.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.34.4",
|
|
4
|
+
"description": "Secure file uploads for Node.js. Scan untrusted files before storage with in-process, local-first checks for MIME spoofing, archive bombs, risky document structures, and optional YARA.",
|
|
5
5
|
"main": "./dist/pompelmi.cjs",
|
|
6
6
|
"module": "./dist/pompelmi.esm.js",
|
|
7
7
|
"type": "module",
|
|
@@ -215,48 +215,30 @@
|
|
|
215
215
|
"CHANGELOG*"
|
|
216
216
|
],
|
|
217
217
|
"keywords": [
|
|
218
|
-
"malware-scanner",
|
|
219
|
-
"file-upload-security",
|
|
220
218
|
"secure-file-upload",
|
|
219
|
+
"file-upload-security",
|
|
221
220
|
"upload-security",
|
|
222
|
-
"
|
|
223
|
-
"antivirus",
|
|
224
|
-
"malware-detection",
|
|
225
|
-
"threat-detection",
|
|
226
|
-
"security-scanner",
|
|
227
|
-
"file-scanner",
|
|
221
|
+
"upload-scanning",
|
|
228
222
|
"file-scanning",
|
|
223
|
+
"file-validation",
|
|
224
|
+
"malware-scanner",
|
|
225
|
+
"mime-sniffing",
|
|
226
|
+
"magic-bytes",
|
|
227
|
+
"archive-security",
|
|
228
|
+
"zip-bomb-protection",
|
|
229
229
|
"yara",
|
|
230
230
|
"yara-rules",
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"koa-middleware",
|
|
234
|
-
"fastify-plugin",
|
|
235
|
-
"nextjs",
|
|
236
|
-
"next-js",
|
|
237
|
-
"nestjs",
|
|
238
|
-
"nuxt",
|
|
231
|
+
"local-first-security",
|
|
232
|
+
"self-hosted-security",
|
|
239
233
|
"nodejs-security",
|
|
240
|
-
"typescript-security",
|
|
241
|
-
"file-validation",
|
|
242
|
-
"upload-sanitization",
|
|
243
|
-
"mime-type-validation",
|
|
244
|
-
"magic-bytes",
|
|
245
|
-
"security",
|
|
246
|
-
"cybersecurity",
|
|
247
|
-
"devsecops",
|
|
248
|
-
"gdpr-compliance",
|
|
249
|
-
"hipaa-compliance",
|
|
250
|
-
"privacy-first",
|
|
251
|
-
"in-process-scanning",
|
|
252
|
-
"zero-cloud",
|
|
253
|
-
"self-hosted",
|
|
254
|
-
"node",
|
|
255
|
-
"nodejs",
|
|
256
234
|
"typescript",
|
|
235
|
+
"nodejs",
|
|
257
236
|
"express",
|
|
237
|
+
"nextjs",
|
|
238
|
+
"nestjs",
|
|
239
|
+
"fastify",
|
|
258
240
|
"koa",
|
|
259
|
-
"
|
|
241
|
+
"nuxt"
|
|
260
242
|
],
|
|
261
243
|
"directories": {
|
|
262
244
|
"example": "examples"
|