pompelmi 0.10.0-dev.9 → 0.11.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +73 -88
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  <h1 align="center">pompelmi</h1>
10
10
 
11
- <p align="center"><strong>Fast file‑upload malware scanning for Node.js</strong> — with optional <strong>YARA</strong>, ZIP deep‑inspection, and drop‑in adapters for <em>Express</em>, <em>Koa</em>, and <em>Next.js</em>. Private by design. Typed. Tiny.</p>
11
+ <p align="center"><strong>Fast file‑upload malware scanning for Node.js</strong> — optional <strong>YARA</strong> integration, ZIP deep‑inspection, and drop‑in adapters for <em>Express</em>, <em>Koa</em>, and <em>Next.js</em>. Private by design. Typed. Tiny.</p>
12
12
 
13
13
  <p align="center">
14
14
  <a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi?label=pompelmi&color=0a7ea4"></a>
@@ -26,19 +26,31 @@
26
26
  <p align="center">
27
27
  <a href="https://pompelmi.github.io/pompelmi/">Documentation</a> ·
28
28
  <a href="#installation">Install</a> ·
29
- <a href="#quickstart">Quickstart</a> ·
29
+ <a href="#quick-start">Quick‑start</a> ·
30
30
  <a href="#github-action">GitHub Action</a> ·
31
31
  <a href="#adapters">Adapters</a> ·
32
32
  <a href="#diagrams">Diagrams</a> ·
33
33
  <a href="#configuration">Config</a> ·
34
+ <a href="#production-checklist">Production checklist</a> ·
34
35
  <a href="#quick-test-eicar">Quick test</a> ·
35
36
  <a href="#security-notes">Security</a> ·
36
- <a href="#packages">Packages</a> ·
37
37
  <a href="#faq">FAQ</a>
38
38
  </p>
39
39
 
40
40
  ---
41
41
 
42
+ ## Highlights
43
+
44
+ - **Block risky uploads early** — mark files as <em>clean</em>, <em>suspicious</em>, or <em>malicious</em> and stop them at the edge.
45
+ - **Real checks** — extension allow‑list, MIME sniffing (magic bytes), per‑file size caps, and **deep ZIP** traversal with anti‑bomb limits.
46
+ - **Pluggable scanner** — bring your own engine (e.g. YARA) via a minimal `{ scan(bytes) }` contract.
47
+ - **Zero cloud** — scans run in‑process. Keep bytes private.
48
+ - **DX first** — TypeScript types, ESM/CJS builds, tiny API, adapters for popular web frameworks.
49
+
50
+ > Keywords: file upload security, malware scanning, YARA, Node.js, Express, Koa, Next.js, ZIP scanning
51
+
52
+ ---
53
+
42
54
  ## Installation
43
55
 
44
56
  ```bash
@@ -46,32 +58,44 @@
46
58
  npm i pompelmi
47
59
  # or
48
60
  pnpm add pompelmi
61
+ # or
62
+ yarn add pompelmi
49
63
  ```
50
64
 
51
- ## Why pompelmi?
52
-
53
- - **Block risky uploads at the edge** — mark files as <em>clean</em>, <em>suspicious</em>, or <em>malicious</em> and stop them early.
54
- - **YARA when you need it** plug in your rules; start simple and iterate.
55
- - **Real checks** — extension allow‑list, MIME sniffing (magic bytes), file size caps, and **ZIP** traversal with anti‑bomb limits.
56
- - **No cloud required** — scans run in‑process. Keep bytes private.
57
- - **DX first** — TypeScript types, ESM/CJS builds, minimal API.
58
-
59
- > Keywords: file upload security, malware scanning, YARA, Node.js, Express, Koa, Next.js, ZIP scanning
65
+ > Optional dev deps used in the examples:
66
+ >
67
+ > ```bash
68
+ > npm i -D tsx express multer @koa/router @koa/multer koa next
69
+ > ```
60
70
 
61
71
  ---
62
72
 
73
+ ## Quick‑start
63
74
 
75
+ **At a glance (policy + scanner)**
64
76
 
