pompelmi 0.35.4 → 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 +160 -143
  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 -1493
  21. package/dist/pompelmi.browser.cjs.map +0 -1
  22. package/dist/pompelmi.browser.esm.js +0 -1467
  23. package/dist/pompelmi.browser.esm.js.map +0 -1
  24. package/dist/pompelmi.cjs +0 -2535
  25. package/dist/pompelmi.cjs.map +0 -1
  26. package/dist/pompelmi.esm.js +0 -2469
  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 -1524
  41. package/dist/pompelmi.react.cjs.map +0 -1
  42. package/dist/pompelmi.react.esm.js +0 -1497
  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 -9
  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,187 +1,204 @@
1
- <div align="center">
2
- <img src="./assets/logo.svg" alt="Pompelmi logo" width="120" />
3
-
4
- <h1>Pompelmi</h1>
5
-
6
- <p><strong>Secure file uploads in Node.js before storage.</strong></p>
7
-
8
- <p>
9
- Open-source route-level upload security for Node.js teams that need to
10
- inspect untrusted files before disk, object storage, previews, or
11
- downstream parsers.
12
- </p>
13
-
14
- <p><code>clean</code> · <code>suspicious</code> · <code>malicious</code></p>
15
-
16
- <p>
17
- MIME spoofing · risky archives · document and binary signals · optional
18
- YARA
19
- </p>
20
-
21
- <p>
22
- <sub>Express · Next.js · NestJS · Fastify · Koa · Nuxt/Nitro · S3 quarantine flows · CI/CD</sub>
23
- </p>
24
-
25
- <p>
26
- <a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi" /></a>
27
- <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>
28
- <a href="https://codecov.io/gh/pompelmi/pompelmi"><img alt="codecov" src="https://codecov.io/gh/pompelmi/pompelmi/graph/badge.svg" /></a>
29
- <a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social" /></a>
30
- </p>
31
-
32
- <p>
33
- <a href="https://pompelmi.app/"><strong>Docs</strong></a>
34
- ·
35
- <a href="https://pompelmi.app/getting-started/"><strong>Getting started</strong></a>
36
- ·
37
- <a href="https://pompelmi.app/#browser-preview"><strong>Browser preview</strong></a>
38
- ·
39
- <a href="./examples/demo"><strong>Express demo</strong></a>
40
- ·
41
- <a href="./examples/README.md"><strong>Examples</strong></a>
42
- </p>
43
-
44
- <p><sub>Node.js 18+ · MIT</sub></p>
45
- </div>
46
-
47
1
  <p align="center">
48
- <strong>Mentioned by</strong>
49
- <a href="https://nodeweekly.com/issues/594">Node Weekly</a>,
50
- <a href="https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/">Stack Overflow</a>,
51
- <a href="https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/">Help Net Security</a>,
52
- <a href="https://github.com/sorrycc/awesome-javascript">Awesome JavaScript</a>,
53
- and
54
- <a href="https://github.com/dzharii/awesome-typescript">Awesome TypeScript</a>
2
+ <img src="./src/grapefruit.png" width="96" alt="pompelmi logo">
55
3
  </p>
56
4
 
5
+ <h1 align="center">pompelmi</h1>
6
+
7
+ <p align="center"><strong>ClamAV for humans</strong></p>
8
+
57
9
  <p align="center">
58
- <sub>If you want upload security to start at the route boundary instead of after storage, consider starring the repo.</sub>
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>
59
14
  </p>
60
15
 
61
- > 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.
62
19
 
63
- ## Quick Start
20
+ ## Table of contents
64
21
 
65
- Install the core package:
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
+ ---
37
+
38
+ ## Quickstart
66
39
 
67
40
  ```bash
68
41
  npm install pompelmi
69
42
  ```
70
43
 
