pompelmi 0.32.1 → 0.33.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/README.md +36 -2
- package/dist/pompelmi.cjs +77 -81
- package/dist/pompelmi.cjs.map +1 -1
- package/dist/pompelmi.esm.js +77 -81
- package/dist/pompelmi.esm.js.map +1 -1
- package/dist/types/scanners/common-heuristics.d.ts +2 -2
- package/package.json +2 -2
package/dist/pompelmi.esm.js
CHANGED
|
@@ -3,6 +3,81 @@ import { createHash } from 'crypto';
|
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
|
|
6
|
+
function hasAsciiToken(buf, token) {
|
|
7
|
+
// Use latin1 so we can safely search binary
|
|
8
|
+
return buf.indexOf(token, 0, 'latin1') !== -1;
|
|
9
|
+
}
|
|
10
|
+
function startsWith(buf, bytes) {
|
|
11
|
+
if (buf.length < bytes.length)
|
|
12
|
+
return false;
|
|
13
|
+
for (let i = 0; i < bytes.length; i++)
|
|
14
|
+
if (buf[i] !== bytes[i])
|
|
15
|
+
return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function isPDF(buf) {
|
|
19
|
+
// %PDF-
|
|
20
|
+
return startsWith(buf, [0x25, 0x50, 0x44, 0x46, 0x2d]);
|
|
21
|
+
}
|
|
22
|
+
function isOleCfb(buf) {
|
|
23
|
+
// D0 CF 11 E0 A1 B1 1A E1
|
|
24
|
+
const sig = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
|
|
25
|
+
return startsWith(buf, sig);
|
|
26
|
+
}
|
|
27
|
+
function isZipLike$1(buf) {
|
|
28
|
+
// PK\x03\x04
|
|
29
|
+
return startsWith(buf, [0x50, 0x4b, 0x03, 0x04]);
|
|
30
|
+
}
|
|
31
|
+
function isPeExecutable(buf) {
|
|
32
|
+
// "MZ"
|
|
33
|
+
return startsWith(buf, [0x4d, 0x5a]);
|
|
34
|
+
}
|
|
35
|
+
/** OOXML macro hint via filename token in ZIP container */
|
|
36
|
+
function hasOoxmlMacros(buf) {
|
|
37
|
+
if (!isZipLike$1(buf))
|
|
38
|
+
return false;
|
|
39
|
+
return hasAsciiToken(buf, 'vbaProject.bin');
|
|
40
|
+
}
|
|
41
|
+
/** PDF risky features (/JavaScript, /OpenAction, /AA, /Launch) */
|
|
42
|
+
function pdfRiskTokens(buf) {
|
|
43
|
+
const tokens = ['/JavaScript', '/OpenAction', '/AA', '/Launch'];
|
|
44
|
+
return tokens.filter(t => hasAsciiToken(buf, t));
|
|
45
|
+
}
|
|
46
|
+
const CommonHeuristicsScanner = {
|
|
47
|
+
async scan(input) {
|
|
48
|
+
const buf = Buffer.from(input);
|
|
49
|
+
const matches = [];
|
|
50
|
+
// Office macros (OLE / OOXML)
|
|
51
|
+
if (isOleCfb(buf)) {
|
|
52
|
+
matches.push({ rule: 'office_ole_container', severity: 'suspicious' });
|
|
53
|
+
}
|
|
54
|
+
if (hasOoxmlMacros(buf)) {
|
|
55
|
+
matches.push({ rule: 'office_ooxml_macros', severity: 'suspicious' });
|
|
56
|
+
}
|
|
57
|
+
// PDF risky tokens
|
|
58
|
+
if (isPDF(buf)) {
|
|
59
|
+
const toks = pdfRiskTokens(buf);
|
|
60
|
+
if (toks.length) {
|
|
61
|
+
matches.push({
|
|
62
|
+
rule: 'pdf_risky_actions',
|
|
63
|
+
severity: 'suspicious',
|
|
64
|
+
meta: { tokens: toks }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Executable header
|
|
69
|
+
if (isPeExecutable(buf)) {
|
|
70
|
+
matches.push({ rule: 'pe_executable_signature', severity: 'suspicious' });
|
|
71
|
+
}
|
|
72
|
+
// EICAR test file
|
|
73
|
+
const EICAR_NEEDLE = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!";
|
|
74
|
+
if (hasAsciiToken(buf, EICAR_NEEDLE)) {
|
|
75
|
+
matches.push({ rule: 'eicar_test_file', severity: 'high', meta: { note: 'EICAR standard antivirus test file detected' } });
|
|
76
|
+
}
|
|
77
|
+
return matches;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
6
81
|
function toScanFn(s) {
|
|
7
82
|
return (typeof s === "function" ? s : s.scan);
|
|
8
83
|
}
|
|
@@ -113,6 +188,8 @@ function composeScanners(...args) {
|
|
|
113
188
|
}
|
|
114
189
|
function createPresetScanner(preset, opts = {}) {
|
|
115
190
|
const scanners = [];
|
|
191
|
+
// Always include heuristics (EICAR, PHP webshells, JS obfuscation, PE hints, etc.)
|
|
192
|
+
scanners.push(CommonHeuristicsScanner);
|
|
116
193
|
// Add decompilation scanners based on preset
|
|
117
194
|
if (preset === 'decompilation-basic' || preset === 'decompilation-deep' ||
|
|
118
195
|
preset === 'malware-analysis' || opts.enableDecompilation) {
|
|
@@ -160,17 +237,6 @@ function createPresetScanner(preset, opts = {}) {
|
|
|
160
237
|
}
|
|
161
238
|
}
|
|
162
239
|
}
|
|
163
|
-
// Add other scanners for advanced presets
|
|
164
|
-
if (preset === 'advanced' || preset === 'malware-analysis') {
|
|
165
|
-
// Add heuristics scanner
|
|
166
|
-
try {
|
|
167
|
-
const { CommonHeuristicsScanner } = require('./scanners/common-heuristics');
|
|
168
|
-
scanners.push(new CommonHeuristicsScanner());
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// Heuristics not available
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
240
|
if (scanners.length === 0) {
|
|
175
241
|
// Fallback scanner that returns no matches
|
|
176
242
|
return async (_input, _ctx) => {
|
|
@@ -3121,76 +3187,6 @@ function mapMatchesToVerdict(matches = []) {
|
|
|
3121
3187
|
return isMal ? 'malicious' : 'suspicious';
|
|
3122
3188
|
}
|
|
3123
3189
|
|
|
3124
|
-
function hasAsciiToken(buf, token) {
|
|
3125
|
-
// Use latin1 so we can safely search binary
|
|
3126
|
-
return buf.indexOf(token, 0, 'latin1') !== -1;
|
|
3127
|
-
}
|
|
3128
|
-
function startsWith(buf, bytes) {
|
|
3129
|
-
if (buf.length < bytes.length)
|
|
3130
|
-
return false;
|
|
3131
|
-
for (let i = 0; i < bytes.length; i++)
|
|
3132
|
-
if (buf[i] !== bytes[i])
|
|
3133
|
-
return false;
|
|
3134
|
-
return true;
|
|
3135
|
-
}
|
|
3136
|
-
function isPDF(buf) {
|
|
3137
|
-
// %PDF-
|
|
3138
|
-
return startsWith(buf, [0x25, 0x50, 0x44, 0x46, 0x2d]);
|
|
3139
|
-
}
|
|
3140
|
-
function isOleCfb(buf) {
|
|
3141
|
-
// D0 CF 11 E0 A1 B1 1A E1
|
|
3142
|
-
const sig = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
|
|
3143
|
-
return startsWith(buf, sig);
|
|
3144
|
-
}
|
|
3145
|
-
function isZipLike$1(buf) {
|
|
3146
|
-
// PK\x03\x04
|
|
3147
|
-
return startsWith(buf, [0x50, 0x4b, 0x03, 0x04]);
|
|
3148
|
-
}
|
|
3149
|
-
function isPeExecutable(buf) {
|
|
3150
|
-
// "MZ"
|
|
3151
|
-
return startsWith(buf, [0x4d, 0x5a]);
|
|
3152
|
-
}
|
|
3153
|
-
/** OOXML macro hint via filename token in ZIP container */
|
|
3154
|
-
function hasOoxmlMacros(buf) {
|
|
3155
|
-
if (!isZipLike$1(buf))
|
|
3156
|
-
return false;
|
|
3157
|
-
return hasAsciiToken(buf, 'vbaProject.bin');
|
|
3158
|
-
}
|
|
3159
|
-
/** PDF risky features (/JavaScript, /OpenAction, /AA, /Launch) */
|
|
3160
|
-
function pdfRiskTokens(buf) {
|
|
3161
|
-
const tokens = ['/JavaScript', '/OpenAction', '/AA', '/Launch'];
|
|
3162
|
-
return tokens.filter(t => hasAsciiToken(buf, t));
|
|
3163
|
-
}
|
|
3164
|
-
const CommonHeuristicsScanner = {
|
|
3165
|
-
async scan(input) {
|
|
3166
|
-
const buf = Buffer.from(input);
|
|
3167
|
-
const matches = [];
|
|
3168
|
-
// Office macros (OLE / OOXML)
|
|
3169
|
-
if (isOleCfb(buf)) {
|
|
3170
|
-
matches.push({ rule: 'office_ole_container', severity: 'suspicious' });
|
|
3171
|
-
}
|
|
3172
|
-
if (hasOoxmlMacros(buf)) {
|
|
3173
|
-
matches.push({ rule: 'office_ooxml_macros', severity: 'suspicious' });
|
|
3174
|
-
}
|
|
3175
|
-
// PDF risky tokens
|
|
3176
|
-
if (isPDF(buf)) {
|
|
3177
|
-
const toks = pdfRiskTokens(buf);
|
|
3178
|
-
if (toks.length) {
|
|
3179
|
-
matches.push({
|
|
3180
|
-
rule: 'pdf_risky_actions',
|
|
3181
|
-
severity: 'suspicious',
|
|
3182
|
-
meta: { tokens: toks }
|
|
3183
|
-
});
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
// Executable header
|
|
3187
|
-
if (isPeExecutable(buf)) {
|
|
3188
|
-
matches.push({ rule: 'pe_executable_signature', severity: 'suspicious' });
|
|
3189
|
-
}
|
|
3190
|
-
return matches;
|
|
3191
|
-
}
|
|
3192
|
-
};
|
|
3193
|
-
|
|
3194
3190
|
const SIG_CEN = 0x02014b50;
|
|
3195
3191
|
const DEFAULTS = {
|
|
3196
3192
|
maxEntries: 1000,
|