65
- Optional dev deps used in examples:
77
+ ```ts
78
+ // Create a tiny scanner (matches EICAR test string)
79
+ const SimpleEicarScanner = {
80
+ async scan(bytes: Uint8Array) {
81
+ const text = Buffer.from(bytes).toString('utf8');
82
+ return text.includes('EICAR-STANDARD-ANTIVIRUS-TEST-FILE') ? [{ rule: 'eicar_test' }] : [];
83
+ }
84
+ };
66
85
 
67
- ```bash
68
- npm i -D tsx express multer @koa/router @koa/multer koa next
86
+ // Example policy used by all adapters
87
+ const policy = {
88
+ scanner: SimpleEicarScanner,
89
+ includeExtensions: ['txt','png','jpg','jpeg','pdf','zip'],
90
+ allowedMimeTypes: ['text/plain','image/png','image/jpeg','application/pdf','application/zip'],
91
+ maxFileSizeBytes: 20 * 1024 * 1024,
92
+ timeoutMs: 5000,
93
+ concurrency: 4,
94
+ failClosed: true,
95
+ onScanEvent: (ev: unknown) => console.log('[scan]', ev)
96
+ };
69
97
  ```
70
98
 
71
- ---
72
-
73
- ## Quickstart
74
-
75
99
  ### Express
76
100
 
77
101
  ```ts
@@ -82,29 +106,9 @@ import { createUploadGuard } from '@pompelmi/express-middleware';
82
106
  const app = express();
83
107
  const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
84
108
 
85
- const SimpleEicarScanner = {
86
- async scan(bytes: Uint8Array) {
87
- const text = Buffer.from(bytes).toString('utf8');
88
- if (text.includes('EICAR-STANDARD-ANTIVIRUS-TEST-FILE')) return [{ rule: 'eicar_test' }];
89
- return [];
90
- }
91
- };
92
-
93
- app.post(
94
- '/upload',
95
- upload.any(),
96
- createUploadGuard({
97
- scanner: SimpleEicarScanner,
98
- includeExtensions: ['txt','png','jpg','jpeg','pdf','zip'],
99
- allowedMimeTypes: ['text/plain','image/png','image/jpeg','application/pdf','application/zip'],
100
- maxFileSizeBytes: 20 * 1024 * 1024,
101
- timeoutMs: 5000,
102
- concurrency: 4,
103
- failClosed: true,
104
- onScanEvent: (ev) => console.log('[scan]', ev)
105
- }),
106
- (req, res) => res.json({ ok: true, scan: (req as any).pompelmi ?? null })
107
- );
109
+ app.post('/upload', upload.any(), createUploadGuard(policy), (req, res) => {
110
+ res.json({ ok: true, scan: (req as any).pompelmi ?? null });
111
+ });
108
112
 
109
113
  app.listen(3000, () => console.log('http://localhost:3000'));
110
114
  ```
@@ -121,25 +125,9 @@ const app = new Koa();
121
125
  const router = new Router();
122
126
  const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
123
127
 
124
- const SimpleEicarScanner = { async scan(b: Uint8Array){
125
- return Buffer.from(b).toString('utf8').includes('EICAR') ? [{ rule: 'eicar_test' }] : [];
126
- }};
127
-
128
- router.post(
129
- '/upload',
130
- upload.any(),
131
- createKoaUploadGuard({
132
- scanner: SimpleEicarScanner,
133
- includeExtensions: ['txt','png','jpg','jpeg','pdf','zip'],
134
- allowedMimeTypes: ['text/plain','image/png','image/jpeg','application/pdf','application/zip'],
135
- maxFileSizeBytes: 20 * 1024 * 1024,
136
- timeoutMs: 5000,
137
- concurrency: 4,
138
- failClosed: true,
139
- onScanEvent: (ev) => console.log('[scan]', ev)
140
- }),
141
- (ctx) => { ctx.body = { ok: true, scan: (ctx as any).pompelmi ?? null }; }
142
- );
128
+ router.post('/upload', upload.any(), createKoaUploadGuard(policy), (ctx) => {
129
+ ctx.body = { ok: true, scan: (ctx as any).pompelmi ?? null };
130
+ });
143
131
 
144
132
  app.use(router.routes()).use(router.allowedMethods());
145
133
  app.listen(3003, () => console.log('http://localhost:3003'));
@@ -154,23 +142,15 @@ import { createNextUploadHandler } from '@pompelmi/next-upload';
154
142
  export const runtime = 'nodejs';
155
143
  export const dynamic = 'force-dynamic';
156
144
 
157
- const SimpleEicarScanner = { async scan(b: Uint8Array){
158
- return Buffer.from(b).toString('utf8').includes('EICAR') ? [{ rule: 'eicar_test' }] : [];
159
- }};
145
+ const SimpleEicarScanner = policy.scanner; // reuse the same scanner
160
146
 
161
147
  export const POST = createNextUploadHandler({
162
- scanner: SimpleEicarScanner,
163
- includeExtensions: ['txt','png','jpg','jpeg','pdf','zip'],
164
- allowedMimeTypes: ['text/plain','image/png','image/jpeg','application/pdf','application/zip'],
165
- maxFileSizeBytes: 20 * 1024 * 1024,
166
- timeoutMs: 5000,
167
- concurrency: 4,
168
- failClosed: true,
169
- onScanEvent: (ev) => console.log('[scan]', ev)
148
+ ...policy,
149
+ scanner: SimpleEicarScanner
170
150
  });
171
151
  ```
