pompelmi 0.4.0 → 0.5.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.
- package/README.md +141 -227
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,165 +1,74 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://github.com/pompelmi/pompelmi" target="_blank" rel="noopener noreferrer">
|
|
3
|
-
<img
|
|
4
|
-
src="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg"
|
|
5
|
-
alt="pompelmi"
|
|
6
|
-
width="360"
|
|
7
|
-
height="280"
|
|
8
|
-
/>
|
|
3
|
+
<img src="https://raw.githubusercontent.com/pompelmi/pompelmi/refs/heads/main/assets/logo.svg" alt="pompelmi logo" width="360" height="280" />
|
|
9
4
|
</a>
|
|
10
5
|
</p>
|
|
11
6
|
|
|
12
7
|
<h1 align="center">pompelmi</h1>
|
|
13
8
|
|
|
14
|
-
<p align="center">
|
|
15
|
-
Lightweight file upload scanner with optional <strong>YARA</strong> rules.<br/>
|
|
16
|
-
Works out‑of‑the‑box on <strong>Node.js</strong>; supports <strong>browser</strong> via a simple HTTP “remote engine”.
|
|
17
|
-
</p>
|
|
9
|
+
<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>
|
|
18
10
|
|
|
19
|
-
<!--
|
|
20
11
|
<p align="center">
|
|
21
|
-
<img alt="
|
|
22
|
-
<img alt="
|
|
23
|
-
<img alt="
|
|
24
|
-
<img alt="
|
|
25
|
-
<img alt="
|
|
26
|
-
<img alt="
|
|
27
|
-
<img alt="
|
|
28
|
-
<img alt="
|
|
29
|
-
<img alt="
|
|
12
|
+
<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>
|
|
13
|
+
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads" src="https://img.shields.io/npm/dm/pompelmi?label=downloads&color=6E9F18"></a>
|
|
14
|
+
<img alt="node engines" src="https://img.shields.io/node/v/pompelmi?label=node">
|
|
15
|
+
<img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white">
|
|
16
|
+
<a href="https://github.com/pompelmi/pompelmi/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/pompelmi"></a>
|
|
17
|
+
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social"></a>
|
|
18
|
+
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/ci-release-publish.yml"><img alt="CI / Release / Publish" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci-release-publish.yml?branch=main&label=CI%20%2F%20Release%20%2F%20Publish"></a>
|
|
19
|
+
<a href="https://github.com/pompelmi/pompelmi/issues"><img alt="open issues" src="https://img.shields.io/github/issues/pompelmi/pompelmi"></a>
|
|
20
|
+
<img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
|
|
30
21
|
</p>
|
|
31
22
|
|
|
32
23
|
<p align="center">
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
24
|
+
<a href="https://pompelmi.github.io/pompelmi/">Documentation</a> ·
|
|
25
|
+
<a href="#installation">Install</a> ·
|
|
26
|
+
<a href="#quickstart">Quickstart</a> ·
|
|
27
|
+
<a href="#adapters">Adapters</a> ·
|
|
28
|
+
<a href="#diagrams">Diagrams</a> ·
|
|
29
|
+
<a href="#configuration">Config</a> ·
|
|
30
|
+
<a href="#quick-test-eicar">Quick test</a> ·
|
|
31
|
+
<a href="#security-notes">Security</a> ·
|
|
32
|
+
<a href="#packages">Packages</a> ·
|
|
33
|
+
<a href="#faq">FAQ</a>
|
|
38
34
|
</p>
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
-->
|
|
42
|
-
|
|
43
|
-
<p align="center">
|
|
44
|
-
<a href="https://www.npmjs.com/package/pompelmi">
|
|
45
|
-
<img alt="npm" src="https://img.shields.io/npm/v/pompelmi?label=pompelmi">
|
|
46
|
-
</a>
|
|
47
|
-
<a href="https://www.npmjs.com/package/pompelmi">
|
|
48
|
-
<img alt="downloads" src="https://img.shields.io/npm/d18m/pompelmi?label=downloads">
|
|
49
|
-
</a>
|
|
50
|
-
<a href="https://github.com/pompelmi/pompelmi/blob/main/LICENSE">
|
|
51
|
-
<img alt="license" src="https://img.shields.io/npm/l/pompelmi">
|
|
52
|
-
</a>
|
|
53
|
-
<img alt="node" src="https://img.shields.io/node/v/pompelmi">
|
|
54
|
-
<img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white">
|
|
55
|
-
<img alt="status" src="https://img.shields.io/badge/channel-alpha-orange">
|
|
56
|
-
<img alt="coverage" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci.yml?branch=main&label=coverage&style=flat-square" />
|
|
57
|
-
|
|
58
|
-
</p>
|
|
36
|
+
---
|
|
59
37
|
|
|
60
38
|
## Installation
|
|
61
39
|
|
|
62
40
|
```bash
|
|
63
41
|
# core library
|
|
64
42
|
npm i pompelmi
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
npm i -D tsx express multer cors
|
|
43
|
+
# or
|
|
44
|
+
pnpm add pompelmi
|
|
68
45
|
```
|
|
69
46
|
|
|
70
|
-
<p align="center">
|
|
71
|
-
<a href="#why-pompelmi">Why</a> •
|
|
72
|
-
<a href="#installation">Installation</a> •
|
|
73
|
-
<a href="#technologies--tools">Technologies & Tools</a> •
|
|
74
|
-
<a href="#features">Features</a> •
|
|
75
|
-
<a href="#packages">Packages</a> •
|
|
76
|
-
<a href="#quickstart">Quickstart</a> •
|
|
77
|
-
<a href="#framework-adapters">Framework Adapters</a> •
|
|
78
|
-
<a href="#architecture--uml">Architecture & UML</a> •
|
|
79
|
-
<a href="#api-overview">API</a> •
|
|
80
|
-
<a href="#security--disclaimer">Security</a> •
|
|
81
|
-
<a href="#license">License</a>
|
|
82
|
-
</p>
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## Technologies & Tools
|
|
87
|
-
|
|
88
|
-
| Technology | Badge | Link | Description |
|
|
89
|
-
| --- | --- | --- | --- |
|
|
90
|
-
| Node.js | <img alt="Node.js" src="https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white" /> | [nodejs.org](https://nodejs.org/) | Runtime used by all adapters and the core engine. |
|
|
91
|
-
| TypeScript | <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" /> | [typescriptlang.org](https://www.typescriptlang.org/) | Typed development and bundled type definitions. |
|
|
92
|
-
| Express | <img alt="Express" src="https://img.shields.io/badge/Express-000000?style=for-the-badge&logo=express&logoColor=white" /> | [expressjs.com](https://expressjs.com/) | Middleware adapter `@pompelmi/express-middleware`. |
|
|
93
|
-
| Koa | <img alt="Koa" src="https://img.shields.io/badge/Koa-33333D?style=for-the-badge&logo=nodedotjs&logoColor=white" /> | [koajs.com](https://koajs.com/) | Middleware adapter `@pompelmi/koa-middleware`. |
|
|
94
|
-
| Next.js | <img alt="Next.js" src="https://img.shields.io/badge/Next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white" /> | [nextjs.org](https://nextjs.org/) | App Router upload handler `@pompelmi/next-upload`. |
|
|
95
|
-
| Fastify *(planned)* | <img alt="Fastify" src="https://img.shields.io/badge/Fastify-000000?style=for-the-badge&logo=fastify&logoColor=white" /> | [fastify.dev](https://fastify.dev/) | Planned plugin with identical policies and ZIP handling. |
|
|
96
|
-
| NestJS *(planned)* | <img alt="NestJS" src="https://img.shields.io/badge/NestJS-E0234E?style=for-the-badge&logo=nestjs&logoColor=white" /> | [nestjs.com](https://nestjs.com/) | Planned interceptor/guard for file uploads. |
|
|
97
|
-
| Remix *(planned)* | <img alt="Remix" src="https://img.shields.io/badge/Remix-000000?style=for-the-badge&logo=remix&logoColor=white" /> | [remix.run](https://remix.run/) | Planned helpers to scan `FormData` in actions/loaders. |
|
|
98
|
-
| SvelteKit *(planned)* | <img alt="SvelteKit" src="https://img.shields.io/badge/SvelteKit-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" /> | [kit.svelte.dev](https://kit.svelte.dev/) | Planned utilities for `+server.ts` and actions. |
|
|
99
|
-
| pnpm | <img alt="pnpm" src="https://img.shields.io/badge/pnpm-222222?style=for-the-badge&logo=pnpm&logoColor=white" /> | [pnpm.io](https://pnpm.io/) | Monorepo/workspace package manager. |
|
|
100
|
-
| npm | <img alt="npm" src="https://img.shields.io/badge/npm-CB3837?style=for-the-badge&logo=npm&logoColor=white" /> | [npmjs.com](https://www.npmjs.com/) | Registry and install scripts. |
|
|
101
|
-
| Vitest | <img alt="Vitest" src="https://img.shields.io/badge/Vitest-6E9F18?style=for-the-badge&logo=vitest&logoColor=white" /> | [vitest.dev](https://vitest.dev/) | Test runner for future E2E and unit tests. |
|
|
102
|
-
| ESLint | <img alt="ESLint" src="https://img.shields.io/badge/ESLint-4B32C3?style=for-the-badge&logo=eslint&logoColor=white" /> | [eslint.org](https://eslint.org/) | Linting. |
|
|
103
|
-
| Prettier | <img alt="Prettier" src="https://img.shields.io/badge/Prettier-F7B93E?style=for-the-badge&logo=prettier&logoColor=white" /> | [prettier.io](https://prettier.io/) | Code formatting. |
|
|
104
|
-
| YARA | <img alt="YARA" src="https://img.shields.io/badge/YARA-2F855A?style=for-the-badge" /> | [virustotal.github.io/yara](https://virustotal.github.io/yara/) | Optional rule engine for advanced detections. |
|
|
105
|
-
| file-type | <img alt="file-type" src="https://img.shields.io/badge/file--type-24292E?style=for-the-badge" /> | [sindresorhus/file-type](https://github.com/sindresorhus/file-type) | MIME sniffing (magic bytes) on buffers. |
|
|
106
|
-
| unzipper | <img alt="unzipper" src="https://img.shields.io/badge/unzipper-24292E?style=for-the-badge" /> | [ZJONSSON/node-unzipper](https://github.com/ZJONSSON/node-unzipper) | ZIP processing with anti‑bomb limits and nested scan. |
|
|
107
|
-
| Multer | <img alt="Multer" src="https://img.shields.io/badge/Multer-000000?style=for-the-badge" /> | [expressjs/multer](https://github.com/expressjs/multer) | In‑memory file buffers for Express/Koa demos. |
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
47
|
## Why pompelmi?
|
|
112
48
|
|
|
113
|
-
- **
|
|
114
|
-
- **
|
|
115
|
-
- **
|
|
116
|
-
- **
|
|
117
|
-
- **
|
|
118
|
-
- **Typed and tiny**: TypeScript types included, ESM & CJS builds.
|
|
49
|
+
- **Block risky uploads at the edge** — mark files as <em>clean</em>, <em>suspicious</em>, or <em>malicious</em> and stop them early.
|
|
50
|
+
- **YARA when you need it** — plug in your rules; start simple and iterate.
|
|
51
|
+
- **Real checks** — extension allow‑list, MIME sniffing (magic bytes), file size caps, and **ZIP** traversal with anti‑bomb limits.
|
|
52
|
+
- **No cloud required** — scans run in‑process. Keep bytes private.
|
|
53
|
+
- **DX first** — TypeScript types, ESM/CJS builds, minimal API.
|
|
119
54
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
## Features
|
|
123
|
-
|
|
124
|
-
- **Node-first scanning** with optional **YARA** engine (native binaries are auto‑pulled by platform packages; no brew/apt for consumers).
|
|
125
|
-
- **ZIP aware**: inspects archive contents with limits on entries, per‑entry size, total uncompressed size, and nesting depth.
|
|
126
|
-
- **Policy filters**:
|
|
127
|
-
- allowed extensions
|
|
128
|
-
- allowed MIME types (with extension fallback)
|
|
129
|
-
- max file size per upload
|
|
130
|
-
- **Clear responses**:
|
|
131
|
-
- success (200) with scan results
|
|
132
|
-
- 4xx for policy violations (415/413)
|
|
133
|
-
- 422 when verdict is suspicious/malicious
|
|
134
|
-
- 503 on fail‑closed errors
|
|
135
|
-
- **Observability**: structured `onScanEvent` callbacks (start/end/blocked/errors/archive_*).
|
|
136
|
-
- **Browser support** via a **Remote Engine** (HTTP endpoint) that compiles rules and runs scans for you.
|
|
55
|
+
> Keywords: file upload security, malware scanning, YARA, Node.js, Express, Koa, Next.js, ZIP scanning
|
|
137
56
|
|
|
138
57
|
---
|
|
139
58
|
|
|
140
|
-
## Packages
|
|
141
59
|
|
|
142
|
-
This is a monorepo. The following packages are included:
|
|
143
60
|
|
|
144
|
-
|
|
145
|
-
| --- | --- | --- |
|
|
146
|
-
| **`pompelmi`** | <a href="https://www.npmjs.com/package/pompelmi"><img src="https://img.shields.io/npm/v/pompelmi?label=pompelmi" alt="npm"/></a> | Core scanning library (Node + Remote Engine for browsers). |
|
|
147
|
-
| **`@pompelmi/express-middleware`** | *(alpha)* | Express middleware that scans uploads and enforces policies. |
|
|
148
|
-
| **`@pompelmi/koa-middleware`** | *(alpha)* | Koa middleware compatible with `@koa/multer`/`koa-body`. |
|
|
149
|
-
| **`@pompelmi/next-upload`** | *(alpha)* | Next.js (App Router) `POST` handler factory for `/api/upload`. |
|
|
150
|
-
| **(Planned)** `@pompelmi/fastify-plugin` | — | Fastify plugin with the same policies and ZIP support. |
|
|
151
|
-
| **(Planned)** `@pompelmi/nestjs` | — | NestJS Guard/Interceptor module for uploads. |
|
|
152
|
-
| **(Planned)** `@pompelmi/remix` | — | Remix helpers to scan `FormData` in actions/loaders. |
|
|
153
|
-
| **(Planned)** `@pompelmi/hapi-plugin` | — | Hapi plugin with `onPreHandler`. |
|
|
154
|
-
| **(Planned)** `@pompelmi/sveltekit` | — | SvelteKit utilities for `+server.ts` and actions. |
|
|
61
|
+
Optional dev deps used in examples:
|
|
155
62
|
|
|
156
|
-
|
|
63
|
+
```bash
|
|
64
|
+
npm i -D tsx express multer @koa/router @koa/multer koa next
|
|
65
|
+
```
|
|
157
66
|
|
|
158
67
|
---
|
|
159
68
|
|
|
160
69
|
## Quickstart
|
|
161
70
|
|
|
162
|
-
### Express
|
|
71
|
+
### Express
|
|
163
72
|
|
|
164
73
|
```ts
|
|
165
74
|
import express from 'express';
|
|
@@ -169,7 +78,6 @@ import { createUploadGuard } from '@pompelmi/express-middleware';
|
|
|
169
78
|
const app = express();
|
|
170
79
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
|
|
171
80
|
|
|
172
|
-
// Simple demo scanner (replace with YARA rules in production)
|
|
173
81
|
const SimpleEicarScanner = {
|
|
174
82
|
async scan(bytes: Uint8Array) {
|
|
175
83
|
const text = Buffer.from(bytes).toString('utf8');
|
|
@@ -191,15 +99,13 @@ app.post(
|
|
|
191
99
|
failClosed: true,
|
|
192
100
|
onScanEvent: (ev) => console.log('[scan]', ev)
|
|
193
101
|
}),
|
|
194
|
-
(req, res) => {
|
|
195
|
-
res.json({ ok: true, scan: (req as any).pompelmi ?? null });
|
|
196
|
-
}
|
|
102
|
+
(req, res) => res.json({ ok: true, scan: (req as any).pompelmi ?? null })
|
|
197
103
|
);
|
|
198
104
|
|
|
199
|
-
app.listen(3000, () => console.log('
|
|
105
|
+
app.listen(3000, () => console.log('http://localhost:3000'));
|
|
200
106
|
```
|
|
201
107
|
|
|
202
|
-
### Koa
|
|
108
|
+
### Koa
|
|
203
109
|
|
|
204
110
|
```ts
|
|
205
111
|
import Koa from 'koa';
|
|
@@ -211,7 +117,9 @@ const app = new Koa();
|
|
|
211
117
|
const router = new Router();
|
|
212
118
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
|
|
213
119
|
|
|
214
|
-
const SimpleEicarScanner = {
|
|
120
|
+
const SimpleEicarScanner = { async scan(b: Uint8Array){
|
|
121
|
+
return Buffer.from(b).toString('utf8').includes('EICAR') ? [{ rule: 'eicar_test' }] : [];
|
|
122
|
+
}};
|
|
215
123
|
|
|
216
124
|
router.post(
|
|
217
125
|
'/upload',
|
|
@@ -230,7 +138,7 @@ router.post(
|
|
|
230
138
|
);
|
|
231
139
|
|
|
232
140
|
app.use(router.routes()).use(router.allowedMethods());
|
|
233
|
-
app.listen(3003, () => console.log('
|
|
141
|
+
app.listen(3003, () => console.log('http://localhost:3003'));
|
|
234
142
|
```
|
|
235
143
|
|
|
236
144
|
### Next.js (App Router)
|
|
@@ -239,10 +147,12 @@ app.listen(3003, () => console.log('demo on http://localhost:3003'));
|
|
|
239
147
|
// app/api/upload/route.ts
|
|
240
148
|
import { createNextUploadHandler } from '@pompelmi/next-upload';
|
|
241
149
|
|
|
242
|
-
export const runtime = 'nodejs';
|
|
243
|
-
export const dynamic = 'force-dynamic';
|
|
150
|
+
export const runtime = 'nodejs';
|
|
151
|
+
export const dynamic = 'force-dynamic';
|
|
244
152
|
|
|
245
|
-
const SimpleEicarScanner = {
|
|
153
|
+
const SimpleEicarScanner = { async scan(b: Uint8Array){
|
|
154
|
+
return Buffer.from(b).toString('utf8').includes('EICAR') ? [{ rule: 'eicar_test' }] : [];
|
|
155
|
+
}};
|
|
246
156
|
|
|
247
157
|
export const POST = createNextUploadHandler({
|
|
248
158
|
scanner: SimpleEicarScanner,
|
|
@@ -258,33 +168,24 @@ export const POST = createNextUploadHandler({
|
|
|
258
168
|
|
|
259
169
|
---
|
|
260
170
|
|
|
261
|
-
##
|
|
262
|
-
|
|
263
|
-
The adapters share the same behavior and defaults:
|
|
171
|
+
## Adapters
|
|
264
172
|
|
|
265
|
-
|
|
266
|
-
- **MIME sniffing with extension fallback**
|
|
267
|
-
- **Max file size**
|
|
268
|
-
- **ZIP scanning** (entry count / per‑entry size / total uncompressed / depth)
|
|
269
|
-
- **Timeout & concurrency** controls
|
|
270
|
-
- **Fail‑closed** and **report‑only** modes
|
|
271
|
-
- **Structured events** via `onScanEvent`
|
|
173
|
+
Use the adapter that matches your web framework. All adapters share the same policy options and scanning contract.
|
|
272
174
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
-
|
|
278
|
-
|
|
279
|
-
|
|
175
|
+
| Framework | Package | Status |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| Express | `@pompelmi/express-middleware` | alpha |
|
|
178
|
+
| Koa | `@pompelmi/koa-middleware` | alpha |
|
|
179
|
+
| Next.js (App Router) | `@pompelmi/next-upload` | alpha |
|
|
180
|
+
| Fastify | fastify plugin — planned |
|
|
181
|
+
| NestJS | nestjs — planned |
|
|
182
|
+
| Remix | remix — planned |
|
|
183
|
+
| hapi | hapi plugin — planned |
|
|
184
|
+
| SvelteKit | sveltekit — planned |
|
|
280
185
|
|
|
281
186
|
---
|
|
282
187
|
|
|
283
|
-
##
|
|
284
|
-
|
|
285
|
-
> **Note:** Diagrams are embedded as images via mermaid.ink so they render on GitHub, npm, and other Markdown viewers. The Mermaid source is included below each image.
|
|
286
|
-
> **Tip:** To avoid parser issues across renderers, labels use quotes inside node shapes (e.g., `A["text"]`, `C{"text"}`) when they include parentheses, slashes, or other symbols.
|
|
287
|
-
|
|
188
|
+
## Diagrams
|
|
288
189
|
|
|
289
190
|
### Upload scanning flow
|
|
290
191
|
<p align="center">
|
|
@@ -368,102 +269,115 @@ flowchart LR
|
|
|
368
269
|
```
|
|
369
270
|
</details>
|
|
370
271
|
|
|
272
|
+
## Packages
|
|
273
|
+
|
|
274
|
+
| Package | NPM | Description |
|
|
275
|
+
| --- | --- | --- |
|
|
276
|
+
| **`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). |
|
|
277
|
+
| **`@pompelmi/express-middleware`** | *(alpha)* | Express middleware to scan uploads & enforce policies. |
|
|
278
|
+
| **`@pompelmi/koa-middleware`** | *(alpha)* | Koa middleware compatible with `@koa/multer`/`koa-body`. |
|
|
279
|
+
| **`@pompelmi/next-upload`** | *(alpha)* | Next.js App Router `POST` handler factory. |
|
|
280
|
+
|
|
281
|
+
> Status: **alpha** — small API refinements may happen before a stable milestone.
|
|
282
|
+
|
|
371
283
|
---
|
|
372
284
|
|
|
373
|
-
##
|
|
285
|
+
## Configuration
|
|
286
|
+
|
|
287
|
+
All adapters accept a common set of options:
|
|
288
|
+
|
|
289
|
+
| Option | Type (TS) | Purpose |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| `scanner` | `{ scan(bytes: Uint8Array): Promise<Match[]> }` | Your scanning engine. Return `[]` when clean; non‑empty to flag. |
|
|
292
|
+
| `includeExtensions` | `string[]` | Allow‑list of file extensions. Evaluated case‑insensitively. |
|
|
293
|
+
| `allowedMimeTypes` | `string[]` | Allow‑list of MIME types after magic‑byte sniffing. |
|
|
294
|
+
| `maxFileSizeBytes` | `number` | Per‑file size cap. Oversize files are rejected early. |
|
|
295
|
+
| `timeoutMs` | `number` | Per‑file scan timeout; guards against stuck scanners. |
|
|
296
|
+
| `concurrency` | `number` | How many files to scan in parallel. |
|
|
297
|
+
| `failClosed` | `boolean` | If `true`, errors/timeouts block the upload. |
|
|
298
|
+
| `onScanEvent` | `(event: unknown) => void` | Optional telemetry hook for logging/metrics. |
|
|
299
|
+
|
|
300
|
+
**Common recipes**
|
|
374
301
|
|
|
375
|
-
|
|
302
|
+
Allow only images up to 5 MB:
|
|
376
303
|
|
|
377
304
|
```ts
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
305
|
+
includeExtensions: ['png','jpg','jpeg','webp'],
|
|
306
|
+
allowedMimeTypes: ['image/png','image/jpeg','image/webp'],
|
|
307
|
+
maxFileSizeBytes: 5 * 1024 * 1024,
|
|
308
|
+
failClosed: true,
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Quick test (EICAR)
|
|
314
|
+
|
|
315
|
+
Use the Express/Koa/Next examples above, then send the standard EICAR test file to verify that blocking works end‑to‑end.
|
|
316
|
+
|
|
317
|
+
**1) Generate the EICAR file (safe test string)**
|
|
388
318
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
319
|
+
Linux:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=' | base64 -d > eicar.txt
|
|
392
323
|
```
|
|
393
324
|
|
|
394
|
-
|
|
325
|
+
macOS:
|
|
395
326
|
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
enableYara?: boolean;
|
|
399
|
-
yaraRules?: string;
|
|
400
|
-
yaraRulesPath?: string;
|
|
401
|
-
includeExtensions?: string[];
|
|
402
|
-
maxFileSizeBytes?: number;
|
|
403
|
-
yaraAsync?: boolean;
|
|
404
|
-
yaraPreferBuffer?: boolean;
|
|
405
|
-
yaraSampleBytes?: number;
|
|
406
|
-
};
|
|
327
|
+
```bash
|
|
328
|
+
echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=' | base64 -D > eicar.txt
|
|
407
329
|
```
|
|
408
330
|
|
|
409
|
-
|
|
331
|
+
**2) Send it to your endpoint**
|
|
410
332
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
rule demo_contains_virus_literal {
|
|
416
|
-
strings: $a = "virus" ascii nocase
|
|
417
|
-
condition: $a
|
|
418
|
-
}`;
|
|
419
|
-
|
|
420
|
-
async function scanFileInBrowser(file: File) {
|
|
421
|
-
const engine = await createRemoteEngine({
|
|
422
|
-
endpoint: 'http://localhost:8787/api/yara/scan',
|
|
423
|
-
mode: 'json-base64',
|
|
424
|
-
rulesAsBase64: true,
|
|
425
|
-
});
|
|
426
|
-
const compiled = await engine.compile(RULES);
|
|
427
|
-
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
428
|
-
const matches = await compiled.scan(bytes);
|
|
429
|
-
console.log('REMOTE MATCHES:', matches);
|
|
430
|
-
}
|
|
333
|
+
Express (default from the Quickstart):
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
curl -F "file=@eicar.txt;type=text/plain" http://localhost:3000/upload -i
|
|
431
337
|
```
|
|
432
338
|
|
|
339
|
+
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.
|
|
340
|
+
|
|
433
341
|
---
|
|
434
342
|
|
|
435
|
-
## Security
|
|
343
|
+
## Security notes
|
|
436
344
|
|
|
437
|
-
- The library **reads** bytes; it
|
|
438
|
-
- YARA detections depend on the **rules you
|
|
439
|
-
-
|
|
440
|
-
-
|
|
345
|
+
- The library **reads** bytes; it never executes files.
|
|
346
|
+
- YARA detections depend on the **rules you provide**; expect some false positives/negatives.
|
|
347
|
+
- ZIP scanning applies limits (entries, per‑entry size, total uncompressed, nesting) to reduce archive‑bomb risk.
|
|
348
|
+
- Prefer running scans in a **dedicated process/container** for defense‑in‑depth.
|
|
441
349
|
|
|
442
350
|
---
|
|
443
351
|
|
|
444
|
-
##
|
|
352
|
+
## Star history
|
|
353
|
+
|
|
354
|
+
[](https://star-history.com/#pompelmi/pompelmi&Date)
|
|
355
|
+
|
|
356
|
+
---
|
|
445
357
|
|
|
446
|
-
|
|
358
|
+
## FAQ
|
|
447
359
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
360
|
+
**Do I need YARA?**
|
|
361
|
+
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.
|
|
362
|
+
|
|
363
|
+
**Where do the results live?**
|
|
364
|
+
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.
|
|
365
|
+
|
|
366
|
+
**Why 422 for blocked files?**
|
|
367
|
+
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.
|
|
368
|
+
|
|
369
|
+
**Are ZIP bombs handled?**
|
|
370
|
+
Archives are traversed with limits to reduce archive‑bomb risk. Keep your size limits conservative and prefer `failClosed: true` in production.
|
|
455
371
|
|
|
456
372
|
---
|
|
457
373
|
|
|
458
|
-
##
|
|
374
|
+
## Contributing
|
|
459
375
|
|
|
460
|
-
|
|
461
|
-
Expect minor API changes before a stable `0.4.0`.
|
|
376
|
+
PRs and issues welcome! Start with:
|
|
462
377
|
|
|
463
|
-
Suggested publish:
|
|
464
378
|
```bash
|
|
465
|
-
|
|
466
|
-
|
|
379
|
+
pnpm -r build
|
|
380
|
+
pnpm -r lint
|
|
467
381
|
```
|
|
468
382
|
|
|
469
383
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pompelmi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Prototipo di scanner di file lato cliente",
|
|
5
5
|
"main": "dist/pompelmi.cjs.js",
|
|
6
6
|
"module": "dist/pompelmi.esm.js",
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"workspaces": [
|
|
106
106
|
"packages/*"
|
|
107
107
|
],
|
|
108
|
+
"packageManager": "pnpm@9.12.0",
|
|
108
109
|
"repository": {
|
|
109
110
|
"type": "git",
|
|
110
111
|
"url": "https://github.com/pompelmi/pompelmi"
|