pompelmi 1.12.1 → 1.14.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 CHANGED
@@ -73,6 +73,9 @@ Most integrations require parsing ClamAV's stdout with regex, managing a clamd d
73
73
  ## Features
74
74
 
75
75
  - Standalone CLI — scan files from any terminal with `npx pompelmi scan`
76
+ - HTML security dashboard — generate beautiful scan reports with `--report` ([docs](./docs/dashboard.html))
77
+ - SVG share card — shareable scan result card with `--share-card` ([docs](./docs/dashboard.html#share-card))
78
+ - GitHub App — one-click installation for organizations, zero-config PR scanning ([docs](./docs/github-app.html))
76
79
  - Single `scan(filePath, [options])` function — works locally or against a remote clamd instance
77
80
  - `scanBuffer(buffer, [options])` — scan in-memory Buffers directly, no temp file required in TCP mode
78
81
  - `scanStream(stream, [options])` — scan a Readable stream directly. In TCP mode, streamed to clamd with no disk I/O.
@@ -86,13 +89,51 @@ Most integrations require parsing ClamAV's stdout with regex, managing a clamd d
86
89
  - Symbol-based verdicts (`Verdict.Clean` / `Verdict.Malicious` / `Verdict.ScanError`) — typo-proof comparisons
87
90
  - Full clamd support via the INSTREAM protocol — TCP (`host`/`port`) or UNIX socket (`socket`) with configurable timeout
88
91
  - Built-in helpers to install ClamAV and update virus definitions programmatically
89
- - Works with Express, Fastify, and any other Node.js HTTP framework
92
+ - Works with Express, Fastify, NestJS, and any other Node.js HTTP framework
90
93
  - Zero runtime dependencies — ships nothing but source code
91
94
  - Tested with EICAR standard antivirus test files
92
95
  - CommonJS module; TypeScript type declarations available inline
93
96
 
94
97
  ---
95
98
 
99
+ ## Framework Integrations
100
+
101
+ Official integration packages for popular frameworks:
102
+
103
+ | Package | Framework | Install |
104
+ |---------|-----------|---------|
105
+ | [@pompelmi/nestjs](./packages/nestjs/) | NestJS | `npm i @pompelmi/nestjs` |
106
+ | [@pompelmi/fastify](./packages/fastify/) | Fastify | `npm i @pompelmi/fastify` |
107
+
108
+ ### NestJS
109
+
110
+ ```ts
111
+ import { PompelmiModule, PompelmiService } from '@pompelmi/nestjs';
112
+
113
+ // app.module.ts
114
+ @Module({ imports: [PompelmiModule.forRoot({ host: 'localhost', port: 3310 })] })
115
+ export class AppModule {}
116
+
117
+ // upload.service.ts
118
+ constructor(private readonly pompelmi: PompelmiService) {}
119
+ const result = await this.pompelmi.scanBuffer(file.buffer);
120
+ ```
121
+
122
+ ### Fastify
123
+
124
+ ```js
125
+ const pompelmi = require('@pompelmi/fastify');
126
+ await fastify.register(pompelmi, { host: 'localhost', port: 3310 });
127
+
128
+ // Scan manually
129
+ const result = await fastify.pompelmi.scanBuffer(buffer);
130
+
131
+ // Or use the preHandler hook
132
+ fastify.post('/upload', { preHandler: fastify.pompelmi.preHandler({ field: 'file' }) }, handler);
133
+ ```
134
+
135
+ ---
136
+
96
137
  ## Requirements
97
138
 
98
139
  - **Node.js** — any LTS release (no native addons, no C++ bindings)
@@ -465,6 +506,8 @@ Scan any repository for viruses on every push or pull request — ClamAV is bund
465
506
 
466
507
  A ready-to-copy workflow is available at [`.github/workflows/action-example.yml`](./.github/workflows/action-example.yml). Full reference — inputs, outputs, layer caching, and more examples — in **[docs/github-action.md](./docs/github-action.md)**.
467
508
 
509
+ > **For organizations:** install the [pompelmi GitHub App](./docs/github-app.html) for zero-config scanning on every PR — no workflow file needed.
510
+
468
511
  ---
469
512
 
470
513
  ## Contributing
package/bin/pompelmi.js CHANGED
@@ -67,6 +67,10 @@ function parseArgs(argv) {
67
67
  quiet: false,
68
68
  delete: false,
69
69
  recursive: true,
70
+ report: false,
71
+ reportOutput: null,
72
+ shareCard: false,
73
+ shareCardOutput: null,
70
74
  };
71
75
 
72
76
  let i = 0;
@@ -92,6 +96,14 @@ function parseArgs(argv) {
92
96
  opts.timeout = parseInt(args[++i], 10);
93
97
  } else if (a === '--retries') {
94
98
  opts.retries = parseInt(args[++i], 10);
99
+ } else if (a === '--report') {
100
+ opts.report = true;
101
+ } else if (a === '--share-card') {
102
+ opts.shareCard = true;
103
+ } else if (a === '--output') {
104
+ const next = args[++i];
105
+ if (next && next.endsWith('.svg')) opts.shareCardOutput = next;
106
+ else opts.reportOutput = next;
95
107
  } else if (!a.startsWith('-') && opts.command && !opts.target) {
96
108
  opts.target = a;
97
109
  }
@@ -276,6 +288,26 @@ async function cmdScan(opts) {
276
288
 
277
289
  printResults(results, elapsed, opts);
278
290
 
291
+ if (opts.report) {
292
+ const { generateDashboard } = require('../src/Dashboard.js');
293
+ const outPath = opts.reportOutput || 'pompelmi-report.html';
294
+ generateDashboard(results, {
295
+ elapsed,
296
+ host: opts.host,
297
+ port: opts.port,
298
+ socket: opts.socket,
299
+ outputPath: outPath,
300
+ });
301
+ if (!opts.quiet) console.log(`\n Report saved: ${outPath}`);
302
+ }
303
+
304
+ if (opts.shareCard) {
305
+ const { generateShareCard } = require('../src/ShareCard.js');
306
+ const outPath = opts.shareCardOutput || 'pompelmi-scan-card.svg';
307
+ generateShareCard(results, { outputPath: outPath });
308
+ if (!opts.quiet) console.log(` Share card saved: ${outPath}`);
309
+ }
310
+
279
311
  const hasInfected = results.some(r => r.verdict === 'infected');
280
312
  const hasError = results.some(r => r.verdict === 'error');
281
313
  const clamdDown = results.some(r => r._clamdUnreachable);
@@ -361,6 +393,10 @@ SCAN OPTIONS
361
393
  --json Output results as JSON (no logo, no colors)
362
394
  --quiet, -q Only print infected files and summary
363
395
  --delete Delete infected files after confirmation
396
+ --report Generate an HTML security dashboard report
397
+ --share-card Generate a shareable SVG scan result card
398
+ --output <file> Output path for --report (default: pompelmi-report.html)
399
+ or --share-card (default: pompelmi-scan-card.svg)
364
400
 
365
401
  WATCH OPTIONS
366
402
  --host, --port, --socket, --timeout (same as scan)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pompelmi",
3
- "version": "1.12.1",
3
+ "version": "1.14.0",
4
4
  "description": "ClamAV for humans — scan any file and get back Clean, Malicious, or ScanError. No daemons. No cloud. No native bindings.",
5
5
  "license": "ISC",
6
6
  "author": "pompelmi contributors",
@@ -37,7 +37,7 @@
37
37
  "pompelmi": "./bin/pompelmi.js"
38
38
  },
39
39
  "scripts": {
40
- "test": "node --test test/unit.test.js && node test/scan.test.js",
40
+ "test": "node --test test/unit.test.js && node --test packages/nestjs/test/index.test.js && node --test packages/fastify/test/index.test.js && node --test packages/nextjs/test/index.test.js && node test/scan.test.js",
41
41
  "lint": "eslint src/"
42
42
  },
43
43
  "publishConfig": {
@@ -0,0 +1,115 @@
1
+ # @pompelmi/fastify
2
+
3
+ Fastify plugin for [pompelmi](https://pompelmi.app) — in-process ClamAV virus scanning with zero extra dependencies.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @pompelmi/fastify pompelmi
9
+ ```
10
+
11
+ ## Plugin Setup
12
+
13
+ ```js
14
+ const fastify = require('fastify')();
15
+ const pompelmi = require('@pompelmi/fastify');
16
+
17
+ await fastify.register(pompelmi, {
18
+ host: 'localhost',
19
+ port: 3310,
20
+ timeout: 5000,
21
+ });
22
+ ```
23
+
24
+ ### UNIX socket
25
+
26
+ ```js
27
+ await fastify.register(pompelmi, {
28
+ socket: '/run/clamav/clamd.sock',
29
+ });
30
+ ```
31
+
32
+ ## Decorated Instance
33
+
34
+ After registration, `fastify.pompelmi` is available everywhere:
35
+
36
+ ```js
37
+ // Scan a file path
38
+ const result = await fastify.pompelmi.scan('/uploads/report.pdf');
39
+
40
+ // Scan an in-memory Buffer
41
+ const result = await fastify.pompelmi.scanBuffer(file.buffer);
42
+
43
+ // Scan a Readable stream
44
+ const result = await fastify.pompelmi.scanStream(readableStream);
45
+
46
+ // Compare against Verdict symbols
47
+ const { Verdict } = require('pompelmi');
48
+ if (result === Verdict.Malicious) { /* ... */ }
49
+ ```
50
+
51
+ ## preHandler Hook
52
+
53
+ Use the built-in `preHandler` helper to scan uploaded files before your route handler runs:
54
+
55
+ ```js
56
+ const multipart = require('@fastify/multipart');
57
+ await fastify.register(multipart);
58
+
59
+ fastify.post('/upload', {
60
+ preHandler: fastify.pompelmi.preHandler({ field: 'file' }),
61
+ }, async (request, reply) => {
62
+ return { message: 'File is clean' };
63
+ });
64
+ ```
65
+
66
+ When a malicious file is detected the preHandler automatically responds with HTTP 400:
67
+
68
+ ```json
69
+ { "error": "Malicious file detected" }
70
+ ```
71
+
72
+ ### Custom malicious handler
73
+
74
+ ```js
75
+ fastify.post('/upload', {
76
+ preHandler: fastify.pompelmi.preHandler({
77
+ field: 'file',
78
+ onMalicious: async (request, reply) => {
79
+ request.log.warn({ ip: request.ip }, 'malicious upload blocked');
80
+ reply.code(422).send({ error: 'File rejected by security scan' });
81
+ },
82
+ }),
83
+ }, handler);
84
+ ```
85
+
86
+ ## Configuration Reference
87
+
88
+ All options are forwarded to pompelmi's `ScanOptions`:
89
+
90
+ | Option | Type | Default | Description |
91
+ |--------|------|---------|-------------|
92
+ | `host` | `string` | — | clamd hostname (enables TCP mode) |
93
+ | `port` | `number` | `3310` | clamd port |
94
+ | `socket` | `string` | — | UNIX domain socket path |
95
+ | `timeout` | `number` | `15000` | Socket idle timeout in ms |
96
+ | `retries` | `number` | `0` | Number of retry attempts |
97
+ | `retryDelay` | `number` | `1000` | Delay between retries in ms |
98
+
99
+ ## TypeScript
100
+
101
+ ```ts
102
+ import fastify from 'fastify';
103
+ import pompelmi from '@pompelmi/fastify';
104
+ import { Verdict } from 'pompelmi';
105
+
106
+ const app = fastify();
107
+ await app.register(pompelmi, { host: 'localhost', port: 3310 });
108
+
109
+ const result = await app.pompelmi.scanBuffer(buffer);
110
+ if (result === Verdict.Malicious) throw new Error('Infected');
111
+ ```
112
+
113
+ ## License
114
+
115
+ ISC — see root [LICENSE](../../LICENSE).
@@ -0,0 +1,27 @@
1
+ import { FastifyPluginCallback } from 'fastify';
2
+ import { ScanOptions, VerdictValue } from 'pompelmi';
3
+ import { Readable } from 'stream';
4
+
5
+ export interface PompelmiPreHandlerOptions {
6
+ /** Multer/Busboy field name to look for the uploaded file (default: 'file') */
7
+ field?: string;
8
+ /** Custom handler called instead of the default 400 response on malicious files */
9
+ onMalicious?: (request: any, reply: any) => void | Promise<void>;
10
+ }
11
+
12
+ export interface PompelmiDecorator {
13
+ Verdict: { readonly Clean: unique symbol; readonly Malicious: unique symbol; readonly ScanError: unique symbol };
14
+ scan(filePath: string): Promise<VerdictValue>;
15
+ scanBuffer(buffer: Buffer): Promise<VerdictValue>;
16
+ scanStream(stream: Readable): Promise<VerdictValue>;
17
+ preHandler(opts?: PompelmiPreHandlerOptions): (request: any, reply: any) => Promise<void>;
18
+ }
19
+
20
+ declare module 'fastify' {
21
+ interface FastifyInstance {
22
+ pompelmi: PompelmiDecorator;
23
+ }
24
+ }
25
+
26
+ declare const pompelmiPlugin: FastifyPluginCallback<ScanOptions>;
27
+ export = pompelmiPlugin;
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const { scan, scanBuffer, scanStream, Verdict } = require('pompelmi');
4
+
5
+ function buildScanOptions(options) {
6
+ const keys = ['host', 'port', 'socket', 'timeout', 'retries', 'retryDelay'];
7
+ const out = {};
8
+ for (const k of keys) {
9
+ if (options[k] !== undefined) out[k] = options[k];
10
+ }
11
+ return out;
12
+ }
13
+
14
+ function pompelmiPlugin(fastify, options, done) {
15
+ const scanOptions = buildScanOptions(options || {});
16
+
17
+ const pompelmi = {
18
+ Verdict,
19
+
20
+ scan(filePath) {
21
+ return scan(filePath, scanOptions);
22
+ },
23
+
24
+ scanBuffer(buffer) {
25
+ return scanBuffer(buffer, scanOptions);
26
+ },
27
+
28
+ scanStream(stream) {
29
+ return scanStream(stream, scanOptions);
30
+ },
31
+
32
+ preHandler(opts) {
33
+ const { field = 'file', onMalicious } = opts || {};
34
+ return async function pompelmiPreHandler(request, reply) {
35
+ const body = request.body;
36
+ if (!body) return;
37
+ const raw = body[field];
38
+ if (!raw) return;
39
+ const buffer = Buffer.isBuffer(raw) ? raw : (raw._buf || raw.data || null);
40
+ if (!buffer) return;
41
+ const result = await scanBuffer(buffer, scanOptions);
42
+ if (result === Verdict.Malicious) {
43
+ if (typeof onMalicious === 'function') return onMalicious(request, reply);
44
+ return reply.code(400).send({ error: 'Malicious file detected' });
45
+ }
46
+ };
47
+ },
48
+ };
49
+
50
+ fastify.decorate('pompelmi', pompelmi);
51
+ done();
52
+ }
53
+
54
+ pompelmiPlugin[Symbol.for('skip-override')] = true;
55
+
56
+ module.exports = pompelmiPlugin;
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@pompelmi/fastify",
3
+ "version": "1.0.0",
4
+ "description": "Fastify plugin for pompelmi — in-process ClamAV virus scanning with zero extra dependencies",
5
+ "license": "ISC",
6
+ "author": "pompelmi contributors",
7
+ "homepage": "https://pompelmi.app",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/pompelmi/pompelmi.git",
11
+ "directory": "packages/fastify"
12
+ },
13
+ "keywords": [
14
+ "fastify",
15
+ "fastify-plugin",
16
+ "clamav",
17
+ "antivirus",
18
+ "virus-scan",
19
+ "malware",
20
+ "file-upload",
21
+ "security",
22
+ "pompelmi"
23
+ ],
24
+ "main": "./index.js",
25
+ "types": "./index.d.ts",
26
+ "scripts": {
27
+ "test": "node --test test/index.test.js"
28
+ },
29
+ "peerDependencies": {
30
+ "fastify": ">=4",
31
+ "pompelmi": ">=1.12.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "fastify": { "optional": false }
35
+ },
36
+ "publishConfig": {
37
+ "registry": "https://registry.npmjs.org/",
38
+ "access": "public"
39
+ }
40
+ }
@@ -0,0 +1,150 @@
1
+ # @pompelmi/nestjs
2
+
3
+ NestJS module for [pompelmi](https://pompelmi.app) — in-process ClamAV virus scanning with zero extra dependencies.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @pompelmi/nestjs pompelmi
9
+ ```
10
+
11
+ Peer dependencies (install if not already present):
12
+
13
+ ```bash
14
+ npm install @nestjs/common @nestjs/core
15
+ ```
16
+
17
+ ## Module Setup
18
+
19
+ Register the module globally in your root `AppModule`:
20
+
21
+ ```ts
22
+ import { Module } from '@nestjs/common';
23
+ import { PompelmiModule } from '@pompelmi/nestjs';
24
+
25
+ @Module({
26
+ imports: [
27
+ PompelmiModule.forRoot({
28
+ host: 'localhost',
29
+ port: 3310,
30
+ timeout: 5000,
31
+ }),
32
+ ],
33
+ })
34
+ export class AppModule {}
35
+ ```
36
+
37
+ ### Async configuration (`forRootAsync`)
38
+
39
+ ```ts
40
+ import { ConfigModule, ConfigService } from '@nestjs/config';
41
+ import { PompelmiModule } from '@pompelmi/nestjs';
42
+
43
+ PompelmiModule.forRootAsync({
44
+ imports: [ConfigModule],
45
+ useFactory: (config: ConfigService) => ({
46
+ host: config.get('CLAMAV_HOST'),
47
+ port: config.get<number>('CLAMAV_PORT'),
48
+ }),
49
+ inject: [ConfigService],
50
+ })
51
+ ```
52
+
53
+ ## Service Usage
54
+
55
+ Inject `PompelmiService` into any provider:
56
+
57
+ ```ts
58
+ import { Injectable } from '@nestjs/common';
59
+ import { PompelmiService } from '@pompelmi/nestjs';
60
+ import { Verdict } from 'pompelmi';
61
+
62
+ @Injectable()
63
+ export class UploadService {
64
+ constructor(private readonly pompelmi: PompelmiService) {}
65
+
66
+ async processUpload(buffer: Buffer) {
67
+ const result = await this.pompelmi.scanBuffer(buffer);
68
+ if (result === Verdict.Malicious) throw new Error('Malicious file');
69
+
70
+ // Or use the convenience helper:
71
+ const isMalware = await this.pompelmi.isMalware(buffer);
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Service API
77
+
78
+ | Method | Signature | Description |
79
+ |--------|-----------|-------------|
80
+ | `scan` | `scan(filePath: string): Promise<VerdictValue>` | Scan a file by path |
81
+ | `scanBuffer` | `scanBuffer(buffer: Buffer): Promise<VerdictValue>` | Scan an in-memory Buffer |
82
+ | `scanStream` | `scanStream(stream: Readable): Promise<VerdictValue>` | Scan a Readable stream |
83
+ | `isMalware` | `isMalware(buffer: Buffer): Promise<boolean>` | Returns `true` when infected |
84
+
85
+ ## Guard Usage
86
+
87
+ `PompelmiGuard` implements `CanActivate`. It reads `req.file` (or `req.files[0]`) set by Multer and blocks the request (`canActivate → false`) when the file is malicious.
88
+
89
+ ```ts
90
+ import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
91
+ import { FileInterceptor } from '@nestjs/platform-express';
92
+ import { PompelmiGuard } from '@pompelmi/nestjs';
93
+
94
+ @Controller('upload')
95
+ export class UploadController {
96
+ @Post()
97
+ @UseGuards(PompelmiGuard)
98
+ @UseInterceptors(FileInterceptor('file'))
99
+ uploadFile(@UploadedFile() file: Express.Multer.File) {
100
+ return { message: 'File is clean', size: file.size };
101
+ }
102
+ }
103
+ ```
104
+
105
+ Register `PompelmiGuard` as a provider in your module so NestJS can inject `PompelmiService`:
106
+
107
+ ```ts
108
+ @Module({
109
+ imports: [PompelmiModule.forRoot({ host: 'localhost', port: 3310 })],
110
+ providers: [PompelmiGuard],
111
+ controllers: [UploadController],
112
+ })
113
+ export class UploadModule {}
114
+ ```
115
+
116
+ ## Interceptor Usage
117
+
118
+ `PompelmiInterceptor` is an alternative to the guard. It throws `BadRequestException` when a malicious file is detected, which NestJS maps to an HTTP 400 response.
119
+
120
+ ```ts
121
+ import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
122
+ import { FileInterceptor } from '@nestjs/platform-express';
123
+ import { PompelmiInterceptor } from '@pompelmi/nestjs';
124
+
125
+ @Controller('upload')
126
+ export class UploadController {
127
+ @Post()
128
+ @UseInterceptors(FileInterceptor('file'), PompelmiInterceptor)
129
+ uploadFile(@UploadedFile() file: Express.Multer.File) {
130
+ return { message: 'File is clean' };
131
+ }
132
+ }
133
+ ```
134
+
135
+ ## Configuration Reference
136
+
137
+ All options are forwarded to pompelmi's `ScanOptions`:
138
+
139
+ | Option | Type | Default | Description |
140
+ |--------|------|---------|-------------|
141
+ | `host` | `string` | — | clamd hostname (enables TCP mode) |
142
+ | `port` | `number` | `3310` | clamd port |
143
+ | `socket` | `string` | — | UNIX domain socket path |
144
+ | `timeout` | `number` | `15000` | Socket idle timeout in ms |
145
+ | `retries` | `number` | `0` | Number of retry attempts |
146
+ | `retryDelay` | `number` | `1000` | Delay between retries in ms |
147
+
148
+ ## License
149
+
150
+ ISC — see root [LICENSE](../../LICENSE).
@@ -0,0 +1,37 @@
1
+ import { DynamicModule, CanActivate, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
+ import { ScanOptions, VerdictValue } from 'pompelmi';
3
+ import { Observable } from 'rxjs';
4
+
5
+ export declare const POMPELMI_OPTIONS = 'POMPELMI_OPTIONS';
6
+
7
+ export interface PompelmiModuleOptions extends ScanOptions {}
8
+
9
+ export interface PompelmiModuleAsyncOptions {
10
+ imports?: any[];
11
+ useFactory: (...args: any[]) => PompelmiModuleOptions | Promise<PompelmiModuleOptions>;
12
+ inject?: any[];
13
+ }
14
+
15
+ export declare class PompelmiModule {
16
+ static forRoot(options?: PompelmiModuleOptions): DynamicModule;
17
+ static forRootAsync(options: PompelmiModuleAsyncOptions): DynamicModule;
18
+ }
19
+
20
+ export declare class PompelmiService {
21
+ readonly options: PompelmiModuleOptions;
22
+ constructor(options?: PompelmiModuleOptions);
23
+ scan(filePath: string): Promise<VerdictValue>;
24
+ scanBuffer(buffer: Buffer): Promise<VerdictValue>;
25
+ scanStream(stream: import('stream').Readable): Promise<VerdictValue>;
26
+ isMalware(buffer: Buffer): Promise<boolean>;
27
+ }
28
+
29
+ export declare class PompelmiGuard implements CanActivate {
30
+ constructor(pompelmiService: PompelmiService);
31
+ canActivate(context: ExecutionContext): Promise<boolean>;
32
+ }
33
+
34
+ export declare class PompelmiInterceptor implements NestInterceptor {
35
+ constructor(pompelmiService: PompelmiService);
36
+ intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>>;
37
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@pompelmi/nestjs",
3
+ "version": "1.0.0",
4
+ "description": "NestJS module for pompelmi — in-process virus scanning with zero extra dependencies",
5
+ "license": "ISC",
6
+ "author": "pompelmi contributors",
7
+ "homepage": "https://pompelmi.app",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/pompelmi/pompelmi.git",
11
+ "directory": "packages/nestjs"
12
+ },
13
+ "keywords": [
14
+ "nestjs",
15
+ "nestjs-module",
16
+ "clamav",
17
+ "antivirus",
18
+ "virus-scan",
19
+ "malware",
20
+ "file-upload",
21
+ "security",
22
+ "pompelmi"
23
+ ],
24
+ "main": "./src/index.js",
25
+ "types": "./index.d.ts",
26
+ "scripts": {
27
+ "test": "node --test test/index.test.js"
28
+ },
29
+ "peerDependencies": {
30
+ "@nestjs/common": ">=9",
31
+ "@nestjs/core": ">=9",
32
+ "pompelmi": ">=1.12.0"
33
+ },
34
+ "publishConfig": {
35
+ "registry": "https://registry.npmjs.org/",
36
+ "access": "public"
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const { PompelmiModule, POMPELMI_OPTIONS } = require('./pompelmi.module');
4
+ const { PompelmiService } = require('./pompelmi.service');
5
+ const { PompelmiGuard } = require('./pompelmi.guard');
6
+ const { PompelmiInterceptor } = require('./pompelmi.interceptor');
7
+
8
+ module.exports = { PompelmiModule, PompelmiService, PompelmiGuard, PompelmiInterceptor, POMPELMI_OPTIONS };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const { Verdict } = require('pompelmi');
4
+
5
+ class PompelmiGuard {
6
+ constructor(pompelmiService) {
7
+ this.pompelmiService = pompelmiService;
8
+ }
9
+
10
+ async canActivate(context) {
11
+ const request = context.switchToHttp().getRequest();
12
+ const file = request.file || (Array.isArray(request.files) && request.files[0]);
13
+ if (!file) return true;
14
+ const result = await this.pompelmiService.scanBuffer(file.buffer);
15
+ return result !== Verdict.Malicious;
16
+ }
17
+ }
18
+
19
+ module.exports = { PompelmiGuard };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { BadRequestException } = require('@nestjs/common');
4
+ const { Verdict } = require('pompelmi');
5
+
6
+ class PompelmiInterceptor {
7
+ constructor(pompelmiService) {
8
+ this.pompelmiService = pompelmiService;
9
+ }
10
+
11
+ async intercept(context, next) {
12
+ const request = context.switchToHttp().getRequest();
13
+ const file = request.file || (Array.isArray(request.files) && request.files[0]);
14
+ if (file) {
15
+ const result = await this.pompelmiService.scanBuffer(file.buffer);
16
+ if (result === Verdict.Malicious) {
17
+ throw new BadRequestException('Malicious file detected');
18
+ }
19
+ }
20
+ return next.handle();
21
+ }
22
+ }
23
+
24
+ module.exports = { PompelmiInterceptor };