pompelmi 0.35.5 → 1.1.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.
Files changed (135) hide show
  1. package/.claude/settings.local.json +45 -0
  2. package/LICENSE +12 -18
  3. package/README.md +174 -181
  4. package/eslint.config.mjs +8 -0
  5. package/package.json +26 -251
  6. package/src/ClamAVDatabaseUpdater.js +48 -0
  7. package/src/ClamAVInstaller.js +49 -0
  8. package/src/ClamAVScanner.js +37 -0
  9. package/src/ClamdScanner.js +81 -0
  10. package/src/InstallerCommand.js +11 -0
  11. package/src/config.js +22 -0
  12. package/src/constants.js +3 -0
  13. package/src/favicon.ico +0 -0
  14. package/src/grapefruit.png +0 -0
  15. package/src/index.js +5 -0
  16. package/test_out.txt +74 -0
  17. package/CHANGELOG.md +0 -71
  18. package/dist/pompelmi.audit.cjs +0 -128
  19. package/dist/pompelmi.audit.cjs.map +0 -1
  20. package/dist/pompelmi.audit.esm.js +0 -107
  21. package/dist/pompelmi.audit.esm.js.map +0 -1
  22. package/dist/pompelmi.browser.cjs +0 -1549
  23. package/dist/pompelmi.browser.cjs.map +0 -1
  24. package/dist/pompelmi.browser.esm.js +0 -1523
  25. package/dist/pompelmi.browser.esm.js.map +0 -1
  26. package/dist/pompelmi.cjs +0 -2591
  27. package/dist/pompelmi.cjs.map +0 -1
  28. package/dist/pompelmi.esm.js +0 -2525
  29. package/dist/pompelmi.esm.js.map +0 -1
  30. package/dist/pompelmi.hooks.cjs +0 -75
  31. package/dist/pompelmi.hooks.cjs.map +0 -1
  32. package/dist/pompelmi.hooks.esm.js +0 -72
  33. package/dist/pompelmi.hooks.esm.js.map +0 -1
  34. package/dist/pompelmi.policy-packs.cjs +0 -240
  35. package/dist/pompelmi.policy-packs.cjs.map +0 -1
  36. package/dist/pompelmi.policy-packs.esm.js +0 -232
  37. package/dist/pompelmi.policy-packs.esm.js.map +0 -1
  38. package/dist/pompelmi.quarantine.cjs +0 -317
  39. package/dist/pompelmi.quarantine.cjs.map +0 -1
  40. package/dist/pompelmi.quarantine.esm.js +0 -293
  41. package/dist/pompelmi.quarantine.esm.js.map +0 -1
  42. package/dist/pompelmi.react.cjs +0 -1580
  43. package/dist/pompelmi.react.cjs.map +0 -1
  44. package/dist/pompelmi.react.esm.js +0 -1553
  45. package/dist/pompelmi.react.esm.js.map +0 -1
  46. package/dist/types/audit.d.ts +0 -84
  47. package/dist/types/browser-index.d.ts +0 -29
  48. package/dist/types/config.d.ts +0 -143
  49. package/dist/types/engines/dynamic-taint.d.ts +0 -102
  50. package/dist/types/engines/hybrid-orchestrator.d.ts +0 -65
  51. package/dist/types/engines/hybrid-taint-integration.d.ts +0 -129
  52. package/dist/types/engines/taint-policies.d.ts +0 -84
  53. package/dist/types/hipaa-compliance.d.ts +0 -110
  54. package/dist/types/hooks.d.ts +0 -89
  55. package/dist/types/index.d.ts +0 -29
  56. package/dist/types/magic.d.ts +0 -7
  57. package/dist/types/node/scanDir.d.ts +0 -30
  58. package/dist/types/policy-packs.d.ts +0 -98
  59. package/dist/types/policy.d.ts +0 -12
  60. package/dist/types/presets.d.ts +0 -72
  61. package/dist/types/quarantine/index.d.ts +0 -18
  62. package/dist/types/quarantine/storage.d.ts +0 -77
  63. package/dist/types/quarantine/types.d.ts +0 -78
  64. package/dist/types/quarantine/workflow.d.ts +0 -97
  65. package/dist/types/react-index.d.ts +0 -13
  66. package/dist/types/risk.d.ts +0 -18
  67. package/dist/types/scan/remote.d.ts +0 -12
  68. package/dist/types/scan.d.ts +0 -17
  69. package/dist/types/scanners/common-heuristics.d.ts +0 -14
  70. package/dist/types/scanners/zip-bomb-guard.d.ts +0 -9
  71. package/dist/types/scanners/zipTraversalGuard.d.ts +0 -19
  72. package/dist/types/src/audit.d.ts +0 -84
  73. package/dist/types/src/browser-index.d.ts +0 -29
  74. package/dist/types/src/config.d.ts +0 -143
  75. package/dist/types/src/engines/dynamic-taint.d.ts +0 -102
  76. package/dist/types/src/engines/hybrid-orchestrator.d.ts +0 -65
  77. package/dist/types/src/engines/hybrid-taint-integration.d.ts +0 -129
  78. package/dist/types/src/engines/taint-policies.d.ts +0 -84
  79. package/dist/types/src/hipaa-compliance.d.ts +0 -110
  80. package/dist/types/src/hooks.d.ts +0 -89
  81. package/dist/types/src/index.d.ts +0 -29
  82. package/dist/types/src/magic.d.ts +0 -7
  83. package/dist/types/src/node/scanDir.d.ts +0 -30
  84. package/dist/types/src/policy-packs.d.ts +0 -98
  85. package/dist/types/src/policy.d.ts +0 -12
  86. package/dist/types/src/presets.d.ts +0 -72
  87. package/dist/types/src/quarantine/index.d.ts +0 -18
  88. package/dist/types/src/quarantine/storage.d.ts +0 -77
  89. package/dist/types/src/quarantine/types.d.ts +0 -78
  90. package/dist/types/src/quarantine/workflow.d.ts +0 -97
  91. package/dist/types/src/react-index.d.ts +0 -13
  92. package/dist/types/src/risk.d.ts +0 -18
  93. package/dist/types/src/scan/remote.d.ts +0 -12
  94. package/dist/types/src/scan.d.ts +0 -17
  95. package/dist/types/src/scanners/common-heuristics.d.ts +0 -14
  96. package/dist/types/src/scanners/zip-bomb-guard.d.ts +0 -11
  97. package/dist/types/src/scanners/zipTraversalGuard.d.ts +0 -19
  98. package/dist/types/src/stream.d.ts +0 -10
  99. package/dist/types/src/types/decompilation.d.ts +0 -96
  100. package/dist/types/src/types/taint-tracking.d.ts +0 -495
  101. package/dist/types/src/types.d.ts +0 -48
  102. package/dist/types/src/useFileScanner.d.ts +0 -15
  103. package/dist/types/src/utils/advanced-detection.d.ts +0 -21
  104. package/dist/types/src/utils/batch-scanner.d.ts +0 -62
  105. package/dist/types/src/utils/cache-manager.d.ts +0 -95
  106. package/dist/types/src/utils/export.d.ts +0 -51
  107. package/dist/types/src/utils/performance-metrics.d.ts +0 -68
  108. package/dist/types/src/utils/threat-intelligence.d.ts +0 -96
  109. package/dist/types/src/validate.d.ts +0 -7
  110. package/dist/types/src/verdict.d.ts +0 -2
  111. package/dist/types/src/yara/browser.d.ts +0 -7
  112. package/dist/types/src/yara/index.d.ts +0 -17
  113. package/dist/types/src/yara/node.d.ts +0 -2
  114. package/dist/types/src/yara/remote.d.ts +0 -10
  115. package/dist/types/src/yara-bridge.d.ts +0 -3
  116. package/dist/types/src/zip.d.ts +0 -13
  117. package/dist/types/stream.d.ts +0 -10
  118. package/dist/types/types/decompilation.d.ts +0 -96
  119. package/dist/types/types/taint-tracking.d.ts +0 -495
  120. package/dist/types/types.d.ts +0 -48
  121. package/dist/types/useFileScanner.d.ts +0 -15
  122. package/dist/types/utils/advanced-detection.d.ts +0 -21
  123. package/dist/types/utils/batch-scanner.d.ts +0 -62
  124. package/dist/types/utils/cache-manager.d.ts +0 -95
  125. package/dist/types/utils/export.d.ts +0 -51
  126. package/dist/types/utils/performance-metrics.d.ts +0 -68
  127. package/dist/types/utils/threat-intelligence.d.ts +0 -96
  128. package/dist/types/validate.d.ts +0 -7
  129. package/dist/types/verdict.d.ts +0 -2
  130. package/dist/types/yara/browser.d.ts +0 -7
  131. package/dist/types/yara/index.d.ts +0 -17
  132. package/dist/types/yara/node.d.ts +0 -2
  133. package/dist/types/yara/remote.d.ts +0 -10
  134. package/dist/types/yara-bridge.d.ts +0 -3
  135. package/dist/types/zip.d.ts +0 -13