71
- This is the core pattern: inspect bytes, get a verdict, and only store clean files.
44
+ ```js
45
+ const pompelmi = require('pompelmi');
72
46
 
73
- ```ts
74
- import { scanBytes, STRICT_PUBLIC_UPLOAD } from 'pompelmi';
75
-
76
- const report = await scanBytes(req.file.buffer, {
77
- filename: req.file.originalname,
78
- mimeType: req.file.mimetype,
79
- policy: STRICT_PUBLIC_UPLOAD,
80
- failClosed: true,
81
- });
82
-
83
- if (report.verdict !== 'clean') {
84
- return res.status(422).json({
85
- error: 'Upload blocked',
86
- verdict: report.verdict,
87
- reasons: report.reasons,
88
- });
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');
89
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.
90
62
 
91
- return res.status(200).json({ verdict: report.verdict });
63
+ ## API
64
+
65
+ ### `pompelmi.scan(filePath)`
66
+
67
+ ```ts
68
+ pompelmi.scan(filePath: string): Promise<"Clean" | "Malicious" | "ScanError">
92
69
  ```
93
70
 
94
- 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
+ ```
116
+
117
+ ## Internal utilities
118
+
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.
95
120
 
96
- ## Why teams use Pompelmi
121
+ ### `ClamAVInstaller()`
122
+
123
+ Installs ClamAV using the platform's native package manager. Skips silently if ClamAV is already installed.
124
+
125
+ ```ts
126
+ ClamAVInstaller(): Promise<string>
127
+ ```
97
128
 
98
- - File upload endpoints are not just form validation. Files can become risky after storage, extraction, rendering, or downstream parsing.
99
- - 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.
100
- - It gives Node.js teams a practical way to build secure file uploads with route-level decisions instead of bolting checks on after persistence.
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.
101
131
 
102
- ## What it checks
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` |
103
137
 
104
- - MIME sniffing, magic-byte validation, and extension mismatch detection
105
- - Risky archives such as ZIP bombs, traversal attempts, deep nesting, and entry-count abuse
106
- - Risky document and binary signals such as PDF actions, Office macro hints, PE headers, and polyglot files
107
- - Optional YARA-based matches when you want malware scanning uploads as part of the flow
108
- - Verdicts and reasons you can use for fail-closed routes, quarantine flows, and auditability
138
+ ### `updateClamAVDatabase()`
109
139
 
110
- ## Where it fits in the upload pipeline
140
+ Downloads or updates the ClamAV virus definition database by running `freshclam`. Skips if `main.cvd` is already present on disk.
111
141
 
112
- 1. Receive the upload into memory or an isolated staging or quarantine area.
113
- 2. Scan the bytes with a route policy.
114
- 3. Act on the verdict: `clean`, `suspicious`, or `malicious`.
115
- 4. Persist, quarantine, or reject based on the route's rules.
142
+ ```ts
143
+ updateClamAVDatabase(): Promise<string>
144
+ ```
116
145
 
117
- That inspect-first, store-later shape is where Pompelmi is strongest.
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.
118
148
 
119
- ## What it is and isn't
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` |
120
154
 
121
- | Approach | Useful for | What it misses |
122
- | --- | --- | --- |
123
- | Browser MIME and extension checks | Fast client-side hints and UX feedback | Client MIME and filenames are easy to spoof |
124
- | File-type or magic-byte validation only | Confirming a file looks like the claimed type | Archive abuse, risky internal structure, and route policy decisions |
125
- | Antivirus or YARA only | Known malicious matches and signature-style detection | Route context, spoofing checks, and before-storage handling |
126
- | 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 |
155
+ ## Supported platforms
127
156
 
128
- ## Supported frameworks and workflows
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` |
129
162
 
130
- | Stack or workflow | Links |
131
- | --- | --- |
132
- | Express | [Docs](https://pompelmi.app/how-to/express/) · [Minimal example](./examples/express-minimal) · [Demo](./examples/demo) |
133
- | Next.js | [Docs](https://pompelmi.app/how-to/nextjs/) · [Example](./examples/next-app-router) · [Package](./packages/next-upload) |
134
- | NestJS | [Docs](https://pompelmi.app/how-to/nestjs/) · [Package](./packages/nestjs) · [Example app](./examples/nestjs-app) |
135
- | Fastify | [Docs](https://pompelmi.app/how-to/fastify/) · [Package](./packages/fastify-plugin) |
136
- | Koa | [Docs](https://pompelmi.app/how-to/koa/) · [Package](./packages/koa-middleware) |
137
- | Nuxt/Nitro | [Docs](https://pompelmi.app/how-to/nuxt-nitro/) · [Example](./examples/nuxt-nitro) |
138
- | 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/) |
139
- | CI/CD | [Use case](https://pompelmi.app/use-cases/cicd-artifact-scanning/) · [Blog](https://pompelmi.app/blog/cicd-scan-build-artifacts/) |
163
+ ClamAV must be installed on the host system. pompelmi does not bundle or download it.
140
164
 
141
- ## Demo, preview, and examples
165
+ ## Installing ClamAV manually
142
166
 
143
- ![Pompelmi upload security demo](assets/malware-detection-node-demo.gif)
167
+ ```bash
168
+ # macOS
169
+ brew install clamav && freshclam
144
170
 
145
- - [Browser preview](https://pompelmi.app/#browser-preview) for a fast local look at the verdict UX without uploading files anywhere
146
- - [Express demo](./examples/demo) for a tiny upload gate that returns `clean`, `suspicious`, or `malicious` before storage
147
- - [Examples index](./examples/README.md) for framework-specific and production-oriented patterns
148
- - [Docs home](https://pompelmi.app/) for guides, comparisons, use cases, and tutorials
171
+ # Linux (Debian / Ubuntu)
172
+ sudo apt-get install -y clamav clamav-daemon && sudo freshclam
149
173
 
150
- ## Why star this repo
174
+ # Windows (Chocolatey)
175
+ choco install clamav -y
176
+ ```
151
177
 
152
- Pompelmi is focused on a real gap in most Node.js stacks: secure file uploads that make a decision before storage, not after. If that matches how you want upload security to work, star the repo to follow the project and help more teams discover the inspect-before-storage model.
178
+ ## Testing
153
179
 
154
- <!-- MENTIONS:START -->
180
+ ```bash
181
+ npm test
182
+ ```
155
183
 
156
- ## Mentioned by
184
+ The test suite has two parts:
157
185
 
158
- - [Node Weekly](https://nodeweekly.com/issues/594)
159
- - [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
160
- - [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
161
- - [Awesome JavaScript](https://github.com/sorrycc/awesome-javascript)
162
- - [Awesome TypeScript](https://github.com/dzharii/awesome-typescript)
163
- - [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.
164
188
 
165
- <!-- MENTIONS:END -->
189
+ ## Contributing
166
190
 
167
- ## 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`.
168
195
 
169
- - [Getting started](https://pompelmi.app/getting-started/)
170
- - [Use cases](https://pompelmi.app/use-cases/)
171
- - [Comparisons](https://pompelmi.app/comparisons/)
172
- - [Tutorials](https://pompelmi.app/tutorials/)
173
- - [Browser preview](https://pompelmi.app/#browser-preview)
174
- - [Featured in](https://pompelmi.app/featured-in/)
175
- - [Translations](https://pompelmi.app/translations/)
196
+ Please read [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) before contributing.
176
197
 
177
- ## Commercial support
198
+ ## Security
178
199
 
179
- 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).
180
201
 
181
- ## Project
202
+ ## License
182
203
 
183
- - [Contributing](./CONTRIBUTING.md)
184
- - [Security](./SECURITY.md)
185
- - [Roadmap](./ROADMAP.md)
186
- - [GitHub Discussions](https://github.com/pompelmi/pompelmi/discussions)
187
- - [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
+ ]);