pompelmi 0.5.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 -228
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,166 +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
|
-
|
|
13
7
|
<h1 align="center">pompelmi</h1>
|
|
14
8
|
|
|
15
|
-
<p align="center">
|
|
16
|
-
Lightweight file upload scanner with optional <strong>YARA</strong> rules.<br/>
|
|
17
|
-
Works out‑of‑the‑box on <strong>Node.js</strong>; supports <strong>browser</strong> via a simple HTTP “remote engine”.
|
|
18
|
-
</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>
|
|
19
10
|
|
|
20
|
-
<!--
|
|
21
11
|
<p align="center">
|
|
22
|
-
<img alt="
|
|
23
|
-
<img alt="
|
|
24
|
-
<img alt="
|
|
25
|
-
<img alt="
|
|
26
|
-
<img alt="
|
|
27
|
-
<img alt="
|
|
28
|
-
<img alt="
|
|
29
|
-
<img alt="
|
|
30
|
-
<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">
|
|
31
21
|
</p>
|
|
32
22
|
|
|
33
23
|
<p align="center">
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
<
|
|
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>
|
|
39
34
|
</p>
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
-->
|
|
43
|
-
|
|
44
|
-
<p align="center">
|
|
45
|
-
<a href="https://github.com/pompelmi/pompelmi">
|
|
46
|
-
<img alt="npm" src="https://img.shields.io/npm/v/pompelmi?label=pompelmi">
|
|
47
|
-
</a>
|
|
48
|
-
<a href="https://github.com/pompelmi/pompelmi">
|
|
49
|
-
<img alt="downloads" src="https://img.shields.io/npm/d18m/pompelmi?label=downloads">
|
|
50
|
-
</a>
|
|
51
|
-
<a href="https://github.com/pompelmi/pompelmi/blob/main/LICENSE">
|
|
52
|
-
<img alt="license" src="https://img.shields.io/npm/l/pompelmi">
|
|
53
|
-
</a>
|
|
54
|
-
<img alt="node" src="https://img.shields.io/node/v/pompelmi">
|
|
55
|
-
<img alt="types" src="https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white">
|
|
56
|
-
<img alt="status" src="https://img.shields.io/badge/channel-alpha-orange">
|
|
57
|
-
<img alt="coverage" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci.yml?branch=main&label=coverage&style=flat-square" />
|
|
58
|
-
|
|
59
|
-
</p>
|
|
36
|
+
---
|
|
60
37
|
|
|
61
38
|
## Installation
|
|
62
39
|
|
|
63
40
|
```bash
|
|
64
41
|
# core library
|
|
65
42
|
npm i pompelmi
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
npm i -D tsx express multer cors
|
|
43
|
+
# or
|
|
44
|
+
pnpm add pompelmi
|
|
69
45
|
```
|
|
70
46
|
|
|
71
|
-
<p align="center">
|
|
72
|
-
<a href="#why-pompelmi">Why</a> •
|
|
73
|
-
<a href="#installation">Installation</a> •
|
|
74
|
-
<a href="#technologies--tools">Technologies & Tools</a> •
|
|
75
|
-
<a href="#features">Features</a> •
|
|
76
|
-
<a href="#packages">Packages</a> •
|
|
77
|
-
<a href="#quickstart">Quickstart</a> •
|
|
78
|
-
<a href="#framework-adapters">Framework Adapters</a> •
|
|
79
|
-
<a href="#architecture--uml">Architecture & UML</a> •
|
|
80
|
-
<a href="#api-overview">API</a> •
|
|
81
|
-
<a href="#security--disclaimer">Security</a> •
|
|
82
|
-
<a href="#license">License</a>
|
|
83
|
-
</p>
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Technologies & Tools
|
|
88
|
-
|
|
89
|
-
| Technology | Badge | Link | Description |
|
|
90
|
-
| --- | --- | --- | --- |
|
|
91
|
-
| 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. |
|
|
92
|
-
| 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. |
|
|
93
|
-
| 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`. |
|
|
94
|
-
| 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`. |
|
|
95
|
-
| 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`. |
|
|
96
|
-
| 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. |
|
|
97
|
-
| 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. |
|
|
98
|
-
| 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. |
|
|
99
|
-
| 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. |
|
|
100
|
-
| 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. |
|
|
101
|
-
| 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. |
|
|
102
|
-
| 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. |
|
|
103
|
-
| 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. |
|
|
104
|
-
| 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. |
|
|
105
|
-
| 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. |
|
|
106
|
-
| 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. |
|
|
107
|
-
| 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. |
|
|
108
|
-
| 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. |
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
47
|
## Why pompelmi?
|
|
113
48
|
|
|
114
|
-
- **
|
|
115
|
-
- **
|
|
116
|
-
- **
|
|
117
|
-
- **
|
|
118
|
-
- **
|
|
119
|
-
- **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.
|
|
120
54
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
## Features
|
|
124
|
-
|
|
125
|
-
- **Node-first scanning** with optional **YARA** engine (native binaries are auto‑pulled by platform packages; no brew/apt for consumers).
|
|
126
|
-
- **ZIP aware**: inspects archive contents with limits on entries, per‑entry size, total uncompressed size, and nesting depth.
|
|
127
|
-
- **Policy filters**:
|
|
128
|
-
- allowed extensions
|
|
129
|
-
- allowed MIME types (with extension fallback)
|
|
130
|
-
- max file size per upload
|
|
131
|
-
- **Clear responses**:
|
|
132
|
-
- success (200) with scan results
|
|
133
|
-
- 4xx for policy violations (415/413)
|
|
134
|
-
- 422 when verdict is suspicious/malicious
|
|
135
|
-
- 503 on fail‑closed errors
|
|
136
|
-
- **Observability**: structured `onScanEvent` callbacks (start/end/blocked/errors/archive_*).
|
|
137
|
-
- **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
|
|
138
56
|
|
|
139
57
|
---
|
|
140
58
|
|
|
141
|
-
## Packages
|
|
142
59
|
|
|
143
|
-
This is a monorepo. The following packages are included:
|
|
144
60
|
|
|
145
|
-
|
|
146
|
-
| --- | --- | --- |
|
|
147
|
-
| **`pompelmi`** | <a href="https://github.com/pompelmi/pompelmi"><img src="https://img.shields.io/npm/v/pompelmi?label=pompelmi" alt="npm"/></a> | Core scanning library (Node + Remote Engine for browsers). |
|
|
148
|
-
| **`@pompelmi/express-middleware`** | *(alpha)* | Express middleware that scans uploads and enforces policies. |
|
|
149
|
-
| **`@pompelmi/koa-middleware`** | *(alpha)* | Koa middleware compatible with `@koa/multer`/`koa-body`. |
|
|
150
|
-
| **`@pompelmi/next-upload`** | *(alpha)* | Next.js (App Router) `POST` handler factory for `/api/upload`. |
|
|
151
|
-
| **(Planned)** `@pompelmi/fastify-plugin` | — | Fastify plugin with the same policies and ZIP support. |
|
|
152
|
-
| **(Planned)** `@pompelmi/nestjs` | — | NestJS Guard/Interceptor module for uploads. |
|
|
153
|
-
| **(Planned)** `@pompelmi/remix` | — | Remix helpers to scan `FormData` in actions/loaders. |
|
|
154
|
-
| **(Planned)** `@pompelmi/hapi-plugin` | — | Hapi plugin with `onPreHandler`. |
|
|
155
|
-
| **(Planned)** `@pompelmi/sveltekit` | — | SvelteKit utilities for `+server.ts` and actions. |
|
|
61
|
+
Optional dev deps used in examples:
|
|
156
62
|
|
|
157
|
-
|
|
63
|
+
```bash
|
|
64
|
+
npm i -D tsx express multer @koa/router @koa/multer koa next
|
|
65
|
+
```
|
|
158
66
|
|
|
159
67
|
---
|
|
160
68
|
|
|
161
69
|
## Quickstart
|
|
162
70
|
|
|
163
|
-
### Express
|
|
71
|
+
### Express
|
|
164
72
|
|
|
165
73
|
```ts
|
|
166
74
|
import express from 'express';
|
|
@@ -170,7 +78,6 @@ import { createUploadGuard } from '@pompelmi/express-middleware';
|
|
|
170
78
|
const app = express();
|
|
171
79
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
|
|
172
80
|
|
|
173
|
-
// Simple demo scanner (replace with YARA rules in production)
|
|
174
81
|
const SimpleEicarScanner = {
|
|
175
82
|
async scan(bytes: Uint8Array) {
|
|
176
83
|
const text = Buffer.from(bytes).toString('utf8');
|
|
@@ -192,15 +99,13 @@ app.post(
|
|
|
192
99
|
failClosed: true,
|
|
193
100
|
onScanEvent: (ev) => console.log('[scan]', ev)
|
|
194
101
|
}),
|
|
195
|
-
(req, res) => {
|
|
196
|
-
res.json({ ok: true, scan: (req as any).pompelmi ?? null });
|
|
197
|
-
}
|
|
102
|
+
(req, res) => res.json({ ok: true, scan: (req as any).pompelmi ?? null })
|
|
198
103
|
);
|
|
199
104
|
|
|
200
|
-
app.listen(3000, () => console.log('
|
|
105
|
+
app.listen(3000, () => console.log('http://localhost:3000'));
|
|
201
106
|
```
|
|
202
107
|
|
|
203
|
-
### Koa
|
|
108
|
+
### Koa
|
|
204
109
|
|
|
205
110
|
```ts
|
|
206
111
|
import Koa from 'koa';
|
|
@@ -212,7 +117,9 @@ const app = new Koa();
|
|
|
212
117
|
const router = new Router();
|
|
213
118
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
|
|
214
119
|
|
|
215
|
-
const SimpleEicarScanner = {
|
|
120
|
+
const SimpleEicarScanner = { async scan(b: Uint8Array){
|
|
121
|
+
return Buffer.from(b).toString('utf8').includes('EICAR') ? [{ rule: 'eicar_test' }] : [];
|
|
122
|
+
}};
|
|
216
123
|
|
|
217
124
|
router.post(
|
|
218
125
|
'/upload',
|
|
@@ -231,7 +138,7 @@ router.post(
|
|
|
231
138
|
);
|
|
232
139
|
|
|
233
140
|
app.use(router.routes()).use(router.allowedMethods());
|
|
234
|
-
app.listen(3003, () => console.log('
|
|
141
|
+
app.listen(3003, () => console.log('http://localhost:3003'));
|
|
235
142
|
```
|
|
236
143
|
|
|
237
144
|
### Next.js (App Router)
|
|
@@ -240,10 +147,12 @@ app.listen(3003, () => console.log('demo on http://localhost:3003'));
|
|
|
240
147
|
// app/api/upload/route.ts
|
|
241
148
|
import { createNextUploadHandler } from '@pompelmi/next-upload';
|
|
242
149
|
|
|
243
|
-
export const runtime = 'nodejs';
|
|
244
|
-
export const dynamic = 'force-dynamic';
|
|
150
|
+
export const runtime = 'nodejs';
|
|
151
|
+
export const dynamic = 'force-dynamic';
|
|
245
152
|
|
|
246
|
-
const SimpleEicarScanner = {
|
|
153
|
+
const SimpleEicarScanner = { async scan(b: Uint8Array){
|
|
154
|
+
return Buffer.from(b).toString('utf8').includes('EICAR') ? [{ rule: 'eicar_test' }] : [];
|
|
155
|
+
}};
|
|
247
156
|
|
|
248
157
|
export const POST = createNextUploadHandler({
|
|
249
158
|
scanner: SimpleEicarScanner,
|
|
@@ -259,33 +168,24 @@ export const POST = createNextUploadHandler({
|
|
|
259
168
|
|
|
260
169
|
---
|
|
261
170
|
|
|
262
|
-
##
|
|
171
|
+
## Adapters
|
|
263
172
|
|
|
264
|
-
|
|
173
|
+
Use the adapter that matches your web framework. All adapters share the same policy options and scanning contract.
|
|
265
174
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
-
|
|
269
|
-
|
|
270
|
-
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
- `200` — accepted, includes `{ scan: { results: [...] } }`
|
|
277
|
-
- `415` — `extension_not_allowed`, `mime_mismatch`, or `mime_not_allowed`
|
|
278
|
-
- `413` — `file_too_large`
|
|
279
|
-
- `422` — `blocked` with `verdict: suspicious|malicious`
|
|
280
|
-
- `503` — `scanner_init_error` / `scan_error` (when `failClosed: true`)
|
|
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 |
|
|
281
185
|
|
|
282
186
|
---
|
|
283
187
|
|
|
284
|
-
##
|
|
285
|
-
|
|
286
|
-
> **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.
|
|
287
|
-
> **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.
|
|
288
|
-
|
|
188
|
+
## Diagrams
|
|
289
189
|
|
|
290
190
|
### Upload scanning flow
|
|
291
191
|
<p align="center">
|
|
@@ -369,102 +269,115 @@ flowchart LR
|
|
|
369
269
|
```
|
|
370
270
|
</details>
|
|
371
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
|
+
|
|
372
283
|
---
|
|
373
284
|
|
|
374
|
-
##
|
|
285
|
+
## Configuration
|
|
286
|
+
|
|
287
|
+
All adapters accept a common set of options:
|
|
375
288
|
|
|
376
|
-
|
|
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**
|
|
301
|
+
|
|
302
|
+
Allow only images up to 5 MB:
|
|
377
303
|
|
|
378
304
|
```ts
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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.
|
|
389
316
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
317
|
+
**1) Generate the EICAR file (safe test string)**
|
|
318
|
+
|
|
319
|
+
Linux:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=' | base64 -d > eicar.txt
|
|
393
323
|
```
|
|
394
324
|
|
|
395
|
-
|
|
325
|
+
macOS:
|
|
396
326
|
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
enableYara?: boolean;
|
|
400
|
-
yaraRules?: string;
|
|
401
|
-
yaraRulesPath?: string;
|
|
402
|
-
includeExtensions?: string[];
|
|
403
|
-
maxFileSizeBytes?: number;
|
|
404
|
-
yaraAsync?: boolean;
|
|
405
|
-
yaraPreferBuffer?: boolean;
|
|
406
|
-
yaraSampleBytes?: number;
|
|
407
|
-
};
|
|
327
|
+
```bash
|
|
328
|
+
echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=' | base64 -D > eicar.txt
|
|
408
329
|
```
|
|
409
330
|
|
|
410
|
-
|
|
331
|
+
**2) Send it to your endpoint**
|
|
411
332
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
rule demo_contains_virus_literal {
|
|
417
|
-
strings: $a = "virus" ascii nocase
|
|
418
|
-
condition: $a
|
|
419
|
-
}`;
|
|
420
|
-
|
|
421
|
-
async function scanFileInBrowser(file: File) {
|
|
422
|
-
const engine = await createRemoteEngine({
|
|
423
|
-
endpoint: 'http://localhost:8787/api/yara/scan',
|
|
424
|
-
mode: 'json-base64',
|
|
425
|
-
rulesAsBase64: true,
|
|
426
|
-
});
|
|
427
|
-
const compiled = await engine.compile(RULES);
|
|
428
|
-
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
429
|
-
const matches = await compiled.scan(bytes);
|
|
430
|
-
console.log('REMOTE MATCHES:', matches);
|
|
431
|
-
}
|
|
333
|
+
Express (default from the Quickstart):
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
curl -F "file=@eicar.txt;type=text/plain" http://localhost:3000/upload -i
|
|
432
337
|
```
|
|
433
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
|
+
|
|
434
341
|
---
|
|
435
342
|
|
|
436
|
-
## Security
|
|
343
|
+
## Security notes
|
|
437
344
|
|
|
438
|
-
- The library **reads** bytes; it
|
|
439
|
-
- YARA detections depend on the **rules you
|
|
440
|
-
-
|
|
441
|
-
-
|
|
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.
|
|
442
349
|
|
|
443
350
|
---
|
|
444
351
|
|
|
445
|
-
##
|
|
352
|
+
## Star history
|
|
353
|
+
|
|
354
|
+
[](https://star-history.com/#pompelmi/pompelmi&Date)
|
|
355
|
+
|
|
356
|
+
---
|
|
446
357
|
|
|
447
|
-
|
|
358
|
+
## FAQ
|
|
448
359
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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.
|
|
456
371
|
|
|
457
372
|
---
|
|
458
373
|
|
|
459
|
-
##
|
|
374
|
+
## Contributing
|
|
460
375
|
|
|
461
|
-
|
|
462
|
-
Expect minor API changes before a stable `0.5.0`.
|
|
376
|
+
PRs and issues welcome! Start with:
|
|
463
377
|
|
|
464
|
-
Suggested publish:
|
|
465
378
|
```bash
|
|
466
|
-
|
|
467
|
-
|
|
379
|
+
pnpm -r build
|
|
380
|
+
pnpm -r lint
|
|
468
381
|
```
|
|
469
382
|
|
|
470
383
|
---
|