172
152
 
173
- ----
153
+ ---
174
154
 
175
155
  ## GitHub Action
176
156
 
@@ -216,6 +196,8 @@ jobs:
216
196
 
217
197
  > 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
198
 
199
+ ---
200
+
219
201
  ## Adapters
220
202
 
221
203
  Use the adapter that matches your web framework. All adapters share the same policy options and scanning contract.
@@ -287,7 +269,7 @@ sequenceDiagram
287
269
 
288
270
  ### Components (monorepo)
289
271
  <p align="center">
290
- <img alt="Monorepo components diagram" width="1100" src="https://mermaid.ink/img/eyJjb2RlIjogImZsb3djaGFydCBMUlxuICBzdWJncmFwaCBSZXBvXG4gICAgY29yZVtcInBvbXBlbG1pIChjb3JlKVwiXVxuICAgIGV4cHJlc3NbXCJAcG9tcGVsbWkvZXhwcmVzcy1taWRkbGV3YXJlXCJdXG4gICAga29hW1wiQHBvbXBlbG1pL2tvYS1taWRkbGV3YXJlXCJdXG4gICAgbmV4dFtcIkBwb21wZWxtaS9uZXh0LXVwbG9hZFwiXVxuICAgIGZhc3RpZnkoKFwiZmFzdGlmeS1wbHVnaW4gwrcgcGxhbm5lZFwiKSlcbiAgICBuZXN0KChcIm5lc3RqcyDCtyBwbGFubmVkXCIpKVxuICAgIHJlbWl4KChcInJlbWl4IMK3IHBsYW5uZWRcIikpXG4gICAgaGFwaSgoXCJoYXBpLXBsdWdpbiDCtyBwbGFubmVkXCIpKVxuICAgIHN2ZWx0ZSgoXCJzdmVsdGVraXQgwrcgcGxhbm5lZFwiKSlcbiAgZW5kXG4gIGNvcmUgLS0+IGV4cHJlc3NcbiAgY29yZSAtLT4ga29hXG4gIGNvcmUgLS0+IG5leHRcbiAgY29yZSAtLi0+IGZhc3RpZnlcbiAgY29yZSAtLi0+IG5lc3RcbiAgY29yZSAtLi0+IHJlbWl4XG4gIGNvcmUgLS4tPiBoYXBpXG4gIGNvcmUgLS4tPiBzdmVsdGUiLCAibWVybWFpZCI6IHsidGhlbWUiOiAiZGVmYXVsdCJ9fQ==?bgColor=white&width=1400&scale=2" />
272
+ <img alt="Monorepo components diagram" width="1100" src="https://mermaid.ink/img/eyJjb2RlIjogImZsb3djaGFydCBMUlxuICBzdWJncmFwaCBSZXBvXG4gICAgY29yZVtcInBvbXBlbG1pIChjb3JlKVwiXVxuICAgIGV4cHJlc3NbXCJAcG9tcGVsbWkvZXhwcmVzcy1taWRkbGV3YXJlXCJdXG4gICAga29hW1wiQHBvbXBlbWkv a29hLW1pZGRsZXdhcmVcIl1cbiAgICBuZXh0W1wiQHBvbXBlbG1pL25leHQtdXBsb2FkXCJdXG4gICAgZmFzdGlmeSgoXCJmYXN0aWZ5LXBsdWdpbiD CtyBwbGFubmVkXCIpKVxuICAgIG5lc3QoKFwibmVzdGpzIMK3IHBsYW5uZWRcIikpXG4gICAgcmVtaXgoKFwicmVtaXggwrsgcGxhbm5lZFwiKSlcbiAgICBoYXBpKChcImhhcGktcGx1Z2luIMK3IHBsYW5uZWRcIikpXG4gICAgc3ZlbHRlKChcInN2ZWx0ZWtpdCD CtyBwbGFubmVkXCIpKVxuICBlbmRcbiAgY29yZSAtLT4gZXhwcmVzc1xuICBjb3JlIC0tPiBrb2F cbiAgY29yZSAtLT4gbmV4dFxuICBjb3JlIC0uLT4gZmFzdGlmeVxuICBjb3JlIC0uLT4gbmVzdFxuICBjb3JlIC0uLT4gcmVtaXh cbiAgY29yZSAtLi0+IGhhcGlcbiAgY29yZSAtLi0+IHN2ZWx0ZSIsICJtZXJtYWlkIjogeyJ0aGVtZSI6ICJkZWZhdWx0In19?bgColor=white&width=1400&scale=2" />
291
273
  </p>
