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.
Files changed (133) hide show
  1. package/.claude/settings.local.json +40 -0
  2. package/LICENSE +12 -18
  3. package/README.md +159 -183
  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 +31 -0
  9. package/src/InstallerCommand.js +11 -0
  10. package/src/config.js +22 -0
  11. package/src/constants.js +3 -0
  12. package/src/favicon.ico +0 -0
  13. package/src/grapefruit.png +0 -0
  14. package/src/index.js +5 -0
  15. package/CHANGELOG.md +0 -71
  16. package/dist/pompelmi.audit.cjs +0 -128
  17. package/dist/pompelmi.audit.cjs.map +0 -1
  18. package/dist/pompelmi.audit.esm.js +0 -107
  19. package/dist/pompelmi.audit.esm.js.map +0 -1
  20. package/dist/pompelmi.browser.cjs +0 -1549
  21. package/dist/pompelmi.browser.cjs.map +0 -1
  22. package/dist/pompelmi.browser.esm.js +0 -1523
  23. package/dist/pompelmi.browser.esm.js.map +0 -1
  24. package/dist/pompelmi.cjs +0 -2591
  25. package/dist/pompelmi.cjs.map +0 -1
  26. package/dist/pompelmi.esm.js +0 -2525
  27. package/dist/pompelmi.esm.js.map +0 -1
  28. package/dist/pompelmi.hooks.cjs +0 -75
  29. package/dist/pompelmi.hooks.cjs.map +0 -1
  30. package/dist/pompelmi.hooks.esm.js +0 -72
  31. package/dist/pompelmi.hooks.esm.js.map +0 -1
  32. package/dist/pompelmi.policy-packs.cjs +0 -240
  33. package/dist/pompelmi.policy-packs.cjs.map +0 -1
  34. package/dist/pompelmi.policy-packs.esm.js +0 -232
  35. package/dist/pompelmi.policy-packs.esm.js.map +0 -1
  36. package/dist/pompelmi.quarantine.cjs +0 -317
  37. package/dist/pompelmi.quarantine.cjs.map +0 -1
  38. package/dist/pompelmi.quarantine.esm.js +0 -293
  39. package/dist/pompelmi.quarantine.esm.js.map +0 -1
  40. package/dist/pompelmi.react.cjs +0 -1580
  41. package/dist/pompelmi.react.cjs.map +0 -1
  42. package/dist/pompelmi.react.esm.js +0 -1553
  43. package/dist/pompelmi.react.esm.js.map +0 -1
  44. package/dist/types/audit.d.ts +0 -84
  45. package/dist/types/browser-index.d.ts +0 -29
  46. package/dist/types/config.d.ts +0 -143
  47. package/dist/types/engines/dynamic-taint.d.ts +0 -102
  48. package/dist/types/engines/hybrid-orchestrator.d.ts +0 -65
  49. package/dist/types/engines/hybrid-taint-integration.d.ts +0 -129
  50. package/dist/types/engines/taint-policies.d.ts +0 -84
  51. package/dist/types/hipaa-compliance.d.ts +0 -110
  52. package/dist/types/hooks.d.ts +0 -89
  53. package/dist/types/index.d.ts +0 -29
  54. package/dist/types/magic.d.ts +0 -7
  55. package/dist/types/node/scanDir.d.ts +0 -30
  56. package/dist/types/policy-packs.d.ts +0 -98
  57. package/dist/types/policy.d.ts +0 -12
  58. package/dist/types/presets.d.ts +0 -72
  59. package/dist/types/quarantine/index.d.ts +0 -18
  60. package/dist/types/quarantine/storage.d.ts +0 -77
  61. package/dist/types/quarantine/types.d.ts +0 -78
  62. package/dist/types/quarantine/workflow.d.ts +0 -97
  63. package/dist/types/react-index.d.ts +0 -13
  64. package/dist/types/risk.d.ts +0 -18
  65. package/dist/types/scan/remote.d.ts +0 -12
  66. package/dist/types/scan.d.ts +0 -17
  67. package/dist/types/scanners/common-heuristics.d.ts +0 -14
  68. package/dist/types/scanners/zip-bomb-guard.d.ts +0 -9
  69. package/dist/types/scanners/zipTraversalGuard.d.ts +0 -19
  70. package/dist/types/src/audit.d.ts +0 -84
  71. package/dist/types/src/browser-index.d.ts +0 -29
  72. package/dist/types/src/config.d.ts +0 -143
  73. package/dist/types/src/engines/dynamic-taint.d.ts +0 -102
  74. package/dist/types/src/engines/hybrid-orchestrator.d.ts +0 -65
  75. package/dist/types/src/engines/hybrid-taint-integration.d.ts +0 -129
  76. package/dist/types/src/engines/taint-policies.d.ts +0 -84
  77. package/dist/types/src/hipaa-compliance.d.ts +0 -110
  78. package/dist/types/src/hooks.d.ts +0 -89
  79. package/dist/types/src/index.d.ts +0 -29
  80. package/dist/types/src/magic.d.ts +0 -7
  81. package/dist/types/src/node/scanDir.d.ts +0 -30
  82. package/dist/types/src/policy-packs.d.ts +0 -98
  83. package/dist/types/src/policy.d.ts +0 -12
  84. package/dist/types/src/presets.d.ts +0 -72
  85. package/dist/types/src/quarantine/index.d.ts +0 -18
  86. package/dist/types/src/quarantine/storage.d.ts +0 -77
  87. package/dist/types/src/quarantine/types.d.ts +0 -78
  88. package/dist/types/src/quarantine/workflow.d.ts +0 -97
  89. package/dist/types/src/react-index.d.ts +0 -13
  90. package/dist/types/src/risk.d.ts +0 -18
  91. package/dist/types/src/scan/remote.d.ts +0 -12
  92. package/dist/types/src/scan.d.ts +0 -17
  93. package/dist/types/src/scanners/common-heuristics.d.ts +0 -14
  94. package/dist/types/src/scanners/zip-bomb-guard.d.ts +0 -11
  95. package/dist/types/src/scanners/zipTraversalGuard.d.ts +0 -19
  96. package/dist/types/src/stream.d.ts +0 -10
  97. package/dist/types/src/types/decompilation.d.ts +0 -96
  98. package/dist/types/src/types/taint-tracking.d.ts +0 -495
  99. package/dist/types/src/types.d.ts +0 -48
  100. package/dist/types/src/useFileScanner.d.ts +0 -15
  101. package/dist/types/src/utils/advanced-detection.d.ts +0 -21
  102. package/dist/types/src/utils/batch-scanner.d.ts +0 -62
  103. package/dist/types/src/utils/cache-manager.d.ts +0 -95
  104. package/dist/types/src/utils/export.d.ts +0 -51
  105. package/dist/types/src/utils/performance-metrics.d.ts +0 -68
  106. package/dist/types/src/utils/threat-intelligence.d.ts +0 -96
  107. package/dist/types/src/validate.d.ts +0 -7
  108. package/dist/types/src/verdict.d.ts +0 -2
  109. package/dist/types/src/yara/browser.d.ts +0 -7
  110. package/dist/types/src/yara/index.d.ts +0 -17
  111. package/dist/types/src/yara/node.d.ts +0 -2
  112. package/dist/types/src/yara/remote.d.ts +0 -10
  113. package/dist/types/src/yara-bridge.d.ts +0 -3
  114. package/dist/types/src/zip.d.ts +0 -13
  115. package/dist/types/stream.d.ts +0 -10
  116. package/dist/types/types/decompilation.d.ts +0 -96
  117. package/dist/types/types/taint-tracking.d.ts +0 -495
  118. package/dist/types/types.d.ts +0 -48
  119. package/dist/types/useFileScanner.d.ts +0 -15
  120. package/dist/types/utils/advanced-detection.d.ts +0 -21
  121. package/dist/types/utils/batch-scanner.d.ts +0 -62
  122. package/dist/types/utils/cache-manager.d.ts +0 -95
  123. package/dist/types/utils/export.d.ts +0 -51
  124. package/dist/types/utils/performance-metrics.d.ts +0 -68
  125. package/dist/types/utils/threat-intelligence.d.ts +0 -96
  126. package/dist/types/validate.d.ts +0 -7
  127. package/dist/types/verdict.d.ts +0 -2
  128. package/dist/types/yara/browser.d.ts +0 -7
  129. package/dist/types/yara/index.d.ts +0 -17
  130. package/dist/types/yara/node.d.ts +0 -2
  131. package/dist/types/yara/remote.d.ts +0 -10
  132. package/dist/types/yara-bridge.d.ts +0 -3
  133. 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