@@ -0,0 +1,45 @@
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
+ "Bash(ls -d /Users/tommy/pompelmi/pompelmi/*/)",
39
+ "Bash(node --test test/unit.test.js)",
40
+ "Bash(echo \"exit:$?\")",
41
+ "Read(//tmp/**)",
42
+ "Bash(tee /Users/tommy/pompelmi/pompelmi/test_out.txt)"
43
+ ]
44
+ }
45
+ }
package/LICENSE CHANGED
@@ -1,21 +1,15 @@
1
- MIT License
1
+ ISC License
2
2
 
3
- Copyright (c) 2025 Tommaso Bertocchi
3
+ Copyright (c) 2024 pompelmi contributors
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
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
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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,221 @@
1
- <!-- ════════════════════════════════════════════════════════════════════
2
- PIVOT NOTICE read before using this package
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
- <div align="center">
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
- <strong>Mentioned by</strong>
86
- <a href="https://nodeweekly.com/issues/594">Node Weekly</a>,
87
- <a href="https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/">Stack Overflow</a>,
88
- <a href="https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/">Help Net Security</a>,
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
- > Upload endpoints are part of your attack surface. Pompelmi helps Node.js teams scan files before storage and make the decision while the route still has context: accept, quarantine, or reject.
16
+ ---
97
17
 