292
274
 
293
275
  <details>
@@ -317,17 +299,6 @@ flowchart LR
317
299
  ```
318
300
  </details>
319
301
 
320
- ## Packages
321
-
322
- | Package | NPM | Description |
323
- | --- | --- | --- |
324
- | **`pompelmi`** | <a href="https://www.npmjs.com/package/pompelmi"><img src="https://img.shields.io/npm/v/pompelmi?label=pompelmi" alt="npm"/></a> | Core scanner (Node + Remote Engine for browsers). |
325
- | **`@pompelmi/express-middleware`** | *(alpha)* | Express middleware to scan uploads & enforce policies. |
326
- | **`@pompelmi/koa-middleware`** | *(alpha)* | Koa middleware compatible with `@koa/multer`/`koa-body`. |
327
- | **`@pompelmi/next-upload`** | *(alpha)* | Next.js App Router `POST` handler factory. |
328
-
329
- > Status: **alpha** — small API refinements may happen before a stable milestone.
330
-
331
302
  ---
332
303
 
333
304
  ## Configuration
@@ -358,9 +329,23 @@ failClosed: true,
358
329
 
359
330
  ---
360
331
 
332
+ ## Production checklist
333
+
334
+ - [ ] **Limit file size** aggressively (`maxFileSizeBytes`).
335
+ - [ ] **Restrict extensions & MIME** to what your app truly needs.
336
+ - [ ] **Set `failClosed: true` in production** to block on timeouts/errors.
337
+ - [ ] **Handle ZIPs carefully** (enable deep ZIP, keep nesting low, cap entry sizes).
338
+ - [ ] **Log scan events** (`onScanEvent`) and monitor for spikes.
339
+ - [ ] **Run scans in a separate process/container** for defense‑in‑depth when possible.
340
+ - [ ] **Sanitize file names and paths** if you persist uploads.
341
+ - [ ] **Prefer memory storage + post‑processing**; avoid writing untrusted bytes before policy passes.
342
+ - [ ] **Add CI scanning** with the GitHub Action to catch bad files in repos/artifacts.
343
+
344
+ ---
345
+
361
346
  ## Quick test (EICAR)
362
347
 
363
- Use the Express/Koa/Next examples above, then send the standard EICAR test file to verify that blocking works end‑to‑end.
348
+ Use the examples above, then send the standard **EICAR** test file to verify end‑to‑end blocking.
364
349
 
365
350
  **1) Generate the EICAR file (safe test string)**
366
351
 
@@ -378,7 +363,7 @@ echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URV
378
363
 
379
364
  **2) Send it to your endpoint**
380
365
 
381
- Express (default from the Quickstart):
366
+ Express (default from the Quick‑start):
382
367
 
383
368
  ```bash
384
369
  curl -F "file=@eicar.txt;type=text/plain" http://localhost:3000/upload -i
@@ -432,4 +417,4 @@ pnpm -r lint
432
417
 
433
418
  ## License
434
419
 
435
- [MIT](./LICENSE) © 2025‑present pompelmi contributors
420
+ [MIT](./LICENSE) © 2025‑present pompelmi contributors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pompelmi",
3
- "version": "0.10.0-dev.9",
3
+ "version": "0.11.0-dev.10",
4
4
  "description": "RFI-safe file uploads for Node.js — Express/Koa/Next.js middleware with deep ZIP inspection, MIME/size checks, and optional YARA scanning.",
5
5
  "main": "dist/pompelmi.cjs.js",
6
6
  "module": "dist/pompelmi.esm.js",