- 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,204 @@
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
+ ---
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
- ## Quick Start
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
- Install the core package:
38
+ ## Quickstart
101
39
 
102
40
  ```bash
103
41
  npm install pompelmi
104
42
  ```
105
43
 
106
- This is the core pattern: inspect bytes, get a verdict, and only store clean files.
44
+ ```js
45
+ const pompelmi = require('pompelmi');
107
46
 
108
- ```ts
109
- import { scanBytes, STRICT_PUBLIC_UPLOAD } from 'pompelmi';
110
-
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
- });
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
- return res.status(200).json({ verdict: report.verdict });
65
+ ### `pompelmi.scan(filePath)`
66
+
67
+ ```ts
68
+ pompelmi.scan(filePath: string): Promise<"Clean" | "Malicious" | "ScanError">
127
69
  ```
128
70
 
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).
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
- ## Why teams use Pompelmi
117
+ ## Internal utilities
132
118
 
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.
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
- 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.
121
+ ### `ClamAVInstaller()`
138
122
 
139
- ## What it checks
123
+ Installs ClamAV using the platform's native package manager. Skips silently if ClamAV is already installed.
140
124
 
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
125
+ ```ts
126
+ ClamAVInstaller(): Promise<string>
127
+ ```
146
128
 
147
- ## Where it fits in the upload pipeline
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
- 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.
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
- That inspect-first, store-later shape is where Pompelmi is strongest.
138
+ ### `updateClamAVDatabase()`
155
139
 
