pompelmi 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -88
- package/package.json +7 -3
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> —
|
|
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="#
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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 =
|
|
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
|
-
|
|
163
|
-
|
|
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/
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.10.0",
|
|
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",
|
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"pnpm": {
|
|
18
18
|
"overrides": {
|
|
19
|
-
"regjsgen": "0.
|
|
20
|
-
"fflate": "0.
|
|
19
|
+
"regjsgen": "0.8.0",
|
|
20
|
+
"fflate": "0.8.2",
|
|
21
|
+
"@tokenizer/inflate>fflate": "0.8.2",
|
|
22
|
+
"file-type>fflate": "0.8.2",
|
|
23
|
+
"regexpu-core>regjsgen": "0.8.0",
|
|
24
|
+
"@babel/helper-create-regexp-features-plugin>regjsgen": "0.8.0"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"scripts": {
|