pompelmi 0.35.5 → 1.0.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/.claude/settings.local.json +40 -0
- package/LICENSE +12 -18
- package/README.md +159 -183
- package/eslint.config.mjs +8 -0
- package/package.json +26 -251
- package/src/ClamAVDatabaseUpdater.js +48 -0
- package/src/ClamAVInstaller.js +49 -0
- package/src/ClamAVScanner.js +31 -0
- package/src/InstallerCommand.js +11 -0
- package/src/config.js +22 -0
- package/src/constants.js +3 -0
- package/src/favicon.ico +0 -0
- package/src/grapefruit.png +0 -0
- package/src/index.js +5 -0
- package/CHANGELOG.md +0 -71
- package/dist/pompelmi.audit.cjs +0 -128
- package/dist/pompelmi.audit.cjs.map +0 -1
- package/dist/pompelmi.audit.esm.js +0 -107
- package/dist/pompelmi.audit.esm.js.map +0 -1
- package/dist/pompelmi.browser.cjs +0 -1549
- package/dist/pompelmi.browser.cjs.map +0 -1
- package/dist/pompelmi.browser.esm.js +0 -1523
- package/dist/pompelmi.browser.esm.js.map +0 -1
- package/dist/pompelmi.cjs +0 -2591
- package/dist/pompelmi.cjs.map +0 -1
- package/dist/pompelmi.esm.js +0 -2525
- package/dist/pompelmi.esm.js.map +0 -1
- package/dist/pompelmi.hooks.cjs +0 -75
- package/dist/pompelmi.hooks.cjs.map +0 -1
- package/dist/pompelmi.hooks.esm.js +0 -72
- package/dist/pompelmi.hooks.esm.js.map +0 -1
- package/dist/pompelmi.policy-packs.cjs +0 -240
- package/dist/pompelmi.policy-packs.cjs.map +0 -1
- package/dist/pompelmi.policy-packs.esm.js +0 -232
- package/dist/pompelmi.policy-packs.esm.js.map +0 -1
- package/dist/pompelmi.quarantine.cjs +0 -317
- package/dist/pompelmi.quarantine.cjs.map +0 -1
- package/dist/pompelmi.quarantine.esm.js +0 -293
- package/dist/pompelmi.quarantine.esm.js.map +0 -1
- package/dist/pompelmi.react.cjs +0 -1580
- package/dist/pompelmi.react.cjs.map +0 -1
- package/dist/pompelmi.react.esm.js +0 -1553
- package/dist/pompelmi.react.esm.js.map +0 -1
- package/dist/types/audit.d.ts +0 -84
- package/dist/types/browser-index.d.ts +0 -29
- package/dist/types/config.d.ts +0 -143
- package/dist/types/engines/dynamic-taint.d.ts +0 -102
- package/dist/types/engines/hybrid-orchestrator.d.ts +0 -65
- package/dist/types/engines/hybrid-taint-integration.d.ts +0 -129
- package/dist/types/engines/taint-policies.d.ts +0 -84
- package/dist/types/hipaa-compliance.d.ts +0 -110
- package/dist/types/hooks.d.ts +0 -89
- package/dist/types/index.d.ts +0 -29
- package/dist/types/magic.d.ts +0 -7
- package/dist/types/node/scanDir.d.ts +0 -30
- package/dist/types/policy-packs.d.ts +0 -98
- package/dist/types/policy.d.ts +0 -12
- package/dist/types/presets.d.ts +0 -72
- package/dist/types/quarantine/index.d.ts +0 -18
- package/dist/types/quarantine/storage.d.ts +0 -77
- package/dist/types/quarantine/types.d.ts +0 -78
- package/dist/types/quarantine/workflow.d.ts +0 -97
- package/dist/types/react-index.d.ts +0 -13
- package/dist/types/risk.d.ts +0 -18
- package/dist/types/scan/remote.d.ts +0 -12
- package/dist/types/scan.d.ts +0 -17
- package/dist/types/scanners/common-heuristics.d.ts +0 -14
- package/dist/types/scanners/zip-bomb-guard.d.ts +0 -9
- package/dist/types/scanners/zipTraversalGuard.d.ts +0 -19
- package/dist/types/src/audit.d.ts +0 -84
- package/dist/types/src/browser-index.d.ts +0 -29
- package/dist/types/src/config.d.ts +0 -143
- package/dist/types/src/engines/dynamic-taint.d.ts +0 -102
- package/dist/types/src/engines/hybrid-orchestrator.d.ts +0 -65
- package/dist/types/src/engines/hybrid-taint-integration.d.ts +0 -129
- package/dist/types/src/engines/taint-policies.d.ts +0 -84
- package/dist/types/src/hipaa-compliance.d.ts +0 -110
- package/dist/types/src/hooks.d.ts +0 -89
- package/dist/types/src/index.d.ts +0 -29
- package/dist/types/src/magic.d.ts +0 -7
- package/dist/types/src/node/scanDir.d.ts +0 -30
- package/dist/types/src/policy-packs.d.ts +0 -98
- package/dist/types/src/policy.d.ts +0 -12
- package/dist/types/src/presets.d.ts +0 -72
- package/dist/types/src/quarantine/index.d.ts +0 -18
- package/dist/types/src/quarantine/storage.d.ts +0 -77
- package/dist/types/src/quarantine/types.d.ts +0 -78
- package/dist/types/src/quarantine/workflow.d.ts +0 -97
- package/dist/types/src/react-index.d.ts +0 -13
- package/dist/types/src/risk.d.ts +0 -18
- package/dist/types/src/scan/remote.d.ts +0 -12
- package/dist/types/src/scan.d.ts +0 -17
- package/dist/types/src/scanners/common-heuristics.d.ts +0 -14
- package/dist/types/src/scanners/zip-bomb-guard.d.ts +0 -11
- package/dist/types/src/scanners/zipTraversalGuard.d.ts +0 -19
- package/dist/types/src/stream.d.ts +0 -10
- package/dist/types/src/types/decompilation.d.ts +0 -96
- package/dist/types/src/types/taint-tracking.d.ts +0 -495
- package/dist/types/src/types.d.ts +0 -48
- package/dist/types/src/useFileScanner.d.ts +0 -15
- package/dist/types/src/utils/advanced-detection.d.ts +0 -21
- package/dist/types/src/utils/batch-scanner.d.ts +0 -62
- package/dist/types/src/utils/cache-manager.d.ts +0 -95
- package/dist/types/src/utils/export.d.ts +0 -51
- package/dist/types/src/utils/performance-metrics.d.ts +0 -68
- package/dist/types/src/utils/threat-intelligence.d.ts +0 -96
- package/dist/types/src/validate.d.ts +0 -7
- package/dist/types/src/verdict.d.ts +0 -2
- package/dist/types/src/yara/browser.d.ts +0 -7
- package/dist/types/src/yara/index.d.ts +0 -17
- package/dist/types/src/yara/node.d.ts +0 -2
- package/dist/types/src/yara/remote.d.ts +0 -10
- package/dist/types/src/yara-bridge.d.ts +0 -3
- package/dist/types/src/zip.d.ts +0 -13
- package/dist/types/stream.d.ts +0 -10
- package/dist/types/types/decompilation.d.ts +0 -96
- package/dist/types/types/taint-tracking.d.ts +0 -495
- package/dist/types/types.d.ts +0 -48
- package/dist/types/useFileScanner.d.ts +0 -15
- package/dist/types/utils/advanced-detection.d.ts +0 -21
- package/dist/types/utils/batch-scanner.d.ts +0 -62
- package/dist/types/utils/cache-manager.d.ts +0 -95
- package/dist/types/utils/export.d.ts +0 -51
- package/dist/types/utils/performance-metrics.d.ts +0 -68
- package/dist/types/utils/threat-intelligence.d.ts +0 -96
- package/dist/types/validate.d.ts +0 -7
- package/dist/types/verdict.d.ts +0 -2
- package/dist/types/yara/browser.d.ts +0 -7
- package/dist/types/yara/index.d.ts +0 -17
- package/dist/types/yara/node.d.ts +0 -2
- package/dist/types/yara/remote.d.ts +0 -10
- package/dist/types/yara-bridge.d.ts +0 -3
- package/dist/types/zip.d.ts +0 -13
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(node:*)",
|
|
5
|
+
"Bash(echo \"EXIT:$?\")",
|
|
6
|
+
"Bash(echo \"EXIT_CODE:$?\")",
|
|
7
|
+
"Bash(tee /tmp/pompelmi_test_out.txt)",
|
|
8
|
+
"Bash(echo \"done: $?\")",
|
|
9
|
+
"Bash(ls /Users/tommy/pompelmi/pompelmi/*.md)",
|
|
10
|
+
"Bash(ls /Users/tommy/pompelmi/pompelmi/LICENSE*)",
|
|
11
|
+
"WebSearch",
|
|
12
|
+
"WebFetch(domain:pompelmi.app)",
|
|
13
|
+
"WebFetch(domain:news.ycombinator.com)",
|
|
14
|
+
"WebFetch(domain:dev.to)",
|
|
15
|
+
"WebFetch(domain:socket.dev)",
|
|
16
|
+
"WebFetch(domain:helpnetsecurity.com)",
|
|
17
|
+
"WebFetch(domain:nodeweekly.com)",
|
|
18
|
+
"WebFetch(domain:bytes.dev)",
|
|
19
|
+
"WebFetch(domain:www.helpnetsecurity.com)",
|
|
20
|
+
"WebFetch(domain:www.enveil.com)",
|
|
21
|
+
"WebFetch(domain:img.helpnetsecurity.com)",
|
|
22
|
+
"WebFetch(domain:logo.clearbit.com)",
|
|
23
|
+
"WebFetch(domain:cdn.brandfetch.io)",
|
|
24
|
+
"WebFetch(domain:cooperpress.com)",
|
|
25
|
+
"WebFetch(domain:wirexsystems.com)",
|
|
26
|
+
"WebFetch(domain:www.zlti.com)",
|
|
27
|
+
"Bash(gh run:*)",
|
|
28
|
+
"Bash(gh api:*)",
|
|
29
|
+
"WebFetch(domain:api.github.com)",
|
|
30
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
31
|
+
"Bash(git -C /Users/tommy/pompelmi/pompelmi status)",
|
|
32
|
+
"Bash(git -C /Users/tommy/pompelmi/pompelmi log --oneline -5)",
|
|
33
|
+
"Bash(git pull:*)",
|
|
34
|
+
"Bash(git add:*)",
|
|
35
|
+
"Bash(git commit -m ':*)",
|
|
36
|
+
"Bash(git push:*)",
|
|
37
|
+
"Bash(grep -E '\\\\.\\(png|svg|ico|webp\\)$')"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
ISC License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2024 pompelmi contributors
|
|
4
4
|
|
|
5
|
-
Permission
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,228 +1,204 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
> [!CAUTION]
|
|
5
|
-
> **v0.x is an experimental prototype. Do NOT use it in production.**
|
|
6
|
-
>
|
|
7
|
-
> Pompelmi v0.x has **known Event Loop blocking issues** and makes overreaching
|
|
8
|
-
> "scanner" claims that it cannot keep. This version is in **soft maintenance
|
|
9
|
-
> only** — no new features, security patches only.
|
|
10
|
-
>
|
|
11
|
-
> ---
|
|
12
|
-
>
|
|
13
|
-
> **Pompelmi is pivoting for v1.0.**
|
|
14
|
-
>
|
|
15
|
-
> The new identity is a **dead-simple, one-line utility wrapper** for Node.js
|
|
16
|
-
> file uploads — not an "ultimate malware scanner". v1.0 will bundle standard,
|
|
17
|
-
> well-understood checks (size limits, magic bytes, basic heuristics) into a
|
|
18
|
-
> single convenient call that returns a traffic-light verdict:
|
|
19
|
-
>
|
|
20
|
-
> ```js
|
|
21
|
-
> const result = await pompelmi.scan(file);
|
|
22
|
-
> // returns 'green', 'suspicious', or 'malicious'
|
|
23
|
-
> ```
|
|
24
|
-
>
|
|
25
|
-
> No magic promises. No impossible guarantees. Just saved developer time.
|
|
26
|
-
>
|
|
27
|
-
> Follow the pivot → [GitHub Issues](https://github.com/pompelmi/pompelmi/issues) · [Discussions](https://github.com/pompelmi/pompelmi/discussions)
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./src/grapefruit.png" width="96" alt="pompelmi logo">
|
|
3
|
+
</p>
|
|
28
4
|
|
|
29
|
-
|
|
5
|
+
<h1 align="center">pompelmi</h1>
|
|
30
6
|
|
|
31
|
-
<
|
|
32
|
-
<img src="./assets/logo.svg" alt="Pompelmi logo" width="120" />
|
|
33
|
-
|
|
34
|
-
<h1>Pompelmi</h1>
|
|
35
|
-
|
|
36
|
-
<p><strong>Secure file uploads in Node.js before storage.</strong></p>
|
|
37
|
-
|
|
38
|
-
<p>
|
|
39
|
-
Open-source route-level upload security for Node.js teams that need to
|
|
40
|
-
inspect untrusted files before disk, object storage, previews, or
|
|
41
|
-
downstream parsers.
|
|
42
|
-
</p>
|
|
43
|
-
|
|
44
|
-
<p><code>clean</code> · <code>suspicious</code> · <code>malicious</code></p>
|
|
45
|
-
|
|
46
|
-
<p>
|
|
47
|
-
MIME spoofing · risky archives · document and binary signals · optional
|
|
48
|
-
YARA
|
|
49
|
-
</p>
|
|
50
|
-
|
|
51
|
-
<p>
|
|
52
|
-
<sub>Express · Next.js · NestJS · Fastify · Koa · Nuxt/Nitro · S3 quarantine flows · CI/CD</sub>
|
|
53
|
-
</p>
|
|
54
|
-
|
|
55
|
-
<p>
|
|
56
|
-
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi" /></a>
|
|
57
|
-
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm total downloads" src="https://img.shields.io/npm/dt/pompelmi" /></a>
|
|
58
|
-
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci.yml?label=ci" /></a>
|
|
59
|
-
<a href="https://codecov.io/gh/pompelmi/pompelmi"><img alt="codecov" src="https://codecov.io/gh/pompelmi/pompelmi/graph/badge.svg" /></a>
|
|
60
|
-
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social" /></a>
|
|
61
|
-
<a href="https://github.com/pompelmi/pompelmi/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-green.svg" /></a>
|
|
62
|
-
<a href="https://nodejs.org"><img alt="Node.js 18+" src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" /></a>
|
|
63
|
-
</p>
|
|
64
|
-
|
|
65
|
-
> If Pompelmi fits how you think about upload security at the route
|
|
66
|
-
> level, starring the repo helps other Node.js teams find it.
|
|
67
|
-
> [★ Star on GitHub](https://github.com/pompelmi/pompelmi/stargazers)
|
|
68
|
-
|
|
69
|
-
<p>
|
|
70
|
-
<a href="https://pompelmi.app/"><strong>Docs</strong></a>
|
|
71
|
-
·
|
|
72
|
-
<a href="https://pompelmi.app/getting-started/"><strong>Getting started</strong></a>
|
|
73
|
-
·
|
|
74
|
-
<a href="https://pompelmi.app/#browser-preview"><strong>Browser preview</strong></a>
|
|
75
|
-
·
|
|
76
|
-
<a href="./examples/demo"><strong>Express demo</strong></a>
|
|
77
|
-
·
|
|
78
|
-
<a href="./examples/README.md"><strong>Examples</strong></a>
|
|
79
|
-
</p>
|
|
80
|
-
|
|
81
|
-
<p><sub>Node.js 18+ · MIT</sub></p>
|
|
82
|
-
</div>
|
|
7
|
+
<p align="center"><strong>ClamAV for humans</strong></p>
|
|
83
8
|
|
|
84
9
|
<p align="center">
|
|
85
|
-
<
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<a href="https://www.
|
|
89
|
-
<a href="https://github.com/sorrycc/awesome-javascript">Awesome JavaScript</a>,
|
|
90
|
-
<a href="https://github.com/dzharii/awesome-typescript">Awesome TypeScript</a>,
|
|
91
|
-
<a href="https://bytes.dev/archives/429">Bytes</a>,
|
|
92
|
-
and
|
|
93
|
-
<a href="https://www.detectionengineering.net/p/det-eng-weekly-124">Detection Engineering Weekly</a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/pompelmi"><img src="https://img.shields.io/npm/v/pompelmi.svg" alt="npm version"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/license-ISC-blue.svg" alt="license">
|
|
12
|
+
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey.svg" alt="platform">
|
|
13
|
+
<a href="https://www.npmjs.com/package/pompelmi?activeTab=dependencies"><img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="zero dependencies"></a>
|
|
94
14
|
</p>
|
|
95
15
|
|
|
96
|
-
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
A minimal Node.js wrapper around [ClamAV](https://www.clamav.net/) that scans any file and returns a plain string: `"Clean"`, `"Malicious"`, or `"ScanError"`. No daemons. No cloud. No native bindings.
|
|
19
|
+
|
|
20
|
+
## Table of contents
|
|
97
21
|
|
|
98
|
-
|
|
22
|
+
- [Quickstart](#quickstart)
|
|
23
|
+
- [How it works](#how-it-works)
|
|
24
|
+
- [API](#api)
|
|
25
|
+
- [pompelmi.scan()](#pompelmiscanfilepath)
|
|
26
|
+
- [Internal utilities](#internal-utilities)
|
|
27
|
+
- [ClamAVInstaller()](#clamavinstaller)
|
|
28
|
+
- [updateClamAVDatabase()](#updateclamavdatabase)
|
|
29
|
+
- [Supported platforms](#supported-platforms)
|
|
30
|
+
- [Installing ClamAV manually](#installing-clamav-manually)
|
|
31
|
+
- [Testing](#testing)
|
|
32
|
+
- [Contributing](#contributing)
|
|
33
|
+
- [Security](#security)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
---
|
|
99
37
|
|
|
100
|
-
|
|
38
|
+
## Quickstart
|
|
101
39
|
|
|
102
40
|
```bash
|
|
103
41
|
npm install pompelmi
|
|
104
42
|
```
|
|
105
43
|
|
|
106
|
-
|
|
44
|
+
```js
|
|
45
|
+
const pompelmi = require('pompelmi');
|
|
107
46
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
mimeType: req.file.mimetype,
|
|
114
|
-
policy: STRICT_PUBLIC_UPLOAD,
|
|
115
|
-
failClosed: true,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
if (report.verdict !== 'clean') {
|
|
119
|
-
return res.status(422).json({
|
|
120
|
-
error: 'Upload blocked',
|
|
121
|
-
verdict: report.verdict,
|
|
122
|
-
reasons: report.reasons,
|
|
123
|
-
});
|
|
47
|
+
const result = await pompelmi.scan('/path/to/file.zip');
|
|
48
|
+
// "Clean" | "Malicious" | "ScanError"
|
|
49
|
+
|
|
50
|
+
if (result === 'Malicious') {
|
|
51
|
+
throw new Error('File rejected: malware detected');
|
|
124
52
|
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## How it works
|
|
56
|
+
|
|
57
|
+
1. **Validate** — pompelmi checks that the argument is a string and that the file exists before spawning anything.
|
|
58
|
+
2. **Scan** — pompelmi spawns `clamscan --no-summary <filePath>` as a child process and reads the exit code.
|
|
59
|
+
3. **Map** — the exit code is mapped to a result string. Unknown codes and spawn errors reject the Promise.
|
|
60
|
+
|
|
61
|
+
No stdout parsing. No regex. No surprises.
|
|
62
|
+
|
|
63
|
+
## API
|
|
125
64
|
|
|
126
|
-
|
|
65
|
+
### `pompelmi.scan(filePath)`
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
pompelmi.scan(filePath: string): Promise<"Clean" | "Malicious" | "ScanError">
|
|
127
69
|
```
|
|
128
70
|
|
|
129
|
-
|
|
71
|
+
| Parameter | Type | Description |
|
|
72
|
+
|------------|----------|-----------------------------------------|
|
|
73
|
+
| `filePath` | `string` | Absolute or relative path to the file. |
|
|
74
|
+
|
|
75
|
+
**Resolves** to one of:
|
|
76
|
+
|
|
77
|
+
| Result | ClamAV exit code | Meaning |
|
|
78
|
+
|---------------|:---:|------------------------------------------------------------------------------------------------------|
|
|
79
|
+
| `"Clean"` | 0 | No threats found. |
|
|
80
|
+
| `"Malicious"` | 1 | A known virus or malware signature was matched. |
|
|
81
|
+
| `"ScanError"` | 2 | The scan itself failed (I/O error, encrypted archive, permission denied). File status is unknown — treat as untrusted. |
|
|
82
|
+
|
|
83
|
+
**Rejects** with an `Error` in these cases:
|
|
84
|
+
|
|
85
|
+
| Condition | Error message |
|
|
86
|
+
|-----------|---------------|
|
|
87
|
+
| `filePath` is not a string | `filePath must be a string` |
|
|
88
|
+
| File does not exist | `File not found: <path>` |
|
|
89
|
+
| `clamscan` is not in PATH | `ENOENT` (from the OS) |
|
|
90
|
+
| ClamAV returns an unknown exit code | `Unexpected exit code: N` |
|
|
91
|
+
| `clamscan` process is killed by a signal | `Process killed by signal: <SIGNAL>` |
|
|
92
|
+
|
|
93
|
+
**Example — full error handling:**
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const pompelmi = require('pompelmi');
|
|
97
|
+
const path = require('path');
|
|
98
|
+
|
|
99
|
+
async function safeScan(filePath) {
|
|
100
|
+
try {
|
|
101
|
+
const result = await pompelmi.scan(path.resolve(filePath));
|
|
102
|
+
|
|
103
|
+
if (result === 'ScanError') {
|
|
104
|
+
// The scan could not complete — treat the file as untrusted.
|
|
105
|
+
console.warn('Scan incomplete, rejecting file as precaution.');
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result; // "Clean" or "Malicious"
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error('Scan failed:', err.message);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
130
116
|
|
|
131
|
-
##
|
|
117
|
+
## Internal utilities
|
|
132
118
|
|
|
133
|
-
|
|
134
|
-
- Pompelmi keeps the first trust decision inside the application path, where the route still knows the file class, trust level, storage path, and failure mode.
|
|
135
|
-
- It gives Node.js teams a practical way to build secure file uploads with route-level decisions instead of bolting checks on after persistence.
|
|
119
|
+
These modules are not part of the public npm API but are used internally to set up the ClamAV environment on a fresh machine.
|
|
136
120
|
|
|
137
|
-
|
|
121
|
+
### `ClamAVInstaller()`
|
|
138
122
|
|
|
139
|
-
|
|
123
|
+
Installs ClamAV using the platform's native package manager. Skips silently if ClamAV is already installed.
|
|
140
124
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- Optional YARA-based matches when you want malware scanning uploads as part of the flow
|
|
145
|
-
- Verdicts and reasons you can use for fail-closed routes, quarantine flows, and auditability
|
|
125
|
+
```ts
|
|
126
|
+
ClamAVInstaller(): Promise<string>
|
|
127
|
+
```
|
|
146
128
|
|
|
147
|
-
|
|
129
|
+
- Resolves with a status message string on success or skip.
|
|
130
|
+
- Rejects if the install process exits with a non-zero code or if spawning the package manager fails.
|
|
148
131
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
132
|
+
| Platform | Package manager | Command |
|
|
133
|
+
|----------|----------------|---------|
|
|
134
|
+
| macOS | Homebrew | `brew install clamav` |
|
|
135
|
+
| Linux | apt-get | `sudo apt-get install -y clamav clamav-daemon` |
|
|
136
|
+
| Windows | Chocolatey | `choco install clamav -y` |
|
|
153
137
|
|
|
154
|
-
|
|
138
|
+
### `updateClamAVDatabase()`
|
|
155
139
|
|
|
156
|
-
|
|
140
|
+
Downloads or updates the ClamAV virus definition database by running `freshclam`. Skips if `main.cvd` is already present on disk.
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
| File-type or magic-byte validation only | Confirming a file looks like the claimed type | Archive abuse, risky internal structure, and route policy decisions |
|
|
162
|
-
| Antivirus or YARA only | Known malicious matches and signature-style detection | Route context, spoofing checks, and before-storage handling |
|
|
163
|
-
| Pompelmi at the upload route | Node.js file upload security, scan files before storage, and verdict-driven workflow decisions | It is not a full antivirus replacement on its own |
|
|
142
|
+
```ts
|
|
143
|
+
updateClamAVDatabase(): Promise<string>
|
|
144
|
+
```
|
|
164
145
|
|
|
165
|
-
|
|
146
|
+
- Resolves with a status message string on success or skip.
|
|
147
|
+
- Rejects if `freshclam` exits with a non-zero code or if spawning fails.
|
|
166
148
|
|
|
167
|
-
|
|
149
|
+
| Platform | Database path |
|
|
150
|
+
|----------|--------------|
|
|
151
|
+
| macOS | `/usr/local/share/clamav/main.cvd` |
|
|
152
|
+
| Linux | `/var/lib/clamav/main.cvd` |
|
|
153
|
+
| Windows | `C:\ProgramData\ClamAV\main.cvd` |
|
|
168
154
|
|
|
169
|
-
|
|
170
|
-
| --- | --- |
|
|
171
|
-
| Express | [Docs](https://pompelmi.app/how-to/express/) · [Minimal example](./examples/express-minimal) · [Demo](./examples/demo) |
|
|
172
|
-
| Next.js | [Docs](https://pompelmi.app/how-to/nextjs/) · [Example](./examples/next-app-router) · [Package](./packages/next-upload) |
|
|
173
|
-
| NestJS | [Docs](https://pompelmi.app/how-to/nestjs/) · [Package](./packages/nestjs) · [Example app](./examples/nestjs-app) |
|
|
174
|
-
| Fastify | [Docs](https://pompelmi.app/how-to/fastify/) · [Package](./packages/fastify-plugin) |
|
|
175
|
-
| Koa | [Docs](https://pompelmi.app/how-to/koa/) · [Package](./packages/koa-middleware) |
|
|
176
|
-
| Nuxt/Nitro | [Docs](https://pompelmi.app/how-to/nuxt-nitro/) · [Example](./examples/nuxt-nitro) |
|
|
177
|
-
| S3 / object storage | [Tutorial](https://pompelmi.app/tutorials/secure-s3-presigned-uploads-with-malware-scanning/) · [Use case](https://pompelmi.app/use-cases/object-storage-promotion-workflows/) |
|
|
178
|
-
| CI/CD | [Use case](https://pompelmi.app/use-cases/cicd-artifact-scanning/) · [Blog](https://pompelmi.app/blog/cicd-scan-build-artifacts/) |
|
|
155
|
+
## Supported platforms
|
|
179
156
|
|
|
180
|
-
|
|
157
|
+
| OS | ClamAV install | DB path checked |
|
|
158
|
+
|---------|---------------------------|------------------------------------|
|
|
159
|
+
| macOS | `brew install clamav` | `/usr/local/share/clamav/main.cvd` |
|
|
160
|
+
| Linux | `apt-get install clamav` | `/var/lib/clamav/main.cvd` |
|
|
161
|
+
| Windows | `choco install clamav -y` | `C:\ProgramData\ClamAV\main.cvd` |
|
|
181
162
|
|
|
182
|
-
|
|
163
|
+
ClamAV must be installed on the host system. pompelmi does not bundle or download it.
|
|
183
164
|
|
|
184
|
-
|
|
185
|
-
- [Express demo](./examples/demo) for a tiny upload gate that returns `clean`, `suspicious`, or `malicious` before storage
|
|
186
|
-
- [Examples index](./examples/README.md) for framework-specific and production-oriented patterns
|
|
187
|
-
- [Docs home](https://pompelmi.app/) for guides, comparisons, use cases, and tutorials
|
|
165
|
+
## Installing ClamAV manually
|
|
188
166
|
|
|
189
|
-
|
|
167
|
+
```bash
|
|
168
|
+
# macOS
|
|
169
|
+
brew install clamav && freshclam
|
|
170
|
+
|
|
171
|
+
# Linux (Debian / Ubuntu)
|
|
172
|
+
sudo apt-get install -y clamav clamav-daemon && sudo freshclam
|
|
173
|
+
|
|
174
|
+
# Windows (Chocolatey)
|
|
175
|
+
choco install clamav -y
|
|
176
|
+
```
|
|
190
177
|
|
|
191
|
-
|
|
178
|
+
## Testing
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm test
|
|
182
|
+
```
|
|
192
183
|
|
|
193
|
-
|
|
184
|
+
The test suite has two parts:
|
|
194
185
|
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
- [Pompelmi: Open-source secure file upload scanning for Node.js](https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/) — Help Net Security
|
|
198
|
-
- [Awesome JavaScript](https://github.com/sorrycc/awesome-javascript)
|
|
199
|
-
- [Awesome TypeScript](https://github.com/dzharii/awesome-typescript)
|
|
200
|
-
- [Bytes #429](https://bytes.dev/archives/429) — bytes.dev (2025-10-03)
|
|
201
|
-
- [Det. Eng. Weekly #124](https://www.detectionengineering.net/p/det-eng-weekly-124) — detectionengineering.net (2025-08-13)
|
|
202
|
-
- [The Overflow #319](https://stackoverflow.blog/2026/03/04/the-overflow-319/) — stackoverflow.blog (2026-03-04)
|
|
203
|
-
- [Hottest OSS tools Feb 2026](https://www.helpnetsecurity.com/2026/02/26/hottest-oss-tools-feb-2026/) — helpnetsecurity.com (2026-02-26)
|
|
204
|
-
- [See all mentions](https://pompelmi.app/featured-in/)
|
|
186
|
+
- **Unit tests** (`test/unit.test.js`) — run with Node's built-in test runner. Mock `cross-spawn` and platform dependencies; no ClamAV installation required.
|
|
187
|
+
- **Integration tests** (`test/scan.test.js`) — spawn real `clamscan` processes against EICAR test files. Skipped automatically if `clamscan` is not found in PATH.
|
|
205
188
|
|
|
206
|
-
|
|
189
|
+
## Contributing
|
|
207
190
|
|
|
208
|
-
|
|
191
|
+
1. Fork the repository at [https://github.com/pompelmi/pompelmi](https://github.com/pompelmi/pompelmi).
|
|
192
|
+
2. Create a feature branch: `git checkout -b feat/your-change`.
|
|
193
|
+
3. Make your changes and run `npm test` to verify.
|
|
194
|
+
4. Open a pull request against `main`.
|
|
209
195
|
|
|
210
|
-
|
|
211
|
-
- [Use cases](https://pompelmi.app/use-cases/)
|
|
212
|
-
- [Comparisons](https://pompelmi.app/comparisons/)
|
|
213
|
-
- [Tutorials](https://pompelmi.app/tutorials/)
|
|
214
|
-
- [Browser preview](https://pompelmi.app/#browser-preview)
|
|
215
|
-
- [Featured in](https://pompelmi.app/featured-in/)
|
|
216
|
-
- [Translations](https://pompelmi.app/translations/)
|
|
196
|
+
Please read [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before contributing.
|
|
217
197
|
|
|
218
|
-
##
|
|
198
|
+
## Security
|
|
219
199
|
|
|
220
|
-
|
|
200
|
+
To report a vulnerability, see [SECURITY.md](./SECURITY.md).
|
|
221
201
|
|
|
222
|
-
##
|
|
202
|
+
## License
|
|
223
203
|
|
|
224
|
-
|
|
225
|
-
- [Security](./SECURITY.md)
|
|
226
|
-
- [Roadmap](./ROADMAP.md)
|
|
227
|
-
- [GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)
|
|
228
|
-
- [License](./LICENSE)
|
|
204
|
+
[ISC](./LICENSE) — © pompelmi contributors
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import { defineConfig } from "eslint/config";
|
|
4
|
+
|
|
5
|
+
export default defineConfig([
|
|
6
|
+
{ files: ["**/*.{js,mjs,cjs}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.node } },
|
|
7
|
+
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
|
|
8
|
+
]);
|