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.
- package/.claude/settings.local.json +45 -0
- package/LICENSE +12 -18
- package/README.md +174 -181
- 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 +37 -0
- package/src/ClamdScanner.js +81 -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/test_out.txt +74 -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
package/package.json
CHANGED
|
@@ -1,263 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pompelmi",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"browser": {
|
|
9
|
-
"yara": false,
|
|
10
|
-
"util": false,
|
|
11
|
-
"crypto": false,
|
|
12
|
-
"os": false,
|
|
13
|
-
"path": false,
|
|
14
|
-
"unzipper": false,
|
|
15
|
-
"child_process": false
|
|
16
|
-
},
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "ClamAV for humans — scan any file and get back Clean, Malicious, or ScanError. No daemons. No cloud. No native bindings.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "pompelmi contributors",
|
|
7
|
+
"homepage": "https://pompelmi.app",
|
|
17
8
|
"repository": {
|
|
18
9
|
"type": "git",
|
|
19
10
|
"url": "https://github.com/pompelmi/pompelmi.git"
|
|
20
11
|
},
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"type": "github",
|
|
24
|
-
"url": "https://github.com/sponsors/pompelmi"
|
|
25
|
-
},
|
|
26
|
-
"pnpm": {
|
|
27
|
-
"overrides": {
|
|
28
|
-
"process": "0.11.10",
|
|
29
|
-
"regjsgen": "0.8.0",
|
|
30
|
-
"fflate": "0.8.2",
|
|
31
|
-
"@tokenizer/inflate>fflate": "0.8.2",
|
|
32
|
-
"file-type>fflate": "0.8.2",
|
|
33
|
-
"regexpu-core>regjsgen": "0.8.0",
|
|
34
|
-
"@babel/helper-create-regexp-features-plugin>regjsgen": "0.8.0",
|
|
35
|
-
"vitest": "2.1.9",
|
|
36
|
-
"@vitest/coverage-v8": "2.1.9",
|
|
37
|
-
"babel-plugin-polyfill-corejs3": "^0.13.0",
|
|
38
|
-
"@types/cookies": "0.9.1",
|
|
39
|
-
"@types/koa>@types/cookies": "0.9.1",
|
|
40
|
-
"pompelmi": "workspace:*",
|
|
41
|
-
"@pompelmi/core": "workspace:*",
|
|
42
|
-
"katex": "0.16.21",
|
|
43
|
-
"react": "^19.2.0",
|
|
44
|
-
"react-dom": "^19.2.0",
|
|
45
|
-
"@types/react": "^19.2.0",
|
|
46
|
-
"@types/react-dom": "^19.2.0",
|
|
47
|
-
"esbuild@<=0.24.2": ">=0.25.0",
|
|
48
|
-
"devalue@<5.3.2": ">=5.3.2",
|
|
49
|
-
"vite@>=6.0.0 <=6.3.5": ">=6.3.6",
|
|
50
|
-
"katex@>=0.12.0 <=0.16.20": ">=0.16.21",
|
|
51
|
-
"astro@<5.14.3": ">=5.14.3",
|
|
52
|
-
"vite@>=6.0.0 <=6.4.0": ">=6.4.1",
|
|
53
|
-
"astro@>=2.16.0 <5.15.5": ">=5.15.5",
|
|
54
|
-
"js-yaml@<3.14.2": ">=3.14.2",
|
|
55
|
-
"js-yaml@>=4.0.0 <4.1.1": ">=4.1.1",
|
|
56
|
-
"glob@>=10.2.0 <10.5.0": ">=10.5.0",
|
|
57
|
-
"astro@<=5.15.6": ">=5.15.8",
|
|
58
|
-
"body-parser@>=2.2.0 <2.2.1": ">=2.2.1",
|
|
59
|
-
"astro@<5.15.9": ">=5.15.9",
|
|
60
|
-
"astro@<5.15.8": ">=5.15.8",
|
|
61
|
-
"astro@>=5.2.0 <5.15.6": ">=5.15.6",
|
|
62
|
-
"mdast-util-to-hast@>=13.0.0 <13.2.1": ">=13.2.1",
|
|
63
|
-
"next@>=16.0.0-canary.0 <16.0.7": ">=16.0.7",
|
|
64
|
-
"next@>=16.0.0-beta.0 <16.0.9": ">=16.0.9",
|
|
65
|
-
"qs@<6.14.2": ">=6.14.2",
|
|
66
|
-
"multer@<2.0.2": ">=2.0.2",
|
|
67
|
-
"@isaacs/brace-expansion@<=5.0.0": ">=5.0.1",
|
|
68
|
-
"ajv@<8.18.0": ">=8.18.0",
|
|
69
|
-
"fastify@<5.7.3": ">=5.7.3",
|
|
70
|
-
"next@>=16.0.9 <16.1.5": ">=16.1.5",
|
|
71
|
-
"preact@>=10.28.0 <10.28.2": ">=10.28.2",
|
|
72
|
-
"devalue@>=5.1.0 <5.6.2": ">=5.6.2",
|
|
73
|
-
"h3@<=1.15.4": ">=1.15.5",
|
|
74
|
-
"koa@>=2.16.2 <2.16.3": ">=2.16.3",
|
|
75
|
-
"lodash-es@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
76
|
-
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
77
|
-
"diff@>=5.0.0 <5.2.2": ">=5.2.2"
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
"scripts": {
|
|
81
|
-
"build": "rollup -c",
|
|
82
|
-
"test": "vitest run --passWithNoTests",
|
|
83
|
-
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
84
|
-
"test:coverage:ci": "vitest run --coverage --reporter=verbose --passWithNoTests",
|
|
85
|
-
"prepublishOnly": "npm run build && npm run pack:strict",
|
|
86
|
-
"yara:node:smoke": "tsx scripts/yara-node-smoke.ts",
|
|
87
|
-
"yara:int:smoke": "tsx scripts/yara-integration-smoke.ts",
|
|
88
|
-
"dev:remote": "tsx examples/remote-yara-server.ts",
|
|
89
|
-
"docs:build": "hugo -s docs -D -d docs",
|
|
90
|
-
"predocs:deploy": "npm run docs:build",
|
|
91
|
-
"docs:deploy": "gh-pages -d docs -b gh-pages",
|
|
92
|
-
"format": "biome format --write .",
|
|
93
|
-
"format:check": "biome format .",
|
|
94
|
-
"lint": "biome ci .",
|
|
95
|
-
"lint:fix": "biome check --write .",
|
|
96
|
-
"yara:check": "node scripts/yara-quick-check-cli.mjs",
|
|
97
|
-
"build:core": "pnpm -r --filter '!./examples/*' --if-present build",
|
|
98
|
-
"preview": "npm pack --dry-run",
|
|
99
|
-
"typecheck": "tsc -p tsconfig.json --noEmit || tsc -p tsconfig.build.json --noEmit",
|
|
100
|
-
"typecheck:strict": "tsc -p tsconfig.strict.json --noEmit",
|
|
101
|
-
"smoke": "node scripts/smoke.mjs",
|
|
102
|
-
"test:e2e": "node scripts/e2e.mjs",
|
|
103
|
-
"repo:doctor": "pnpm install --frozen-lockfile && pnpm -r --if-present build && pnpm -r --if-present test && npm run -s preview || true && node scripts/smoke.mjs && node scripts/e2e.mjs || true",
|
|
104
|
-
"audit:deps": "depcheck --skip-missing true || true",
|
|
105
|
-
"audit:code": "knip --reporter compact || true",
|
|
106
|
-
"audit:exports": "ts-prune -p tsconfig.json || true",
|
|
107
|
-
"repo:audit": "node scripts/audit.mjs",
|
|
108
|
-
"pack:check": "node scripts/pack-check.mjs",
|
|
109
|
-
"pack:list": "pnpm -r --filter \"@pompelmi/*\" --if-present pack --json --dry-run",
|
|
110
|
-
"pack:strict": "node scripts/pack-check.mjs --strict",
|
|
111
|
-
"clean": "rimraf dist",
|
|
112
|
-
"mentions:find": "node scripts/find-mentions.mjs",
|
|
113
|
-
"mentions:render": "node scripts/render-mentions-readme.mjs",
|
|
114
|
-
"mentions:inject": "node scripts/inject-mentions-readme.mjs",
|
|
115
|
-
"mentions:update": "npm run mentions:find && npm run mentions:render && npm run mentions:inject"
|
|
116
|
-
},
|
|
117
|
-
"license": "MIT",
|
|
118
|
-
"devDependencies": {
|
|
119
|
-
"@biomejs/biome": "^2.2.4",
|
|
120
|
-
"@pompelmi/core": "workspace:*",
|
|
121
|
-
"@pompelmi/engine": "workspace:*",
|
|
122
|
-
"@pompelmi/engine-heuristics": "workspace:^0.2.0",
|
|
123
|
-
"@rollup/plugin-commonjs": "^29.0.2",
|
|
124
|
-
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
125
|
-
"@rollup/plugin-typescript": "^12.1.4",
|
|
126
|
-
"@types/cors": "^2.8.19",
|
|
127
|
-
"@types/express": "^5.0.3",
|
|
128
|
-
"@types/multer": "^2.0.0",
|
|
129
|
-
"@types/node": "^25.5.0",
|
|
130
|
-
"@types/react": "^19.1.8",
|
|
131
|
-
"@types/unzipper": "^0.10.11",
|
|
132
|
-
"@vitest/coverage-v8": "^4",
|
|
133
|
-
"cors": "^2.8.5",
|
|
134
|
-
"depcheck": "^1.4.7",
|
|
135
|
-
"express": "^5.1.0",
|
|
136
|
-
"gh-pages": "^6.3.0",
|
|
137
|
-
"knip": "^6.1.1",
|
|
138
|
-
"multer": "^2.0.2",
|
|
139
|
-
"react": "^19.2.0",
|
|
140
|
-
"rollup": "^4.x",
|
|
141
|
-
"ts-prune": "^0.10.3",
|
|
142
|
-
"tslib": "^2.8.1",
|
|
143
|
-
"tsup": "^8",
|
|
144
|
-
"tsx": "^4.20.3",
|
|
145
|
-
"typescript": "^6.0.2",
|
|
146
|
-
"vitest": "4.1.2"
|
|
147
|
-
},
|
|
148
|
-
"peerDependencies": {
|
|
149
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
150
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
151
|
-
},
|
|
152
|
-
"peerDependenciesMeta": {
|
|
153
|
-
"react": {
|
|
154
|
-
"optional": true
|
|
155
|
-
},
|
|
156
|
-
"react-dom": {
|
|
157
|
-
"optional": true
|
|
158
|
-
}
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pompelmi/pompelmi/issues"
|
|
159
14
|
},
|
|
160
|
-
"optionalDependencies": {
|
|
161
|
-
"@litko/yara-x": "^0.2.1"
|
|
162
|
-
},
|
|
163
|
-
"exports": {
|
|
164
|
-
".": {
|
|
165
|
-
"types": "./dist/types/index.d.ts",
|
|
166
|
-
"import": "./dist/pompelmi.esm.js",
|
|
167
|
-
"require": "./dist/pompelmi.cjs",
|
|
168
|
-
"default": "./dist/pompelmi.esm.js"
|
|
169
|
-
},
|
|
170
|
-
"./node": {
|
|
171
|
-
"types": "./dist/types/index.d.ts",
|
|
172
|
-
"import": "./dist/pompelmi.esm.js",
|
|
173
|
-
"require": "./dist/pompelmi.cjs",
|
|
174
|
-
"default": "./dist/pompelmi.esm.js"
|
|
175
|
-
},
|
|
176
|
-
"./browser": {
|
|
177
|
-
"types": "./dist/types/browser-index.d.ts",
|
|
178
|
-
"import": "./dist/pompelmi.browser.esm.js",
|
|
179
|
-
"require": "./dist/pompelmi.browser.cjs",
|
|
180
|
-
"default": "./dist/pompelmi.browser.esm.js"
|
|
181
|
-
},
|
|
182
|
-
"./react": {
|
|
183
|
-
"types": "./dist/types/react-index.d.ts",
|
|
184
|
-
"import": "./dist/pompelmi.react.esm.js",
|
|
185
|
-
"require": "./dist/pompelmi.react.cjs",
|
|
186
|
-
"default": "./dist/pompelmi.react.esm.js"
|
|
187
|
-
},
|
|
188
|
-
"./quarantine": {
|
|
189
|
-
"types": "./dist/types/quarantine/index.d.ts",
|
|
190
|
-
"import": "./dist/pompelmi.quarantine.esm.js",
|
|
191
|
-
"require": "./dist/pompelmi.quarantine.cjs",
|
|
192
|
-
"default": "./dist/pompelmi.quarantine.esm.js"
|
|
193
|
-
},
|
|
194
|
-
"./hooks": {
|
|
195
|
-
"types": "./dist/types/hooks.d.ts",
|
|
196
|
-
"import": "./dist/pompelmi.hooks.esm.js",
|
|
197
|
-
"require": "./dist/pompelmi.hooks.cjs",
|
|
198
|
-
"default": "./dist/pompelmi.hooks.esm.js"
|
|
199
|
-
},
|
|
200
|
-
"./audit": {
|
|
201
|
-
"types": "./dist/types/audit.d.ts",
|
|
202
|
-
"import": "./dist/pompelmi.audit.esm.js",
|
|
203
|
-
"require": "./dist/pompelmi.audit.cjs",
|
|
204
|
-
"default": "./dist/pompelmi.audit.esm.js"
|
|
205
|
-
},
|
|
206
|
-
"./policy-packs": {
|
|
207
|
-
"types": "./dist/types/policy-packs.d.ts",
|
|
208
|
-
"import": "./dist/pompelmi.policy-packs.esm.js",
|
|
209
|
-
"require": "./dist/pompelmi.policy-packs.cjs",
|
|
210
|
-
"default": "./dist/pompelmi.policy-packs.esm.js"
|
|
211
|
-
},
|
|
212
|
-
"./package.json": "./package.json"
|
|
213
|
-
},
|
|
214
|
-
"files": [
|
|
215
|
-
"dist/",
|
|
216
|
-
"README.md",
|
|
217
|
-
"LICENSE",
|
|
218
|
-
"package.json",
|
|
219
|
-
"CHANGELOG*"
|
|
220
|
-
],
|
|
221
15
|
"keywords": [
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
"mime-sniffing",
|
|
230
|
-
"magic-bytes",
|
|
231
|
-
"archive-security",
|
|
232
|
-
"zip-bomb-protection",
|
|
233
|
-
"yara",
|
|
234
|
-
"yara-rules",
|
|
235
|
-
"local-first-security",
|
|
236
|
-
"self-hosted-security",
|
|
237
|
-
"nodejs-security",
|
|
238
|
-
"typescript",
|
|
239
|
-
"nodejs",
|
|
240
|
-
"express",
|
|
241
|
-
"nextjs",
|
|
242
|
-
"nestjs",
|
|
243
|
-
"fastify",
|
|
244
|
-
"koa",
|
|
245
|
-
"nuxt"
|
|
16
|
+
"clamav",
|
|
17
|
+
"antivirus",
|
|
18
|
+
"malware",
|
|
19
|
+
"virus",
|
|
20
|
+
"scan",
|
|
21
|
+
"security",
|
|
22
|
+
"file-scan"
|
|
246
23
|
],
|
|
247
|
-
"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"resolutions": {
|
|
253
|
-
"process": "0.11.10"
|
|
254
|
-
},
|
|
255
|
-
"sideEffects": false,
|
|
256
|
-
"engines": {
|
|
257
|
-
"node": ">=18"
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"main": "./src/index.js",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node --test test/unit.test.js && node test/scan.test.js",
|
|
28
|
+
"lint": "eslint src/"
|
|
258
29
|
},
|
|
259
|
-
"
|
|
260
|
-
"
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"cross-spawn": "^7.0.6"
|
|
261
32
|
},
|
|
262
|
-
"
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@eslint/js": "^10.0.1",
|
|
35
|
+
"eslint": "^10.2.0",
|
|
36
|
+
"globals": "^17.4.0"
|
|
37
|
+
}
|
|
263
38
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const spawn = require("cross-spawn");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { getUpdaterCommand } = require('./InstallerCommand.js');
|
|
4
|
+
const { PLATFORM } = require('./constants.js');
|
|
5
|
+
const { DB_PATHS } = require('./config.js');
|
|
6
|
+
|
|
7
|
+
const MESSAGES = {
|
|
8
|
+
DB_PRESENT: "Virus database already present, skipping.",
|
|
9
|
+
SUCCESS: "Database updated successfully!",
|
|
10
|
+
FAILURE: (code) => `Database update failed with exit code: ${code}`,
|
|
11
|
+
STARTING: "Downloading virus definitions...",
|
|
12
|
+
PLATFORM_NOT_SUPPORTED: "Current platform is not supported."
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function isDatabasePresent() {
|
|
16
|
+
const dbPath = DB_PATHS[PLATFORM];
|
|
17
|
+
return dbPath ? fs.existsSync(dbPath) : false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function updateClamAVDatabase() {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
if (isDatabasePresent()) {
|
|
23
|
+
console.log(MESSAGES.DB_PRESENT);
|
|
24
|
+
return resolve(MESSAGES.DB_PRESENT);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [command, args] = getUpdaterCommand(PLATFORM);
|
|
28
|
+
|
|
29
|
+
if (!command) {
|
|
30
|
+
console.log(MESSAGES.PLATFORM_NOT_SUPPORTED);
|
|
31
|
+
return resolve(MESSAGES.PLATFORM_NOT_SUPPORTED);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(MESSAGES.STARTING);
|
|
35
|
+
|
|
36
|
+
const child = spawn(command, args, { stdio: 'inherit' });
|
|
37
|
+
child.on('error', (err) => reject(err));
|
|
38
|
+
child.on('close', (code) => {
|
|
39
|
+
if (code !== 0) {
|
|
40
|
+
return reject(new Error(MESSAGES.FAILURE(code)));
|
|
41
|
+
}
|
|
42
|
+
console.log(MESSAGES.SUCCESS);
|
|
43
|
+
resolve(MESSAGES.SUCCESS);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { updateClamAVDatabase };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const spawn = require("cross-spawn");
|
|
2
|
+
const { execSync } = require("child_process");
|
|
3
|
+
const { getInstallerCommand } = require('./InstallerCommand.js');
|
|
4
|
+
const { PLATFORM } = require('./constants.js')
|
|
5
|
+
|
|
6
|
+
const MESSAGES = {
|
|
7
|
+
ALREADY_INSTALLED: "ClamAV is already installed, skipping.",
|
|
8
|
+
SUCCESS: "Installation completed successfully!",
|
|
9
|
+
PLATFORM_NOT_SUPPORTED: "Current platform is not supported.",
|
|
10
|
+
FAILURE: (code) => `Installation failed with exit code: ${code}`
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function isClamAVInstalled() {
|
|
14
|
+
const command = PLATFORM === 'win32' ? 'where clamscan' : 'which clamscan';
|
|
15
|
+
try {
|
|
16
|
+
execSync(command, { stdio: 'ignore' });
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ClamAVInstaller() {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
if (isClamAVInstalled()) {
|
|
26
|
+
console.log(MESSAGES.ALREADY_INSTALLED);
|
|
27
|
+
return resolve(MESSAGES.ALREADY_INSTALLED);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const [packageManager, packageToInstall] = getInstallerCommand(PLATFORM);
|
|
31
|
+
|
|
32
|
+
if (!packageManager) {
|
|
33
|
+
console.log(MESSAGES.PLATFORM_NOT_SUPPORTED);
|
|
34
|
+
return resolve(MESSAGES.PLATFORM_NOT_SUPPORTED);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const child = spawn(packageManager, packageToInstall, { stdio: 'inherit' });
|
|
38
|
+
child.on('error', (err) => reject(err));
|
|
39
|
+
child.on('close', (code) => {
|
|
40
|
+
if (code !== 0) {
|
|
41
|
+
return reject(new Error(MESSAGES.FAILURE(code)));
|
|
42
|
+
}
|
|
43
|
+
console.log(MESSAGES.SUCCESS);
|
|
44
|
+
resolve(MESSAGES.SUCCESS);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { ClamAVInstaller};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const spawn = require("cross-spawn");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { SCAN_RESULTS } = require('./config.js');
|
|
4
|
+
const { scanViaClamd } = require('./ClamdScanner.js');
|
|
5
|
+
|
|
6
|
+
const MESSAGES = {
|
|
7
|
+
FILE_NOT_FOUND: (filePath) => `File not found: ${filePath}`,
|
|
8
|
+
UNEXPECTED_EXIT_CODE: (code) => `Unexpected exit code: ${code}`,
|
|
9
|
+
PROCESS_KILLED: (signal) => `Process killed by signal: ${signal}`,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function scan(filePath, options = {}) {
|
|
13
|
+
// When a host or port is provided, delegate to the clamd TCP path.
|
|
14
|
+
if (options.host !== undefined || options.port !== undefined) {
|
|
15
|
+
return scanViaClamd(filePath, options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
if (typeof filePath !== 'string') {
|
|
20
|
+
return reject(new Error('filePath must be a string'));
|
|
21
|
+
}
|
|
22
|
+
if (!fs.existsSync(filePath)) {
|
|
23
|
+
return reject(new Error(MESSAGES.FILE_NOT_FOUND(filePath)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const child = spawn('clamscan', ['--no-summary', filePath]);
|
|
27
|
+
child.on('error', (err) => reject(err));
|
|
28
|
+
child.on('close', (code, signal) => {
|
|
29
|
+
if (code === null) return reject(new Error(MESSAGES.PROCESS_KILLED(signal)));
|
|
30
|
+
const result = SCAN_RESULTS[code];
|
|
31
|
+
if (!result) return reject(new Error(MESSAGES.UNEXPECTED_EXIT_CODE(code)));
|
|
32
|
+
resolve(result);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { scan };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const net = require('net');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// ClamAV INSTREAM protocol:
|
|
7
|
+
// 1. Send "zINSTREAM\0"
|
|
8
|
+
// 2. Send N chunks, each prefixed with a 4-byte big-endian length
|
|
9
|
+
// 3. Terminate with four zero bytes
|
|
10
|
+
// 4. Read response line: "stream: OK", "stream: <name> FOUND", or an error
|
|
11
|
+
const CLAMD_INSTREAM = Buffer.from('zINSTREAM\0');
|
|
12
|
+
const CHUNK_SIZE = 64 * 1024; // 64 KB — well within clamd's default StreamMaxLength
|
|
13
|
+
|
|
14
|
+
function parseClamdResponse(raw) {
|
|
15
|
+
const text = raw.toString('utf8').trim();
|
|
16
|
+
if (text === 'stream: OK') return 'Clean';
|
|
17
|
+
if (text.endsWith(' FOUND')) return 'Malicious';
|
|
18
|
+
return 'ScanError';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scan a file by streaming it to a running clamd instance over TCP.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} filePath - Absolute or relative path to the file to scan.
|
|
25
|
+
* @param {object} [options]
|
|
26
|
+
* @param {string} [options.host='127.0.0.1']
|
|
27
|
+
* @param {number} [options.port=3310]
|
|
28
|
+
* @param {number} [options.timeout=15000] - Socket idle timeout in ms.
|
|
29
|
+
* @returns {Promise<'Clean'|'Malicious'|'ScanError'>}
|
|
30
|
+
*/
|
|
31
|
+
function scanViaClamd(filePath, { host = '127.0.0.1', port = 3310, timeout = 15_000 } = {}) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
if (typeof filePath !== 'string') {
|
|
34
|
+
return reject(new Error('filePath must be a string'));
|
|
35
|
+
}
|
|
36
|
+
if (!fs.existsSync(filePath)) {
|
|
37
|
+
return reject(new Error(`File not found: ${filePath}`));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const socket = net.createConnection({ host, port });
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let settled = false;
|
|
43
|
+
|
|
44
|
+
function settle(fn, value) {
|
|
45
|
+
if (settled) return;
|
|
46
|
+
settled = true;
|
|
47
|
+
socket.destroy();
|
|
48
|
+
fn(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
socket.setTimeout(timeout);
|
|
52
|
+
socket.on('timeout', () =>
|
|
53
|
+
settle(reject, new Error(`clamd connection timed out after ${timeout}ms`))
|
|
54
|
+
);
|
|
55
|
+
socket.on('error', (err) => settle(reject, err));
|
|
56
|
+
socket.on('data', (chunk) => chunks.push(chunk));
|
|
57
|
+
socket.on('end', () => settle(resolve, parseClamdResponse(Buffer.concat(chunks))));
|
|
58
|
+
|
|
59
|
+
socket.on('connect', () => {
|
|
60
|
+
socket.write(CLAMD_INSTREAM);
|
|
61
|
+
|
|
62
|
+
const fileStream = fs.createReadStream(filePath, { highWaterMark: CHUNK_SIZE });
|
|
63
|
+
|
|
64
|
+
fileStream.on('error', (err) => settle(reject, err));
|
|
65
|
+
|
|
66
|
+
fileStream.on('data', (chunk) => {
|
|
67
|
+
const header = Buffer.allocUnsafe(4);
|
|
68
|
+
header.writeUInt32BE(chunk.length, 0);
|
|
69
|
+
socket.write(header);
|
|
70
|
+
socket.write(chunk);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
fileStream.on('end', () => {
|
|
74
|
+
socket.write(Buffer.alloc(4)); // terminating zero-length chunk
|
|
75
|
+
socket.end();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { scanViaClamd };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { INSTALLER_COMMANDS, UPDATER_COMMANDS } = require('./config.js');
|
|
2
|
+
|
|
3
|
+
function getInstallerCommand(platform) {
|
|
4
|
+
return INSTALLER_COMMANDS[platform] ?? [null, []];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function getUpdaterCommand(platform) {
|
|
8
|
+
return UPDATER_COMMANDS[platform] ?? [null, []];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { getInstallerCommand, getUpdaterCommand };
|
package/src/config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = Object.freeze({
|
|
2
|
+
INSTALLER_COMMANDS: Object.freeze({
|
|
3
|
+
win32: ['choco', ['install', 'clamav', '-y']],
|
|
4
|
+
darwin: ['brew', ['install', 'clamav']],
|
|
5
|
+
linux: ['sudo', ['apt-get', 'install', '-y', 'clamav', 'clamav-daemon']],
|
|
6
|
+
}),
|
|
7
|
+
UPDATER_COMMANDS: Object.freeze({
|
|
8
|
+
win32: ['freshclam', []],
|
|
9
|
+
darwin: ['freshclam', []],
|
|
10
|
+
linux: ['sudo', ['freshclam']],
|
|
11
|
+
}),
|
|
12
|
+
DB_PATHS: Object.freeze({
|
|
13
|
+
darwin: '/usr/local/share/clamav/main.cvd',
|
|
14
|
+
linux: '/var/lib/clamav/main.cvd',
|
|
15
|
+
win32: 'C:\\ProgramData\\ClamAV\\main.cvd',
|
|
16
|
+
}),
|
|
17
|
+
SCAN_RESULTS: Object.freeze({
|
|
18
|
+
0: 'Clean',
|
|
19
|
+
1: 'Malicious',
|
|
20
|
+
2: 'ScanError'
|
|
21
|
+
}),
|
|
22
|
+
});
|
package/src/constants.js
ADDED
package/src/favicon.ico
ADDED
|
Binary file
|
|
Binary file
|
package/src/index.js
ADDED
package/test_out.txt
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
ClamAV is already installed, skipping.
|
|
2
|
+
Current platform is not supported.
|
|
3
|
+
Installation completed successfully!
|
|
4
|
+
Virus database already present, skipping.
|
|
5
|
+
Current platform is not supported.
|
|
6
|
+
Downloading virus definitions...
|
|
7
|
+
Database updated successfully!
|
|
8
|
+
Downloading virus definitions...
|
|
9
|
+
Downloading virus definitions...
|
|
10
|
+
▶ InstallerCommand
|
|
11
|
+
▶ getInstallerCommand
|
|
12
|
+
✔ darwin → brew install clamav (1.172833ms)
|
|
13
|
+
✔ linux → sudo apt-get install (0.163833ms)
|
|
14
|
+
✔ win32 → choco install clamav (0.07125ms)
|
|
15
|
+
✔ unknown → [null, []] (0.054792ms)
|
|
16
|
+
✔ getInstallerCommand (1.877625ms)
|
|
17
|
+
▶ getUpdaterCommand
|
|
18
|
+
✔ darwin → freshclam (0.094625ms)
|
|
19
|
+
✔ linux → sudo freshclam (0.255ms)
|
|
20
|
+
✔ win32 → freshclam (0.055834ms)
|
|
21
|
+
✔ unknown → [null, []] (0.308416ms)
|
|
22
|
+
✔ getUpdaterCommand (0.965833ms)
|
|
23
|
+
✔ InstallerCommand (3.261333ms)
|
|
24
|
+
▶ ClamAVScanner
|
|
25
|
+
✔ rejects if filePath is not a string (10.062042ms)
|
|
26
|
+
✔ rejects if file does not exist (0.441042ms)
|
|
27
|
+
✔ exit code 0 → resolves to "Clean" (0.847959ms)
|
|
28
|
+
✔ exit code 1 → resolves to "Malicious" (0.890875ms)
|
|
29
|
+
✔ exit code 2 → resolves to "ScanError" (0.5805ms)
|
|
30
|
+
✔ exit code 99 → rejects with exit code message (0.535625ms)
|
|
31
|
+
✔ spawn error → rejects with the error (0.550542ms)
|
|
32
|
+
✔ signal kill → rejects with signal name (0.405667ms)
|
|
33
|
+
✔ ClamAVScanner (14.502167ms)
|
|
34
|
+
▶ ClamAVInstaller
|
|
35
|
+
✔ resolves if ClamAV is already installed (1.17225ms)
|
|
36
|
+
✔ resolves with platform-not-supported message (1.279459ms)
|
|
37
|
+
✔ resolves after successful installation (0.755125ms)
|
|
38
|
+
✔ rejects when installation exits non-zero (0.553375ms)
|
|
39
|
+
✔ rejects on spawn error (0.969208ms)
|
|
40
|
+
✔ ClamAVInstaller (4.845042ms)
|
|
41
|
+
▶ ClamdScanner
|
|
42
|
+
✔ rejects if filePath is not a string (0.186125ms)
|
|
43
|
+
✔ rejects if file does not exist (0.371125ms)
|
|
44
|
+
✔ "stream: OK" → "Clean" (0.388458ms)
|
|
45
|
+
✔ "stream: ... FOUND" → "Malicious" (0.163792ms)
|
|
46
|
+
✔ any other clamd response → "ScanError" (0.294458ms)
|
|
47
|
+
✔ socket error (ECONNREFUSED) → rejects with that error (0.187ms)
|
|
48
|
+
✔ socket timeout → rejects with timeout message (0.119208ms)
|
|
49
|
+
✔ file read stream error → rejects with that error (0.118084ms)
|
|
50
|
+
✔ sends zINSTREAM command, chunk header, chunk data, and terminator (0.327083ms)
|
|
51
|
+
✔ ClamdScanner (2.320125ms)
|
|
52
|
+
▶ ClamAVScanner (TCP routing)
|
|
53
|
+
✔ routes to clamd when { port } is given (2.303ms)
|
|
54
|
+
✔ routes to clamd when { host } is given (0.207792ms)
|
|
55
|
+
✔ routes to clamd when { host, port } are both given (0.27525ms)
|
|
56
|
+
✔ forwards filePath and options unchanged to scanViaClamd (0.267083ms)
|
|
57
|
+
✔ uses the CLI path when called without options (0.31125ms)
|
|
58
|
+
✔ uses the CLI path when called with an empty options object {} (0.318041ms)
|
|
59
|
+
✔ ClamAVScanner (TCP routing) (3.793958ms)
|
|
60
|
+
▶ ClamAVDatabaseUpdater
|
|
61
|
+
✔ resolves if database is already present (0.793833ms)
|
|
62
|
+
✔ resolves with platform-not-supported message (0.326166ms)
|
|
63
|
+
✔ resolves after successful update (0.37675ms)
|
|
64
|
+
✔ rejects when update exits non-zero (0.330167ms)
|
|
65
|
+
✔ rejects on spawn error (0.545833ms)
|
|
66
|
+
✔ ClamAVDatabaseUpdater (2.478542ms)
|
|
67
|
+
ℹ tests 41
|
|
68
|
+
ℹ suites 8
|
|
69
|
+
ℹ pass 41
|
|
70
|
+
ℹ fail 0
|
|
71
|
+
ℹ cancelled 0
|
|
72
|
+
ℹ skipped 0
|
|
73
|
+
ℹ todo 0
|
|
74
|
+
ℹ duration_ms 107.87375
|