156
- ## What it is and isn't
140
+ Downloads or updates the ClamAV virus definition database by running `freshclam`. Skips if `main.cvd` is already present on disk.
157
141
 
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 |
142
+ ```ts
143
+ updateClamAVDatabase(): Promise<string>
144
+ ```
164
145
 
165
- <!-- search: file upload security Node.js, MIME spoofing protection, archive bomb defense -->
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
- ## Supported frameworks and workflows
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
- | 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/) |
155
+ ## Supported platforms
179
156
 
180
- ## Demo, preview, and examples
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
- ![Pompelmi upload security demo](assets/malware-detection-node-demo.gif)
163
+ ClamAV must be installed on the host system. pompelmi does not bundle or download it.
183
164
 
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
165
+ ## Installing ClamAV manually
188
166
 
189
- Listed in Awesome JavaScript and Awesome TypeScript, and featured by Node Weekly, Stack Overflow, Help Net Security, Bytes, and Detection Engineering Weekly.
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
- <!-- MENTIONS:START -->
178
+ ## Testing
179
+
180
+ ```bash
181
+ npm test
182
+ ```
192
183
 
193
- ## Mentioned by
184
+ The test suite has two parts:
194
185
 
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/)
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
- <!-- MENTIONS:END -->
189
+ ## Contributing
207
190
 
208
- ## Docs
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
- - [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/)
196
+ Please read [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before contributing.
217
197
 
218
- ## Commercial support
198
+ ## Security
219
199
 
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/).
200
+ To report a vulnerability, see [SECURITY.md](./SECURITY.md).
221
201
 
222
- ## Project
202
+ ## License
223
203
 
224
- - [Contributing](./CONTRIBUTING.md)
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
+ ]);