pompelmi 1.4.0 → 1.5.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 +67 -5
- package/package.json +4 -1
- package/src/ClamAVScanner.js +38 -1
- package/src/index.js +3 -3
package/README.md
CHANGED
|
@@ -7,16 +7,33 @@
|
|
|
7
7
|
<p align="center"><strong>ClamAV antivirus scanning for Node.js — clean, typed, zero dependencies.</strong></p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
<a href="https://github.com/pompelmi/pompelmi"><img src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social" alt="GitHub stars"></a>
|
|
13
|
-
<img src="https://img.shields.io/badge/docker-available-blue?logo=docker" alt="Docker available">
|
|
10
|
+
<img src="https://img.shields.io/npm/v/pompelmi.svg" alt="npm version">
|
|
11
|
+
<img src="https://img.shields.io/npm/dw/pompelmi" alt="npm downloads">
|
|
14
12
|
<img src="https://img.shields.io/badge/license-ISC-blue.svg" alt="license">
|
|
15
|
-
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="
|
|
13
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="dependencies">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
16
17
|
<img src="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml/badge.svg" alt="Node.js CI">
|
|
17
18
|
<img src="https://github.com/pompelmi/pompelmi/actions/workflows/release.yml/badge.svg" alt="npm publish">
|
|
18
19
|
</p>
|
|
19
20
|
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="https://img.shields.io/badge/express-available-black?logo=express" alt="Express">
|
|
23
|
+
<img src="https://img.shields.io/badge/fastify-available-black?logo=fastify" alt="Fastify">
|
|
24
|
+
<img src="https://img.shields.io/badge/nestjs-available-red?logo=nestjs" alt="NestJS">
|
|
25
|
+
<img src="https://img.shields.io/badge/koa-available-lightgrey?logo=node.js" alt="Koa">
|
|
26
|
+
<img src="https://img.shields.io/badge/hono-available-orange" alt="Hono">
|
|
27
|
+
<img src="https://img.shields.io/badge/next.js-available-black?logo=next.js" alt="Next.js">
|
|
28
|
+
<img src="https://img.shields.io/badge/sveltekit-available-red?logo=svelte" alt="SvelteKit">
|
|
29
|
+
<img src="https://img.shields.io/badge/remix-available-black?logo=remix" alt="Remix">
|
|
30
|
+
<img src="https://img.shields.io/badge/docker-available-blue?logo=docker" alt="Docker">
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social" alt="GitHub stars">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
20
37
|
---
|
|
21
38
|
|
|
22
39
|
## Overview
|
|
@@ -45,6 +62,7 @@ Most integrations require parsing ClamAV's stdout with regex, managing a clamd d
|
|
|
45
62
|
- Single `scan(filePath, [options])` function — works locally or against a remote clamd instance
|
|
46
63
|
- `scanBuffer(buffer, [options])` — scan in-memory Buffers directly, no temp file required in TCP mode
|
|
47
64
|
- `scanStream(stream, [options])` — scan a Readable stream directly. In TCP mode, streamed to clamd with no disk I/O.
|
|
65
|
+
- `scanDirectory(dirPath, [options])` — recursively scan every file in a directory, returns clean/malicious/errors arrays
|
|
48
66
|
- Symbol-based verdicts (`Verdict.Clean` / `Verdict.Malicious` / `Verdict.ScanError`) — typo-proof comparisons
|
|
49
67
|
- Full TCP/clamd support via the INSTREAM protocol with configurable host, port, and timeout
|
|
50
68
|
- Built-in helpers to install ClamAV and update virus definitions programmatically
|
|
@@ -216,6 +234,22 @@ const files = ['/uploads/a.pdf', '/uploads/b.zip', '/uploads/c.png'];
|
|
|
216
234
|
const results = await Promise.all(files.map((f) => scan(f)));
|
|
217
235
|
```
|
|
218
236
|
|
|
237
|
+
### Scan a Directory
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const fs = require('fs');
|
|
241
|
+
const { scanDirectory } = require('pompelmi');
|
|
242
|
+
|
|
243
|
+
const results = await scanDirectory('/uploads');
|
|
244
|
+
|
|
245
|
+
console.log('Clean:', results.clean);
|
|
246
|
+
console.log('Malicious:', results.malicious);
|
|
247
|
+
console.log('Errors:', results.errors);
|
|
248
|
+
|
|
249
|
+
// Delete all malicious files
|
|
250
|
+
results.malicious.forEach(f => fs.unlinkSync(f));
|
|
251
|
+
```
|
|
252
|
+
|
|
219
253
|
### Scan a Buffer
|
|
220
254
|
|
|
221
255
|
```js
|
|
@@ -368,6 +402,34 @@ In TCP mode (`host` or `port` provided), the stream is piped directly to clamd v
|
|
|
368
402
|
|
|
369
403
|
---
|
|
370
404
|
|
|
405
|
+
### `scanDirectory(dirPath, [options])`
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
scanDirectory(
|
|
409
|
+
dirPath: string,
|
|
410
|
+
options?: { host?: string; port?: number; timeout?: number }
|
|
411
|
+
): Promise<{ clean: string[], malicious: string[], errors: string[] }>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Recursively scans every file in `dirPath` and returns three arrays of absolute paths.
|
|
415
|
+
|
|
416
|
+
| Field | Type | Description |
|
|
417
|
+
|---|---|---|
|
|
418
|
+
| `clean` | `string[]` | Paths of files with no threats found |
|
|
419
|
+
| `malicious` | `string[]` | Paths of files with a matched signature |
|
|
420
|
+
| `errors` | `string[]` | Paths of files that could not be scanned |
|
|
421
|
+
|
|
422
|
+
Per-file scan failures are caught and collected into `errors` — the function never throws because of an individual file.
|
|
423
|
+
|
|
424
|
+
**Rejects** with an `Error` in these cases:
|
|
425
|
+
|
|
426
|
+
| Condition | Error message |
|
|
427
|
+
|---|---|
|
|
428
|
+
| `dirPath` is not a string | `dirPath must be a string` |
|
|
429
|
+
| Directory does not exist | `Directory not found: <path>` |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
371
433
|
### `ClamAVInstaller()` _(internal)_
|
|
372
434
|
|
|
373
435
|
Installs ClamAV using the platform's native package manager. Resolves immediately if ClamAV is already installed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pompelmi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"test": "node --test test/unit.test.js && node test/scan.test.js",
|
|
37
37
|
"lint": "eslint src/"
|
|
38
38
|
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
},
|
|
39
42
|
"dependencies": {},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@eslint/js": "^10.0.1",
|
package/src/ClamAVScanner.js
CHANGED
|
@@ -4,6 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { Readable } = require('stream');
|
|
6
6
|
const { SCAN_RESULTS } = require('./config.js');
|
|
7
|
+
const { Verdict } = require('./verdicts.js');
|
|
7
8
|
const { scanViaClamd } = require('./ClamdScanner.js');
|
|
8
9
|
const { scanBufferViaClamd } = require('./BufferScanner.js');
|
|
9
10
|
const { scanStreamViaClamd } = require('./StreamScanner.js');
|
|
@@ -100,4 +101,40 @@ async function scanStream(stream, options = {}) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
async function scanDirectory(dirPath, options = {}) {
|
|
105
|
+
if (typeof dirPath !== 'string') {
|
|
106
|
+
throw new Error('dirPath must be a string');
|
|
107
|
+
}
|
|
108
|
+
if (!fs.existsSync(dirPath)) {
|
|
109
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const entries = fs.readdirSync(dirPath, { recursive: true });
|
|
113
|
+
const files = entries
|
|
114
|
+
.map(entry => path.join(dirPath, entry))
|
|
115
|
+
.filter(fullPath => fs.statSync(fullPath).isFile());
|
|
116
|
+
|
|
117
|
+
const results = await Promise.all(
|
|
118
|
+
files.map(async (filePath) => {
|
|
119
|
+
try {
|
|
120
|
+
return { filePath, verdict: await scan(filePath, options) };
|
|
121
|
+
} catch {
|
|
122
|
+
return { filePath, verdict: null };
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const clean = [];
|
|
128
|
+
const malicious = [];
|
|
129
|
+
const errors = [];
|
|
130
|
+
|
|
131
|
+
for (const { filePath, verdict } of results) {
|
|
132
|
+
if (verdict === Verdict.Clean) clean.push(filePath);
|
|
133
|
+
else if (verdict === Verdict.Malicious) malicious.push(filePath);
|
|
134
|
+
else errors.push(filePath);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { clean, malicious, errors };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { scan, scanBuffer, scanStream, scanDirectory };
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { scan, scanBuffer, scanStream } = require('./ClamAVScanner.js');
|
|
2
|
-
const { Verdict }
|
|
1
|
+
const { scan, scanBuffer, scanStream, scanDirectory } = require('./ClamAVScanner.js');
|
|
2
|
+
const { Verdict } = require('./verdicts.js');
|
|
3
3
|
|
|
4
|
-
module.exports = { scan, scanBuffer, scanStream, Verdict };
|
|
4
|
+
module.exports = { scan, scanBuffer, scanStream, scanDirectory, Verdict };
|