98
- ## Quick Start
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
21
+
22
+ - [Quickstart](#quickstart)
23
+ - [How it works](#how-it-works)
24
+ - [API](#api)
25
+ - [pompelmi.scan()](#pompelmiscanfilepath-options)
26
+ - [Docker / remote scanning](#docker--remote-scanning)
27
+ - [Internal utilities](#internal-utilities)
28
+ - [ClamAVInstaller()](#clamavinstaller)
29
+ - [updateClamAVDatabase()](#updateclamavdatabase)
30
+ - [Supported platforms](#supported-platforms)
31
+ - [Installing ClamAV manually](#installing-clamav-manually)
32
+ - [Testing](#testing)
33
+ - [Contributing](#contributing)
34
+ - [Security](#security)
35
+ - [License](#license)
99
36
 
100
- Install the core package:
37
+ ---
38
+
39
+ ## Quickstart
101
40
 
102
41
  ```bash
103
42
  npm install pompelmi
104
43
  ```
105
44
 
106
- This is the core pattern: inspect bytes, get a verdict, and only store clean files.
45
+ ```js
46
+ const pompelmi = require('pompelmi');
107
47
 
108
- ```ts
109
- import { scanBytes, STRICT_PUBLIC_UPLOAD } from 'pompelmi';
48
+ const result = await pompelmi.scan('/path/to/file.zip');
49
+ // "Clean" | "Malicious" | "ScanError"
110
50
 
111
- const report = await scanBytes(req.file.buffer, {
112
- filename: req.file.originalname,
113
- mimeType: req.file.mimetype,
114
- policy: STRICT_PUBLIC_UPLOAD,
115
- failClosed: true,
116
- });
51
+ if (result === 'Malicious') {
52
+ throw new Error('File rejected: malware detected');
53
+ }
54
+ ```
55
+
56
+ ## How it works
57
+
58
+ 1. **Validate** — pompelmi checks that the argument is a string and that the file exists before spawning anything.
59
+ 2. **Scan** — pompelmi spawns `clamscan --no-summary <filePath>` as a child process and reads the exit code.
60
+ 3. **Map** — the exit code is mapped to a result string. Unknown codes and spawn errors reject the Promise.
61
+
62
+ No stdout parsing. No regex. No surprises.
63
+
64
+ ## API
65
+
66
+ ### `pompelmi.scan(filePath, [options])`
67
+
68
+ ```ts
69
+ pompelmi.scan(filePath: string, options?: { host?: string; port?: number; timeout?: number }): Promise<"Clean" | "Malicious" | "ScanError">
70
+ ```
117
71
 
118
- if (report.verdict !== 'clean') {
119
- return res.status(422).json({
120
- error: 'Upload blocked',
121
- verdict: report.verdict,
122
- reasons: report.reasons,
123
- });
72
+ | Parameter | Type | Description |
73
+ |------------|----------|-----------------------------------------|
74
+ | `filePath` | `string` | Absolute or relative path to the file. |
75
+ | `options` | `object` | Optional. Omit to use the local `clamscan` CLI. Pass `host` / `port` to scan via a clamd TCP socket instead. See [docs/api.md](./docs/api.md) for the full reference. |
76
+
77
+ **Resolves** to one of:
78
+
79
+ | Result | ClamAV exit code | Meaning |
80
+ |---------------|:---:|------------------------------------------------------------------------------------------------------|
81
+ | `"Clean"` | 0 | No threats found. |
82
+ | `"Malicious"` | 1 | A known virus or malware signature was matched. |
83
+ | `"ScanError"` | 2 | The scan itself failed (I/O error, encrypted archive, permission denied). File status is unknown — treat as untrusted. |
84
+
85
+ **Rejects** with an `Error` in these cases:
86
+
87
+ | Condition | Error message |
88
+ |-----------|---------------|
89
+ | `filePath` is not a string | `filePath must be a string` |
90
+ | File does not exist | `File not found: <path>` |
91
+ | `clamscan` is not in PATH | `ENOENT` (from the OS) |
92
+ | ClamAV returns an unknown exit code | `Unexpected exit code: N` |
93
+ | `clamscan` process is killed by a signal | `Process killed by signal: <SIGNAL>` |
94
+
95
+ **Example — full error handling:**
96
+
97
+ ```js
98
+ const pompelmi = require('pompelmi');
99
+ const path = require('path');
100
+
101
+ async function safeScan(filePath) {
102
+ try {
103
+ const result = await pompelmi.scan(path.resolve(filePath));
104
+
105
+ if (result === 'ScanError') {
106
+ // The scan could not complete — treat the file as untrusted.
107
+ console.warn('Scan incomplete, rejecting file as precaution.');
108
+ return null;
109
+ }
110
+
111
+ return result; // "Clean" or "Malicious"
112
+ } catch (err) {
113
+ console.error('Scan failed:', err.message);
114
+ return null;
115
+ }
124
116
  }
117
+ ```
118
+
119
+ ## Docker / remote scanning
120
+
121
+ If ClamAV runs in a Docker container (or anywhere on the network), pass `host` and `port` — everything else stays the same.
125
122
 
126
- return res.status(200).json({ verdict: report.verdict });
123
+ ```js
124
+ const result = await pompelmi.scan('/path/to/upload.zip', {
125
+ host: '127.0.0.1',
126
+ port: 3310,
127
+ });
127
128
  ```
128
129
 
129
- Start with [Getting started](https://pompelmi.app/getting-started/) for a local scan in under a minute, open the [browser preview](https://pompelmi.app/#browser-preview) to inspect the verdict flow without sending files anywhere, or run the minimal [Express demo](./examples/demo).
130
+ See [docs/docker.md](./docs/docker.md) for the `docker-compose.yml` snippet and first-boot notes.
130
131
 
131
- ## Why teams use Pompelmi
132
+ ---
132
133
 
133
- - File upload endpoints are not just form validation. Files can become risky after storage, extraction, rendering, or downstream parsing.
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.
134
+ ## Internal utilities
136
135
 
137
- File upload vulnerabilities are the root cause of real CVEs across web frameworks. Application-layer inspection is the earliest point where the route still has full policy context — file class, trust level, storage path, and failure mode. Waiting until after storage removes most of that context and limits what the application can decide.
136
+ These modules are not part of the public npm API but are used internally to set up the ClamAV environment on a fresh machine.
138
137
 
139
- ## What it checks
138
+ ### `ClamAVInstaller()`
140
139
 
141
- - MIME sniffing, magic-byte validation, and extension mismatch detection
142
- - Risky archives such as ZIP bombs, traversal attempts, deep nesting, and entry-count abuse
143
- - Risky document and binary signals such as PDF actions, Office macro hints, PE headers, and polyglot files
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
140
+ Installs ClamAV using the platform's native package manager. Skips silently if ClamAV is already installed.
146
141
 
147
- ## Where it fits in the upload pipeline
142
+ ```ts
143
+ ClamAVInstaller(): Promise<string>
144
+ ```
148
145
 
149
- 1. Receive the upload into memory or an isolated staging or quarantine area.
150
- 2. Scan the bytes with a route policy.
151
- 3. Act on the verdict: `clean`, `suspicious`, or `malicious`.
152
- 4. Persist, quarantine, or reject based on the route's rules.
146
+ - Resolves with a status message string on success or skip.
147
+ - Rejects if the install process exits with a non-zero code or if spawning the package manager fails.
153
148
 
154
- That inspect-first, store-later shape is where Pompelmi is strongest.
149
+ | Platform | Package manager | Command |
150
+ |----------|----------------|---------|
151
+ | macOS | Homebrew | `brew install clamav` |
152
+ | Linux | apt-get | `sudo apt-get install -y clamav clamav-daemon` |
153
+ | Windows | Chocolatey | `choco install clamav -y` |
155
154
 
156
- ## What it is and isn't
155
+ ### `updateClamAVDatabase()`
157
156
 
158
- | Approach | Useful for | What it misses |
159
- | --- | --- | --- |
160
- | Browser MIME and extension checks | Fast client-side hints and UX feedback | Client MIME and filenames are easy to spoof |
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 |
157
+ Downloads or updates the ClamAV virus definition database by running `freshclam`. Skips if `main.cvd` is already present on disk.
164
158
 
165
- <!-- search: file upload security Node.js, MIME spoofing protection, archive bomb defense -->
159
+ ```ts
160
+ updateClamAVDatabase(): Promise<string>
161
+ ```
166
162
 
167
- ## Supported frameworks and workflows
163
+ - Resolves with a status message string on success or skip.
164
+ - Rejects if `freshclam` exits with a non-zero code or if spawning fails.
168
165
 
169
- | Stack or workflow | Links |
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/) |
166
+ | Platform | Database path |
167
+ |----------|--------------|
168
+ | macOS | `/usr/local/share/clamav/main.cvd` |
169
+ | Linux | `/var/lib/clamav/main.cvd` |
170
+ | Windows | `C:\ProgramData\ClamAV\main.cvd` |
179
171
 
180
- ## Demo, preview, and examples
172
+ ## Supported platforms
181
173
 
182
- ![Pompelmi upload security demo](assets/malware-detection-node-demo.gif)
174
+ | OS | ClamAV install | DB path checked |
175
+ |---------|---------------------------|------------------------------------|
176
+ | macOS | `brew install clamav` | `/usr/local/share/clamav/main.cvd` |
177
+ | Linux | `apt-get install clamav` | `/var/lib/clamav/main.cvd` |
178
+ | Windows | `choco install clamav -y` | `C:\ProgramData\ClamAV\main.cvd` |
183
179
 
184
- - [Browser preview](https://pompelmi.app/#browser-preview) for a fast local look at the verdict UX without uploading files anywhere
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
180
+ ClamAV must be installed on the host system. pompelmi does not bundle or download it.
188
181
 
189
- Listed in Awesome JavaScript and Awesome TypeScript, and featured by Node Weekly, Stack Overflow, Help Net Security, Bytes, and Detection Engineering Weekly.
182
+ ## Installing ClamAV manually
190
183
 
191
- <!-- MENTIONS:START -->
184
+ ```bash
185
+ # macOS
186
+ brew install clamav && freshclam
187
+
188
+ # Linux (Debian / Ubuntu)
189
+ sudo apt-get install -y clamav clamav-daemon && sudo freshclam
190
+
191
+ # Windows (Chocolatey)
192
+ choco install clamav -y
193
+ ```
194
+
195
+ ## Testing
196
+
197
+ ```bash
198
+ npm test
199
+ ```
192
200
 
193
- ## Mentioned by
201
+ The test suite has two parts:
194
202
 
195
- - [Node Weekly](https://nodeweekly.com/issues/594)
196
- - [Defense against uploads: Q&A with OSS file scanner, pompelmi](https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/) Stack Overflow
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/)
203
+ - **Unit tests** (`test/unit.test.js`) — run with Node's built-in test runner. Mock `cross-spawn` and platform dependencies; no ClamAV installation required.
204
+ - **Integration tests** (`test/scan.test.js`) spawn real `clamscan` processes against EICAR test files. Skipped automatically if `clamscan` is not found in PATH.
205
205
 
206
- <!-- MENTIONS:END -->
206
+ ## Contributing
207
207
 
208
- ## Docs
208
+ 1. Fork the repository at [https://github.com/pompelmi/pompelmi](https://github.com/pompelmi/pompelmi).
209
+ 2. Create a feature branch: `git checkout -b feat/your-change`.
210
+ 3. Make your changes and run `npm test` to verify.
211
+ 4. Open a pull request against `main`.
209
212
 
210
- - [Getting started](https://pompelmi.app/getting-started/)
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/)
213
+ Please read [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before contributing.
217
214
 
218
- ## Commercial support
215
+ ## Security
219
216
 
220
- The MIT core remains the primary path. Teams that need private rollout help, architecture review, or policy tuning can use the existing [enterprise support path](https://pompelmi.app/enterprise/).
217
+ To report a vulnerability, see [SECURITY.md](./SECURITY.md).
221
218
 
222
- ## Project
219
+ ## License
223
220
 
224
- - [Contributing](./CONTRIBUTING.md)
225
- - [Security](./SECURITY.md)
226
- - [Roadmap](./ROADMAP.md)
227
- - [GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)
228
- - [License](./LICENSE)
221
+ [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
+ ]);