pompelmi 0.10.0 → 0.11.0-dev.11
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/dist/pompelmi.cjs.js +181 -0
- package/dist/pompelmi.cjs.js.map +1 -1
- package/dist/pompelmi.esm.js +180 -1
- package/dist/pompelmi.esm.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
package/dist/pompelmi.esm.js
CHANGED
|
@@ -3100,5 +3100,184 @@ function mapMatchesToVerdict(matches = []) {
|
|
|
3100
3100
|
return isMal ? 'malicious' : 'suspicious';
|
|
3101
3101
|
}
|
|
3102
3102
|
|
|
3103
|
-
|
|
3103
|
+
function hasAsciiToken(buf, token) {
|
|
3104
|
+
// Use latin1 so we can safely search binary
|
|
3105
|
+
return buf.indexOf(token, 0, 'latin1') !== -1;
|
|
3106
|
+
}
|
|
3107
|
+
function startsWith(buf, bytes) {
|
|
3108
|
+
if (buf.length < bytes.length)
|
|
3109
|
+
return false;
|
|
3110
|
+
for (let i = 0; i < bytes.length; i++)
|
|
3111
|
+
if (buf[i] !== bytes[i])
|
|
3112
|
+
return false;
|
|
3113
|
+
return true;
|
|
3114
|
+
}
|
|
3115
|
+
function isPDF(buf) {
|
|
3116
|
+
// %PDF-
|
|
3117
|
+
return startsWith(buf, [0x25, 0x50, 0x44, 0x46, 0x2d]);
|
|
3118
|
+
}
|
|
3119
|
+
function isOleCfb(buf) {
|
|
3120
|
+
// D0 CF 11 E0 A1 B1 1A E1
|
|
3121
|
+
const sig = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
|
|
3122
|
+
return startsWith(buf, sig);
|
|
3123
|
+
}
|
|
3124
|
+
function isZipLike$1(buf) {
|
|
3125
|
+
// PK\x03\x04
|
|
3126
|
+
return startsWith(buf, [0x50, 0x4b, 0x03, 0x04]);
|
|
3127
|
+
}
|
|
3128
|
+
function isPeExecutable(buf) {
|
|
3129
|
+
// "MZ"
|
|
3130
|
+
return startsWith(buf, [0x4d, 0x5a]);
|
|
3131
|
+
}
|
|
3132
|
+
/** OOXML macro hint via filename token in ZIP container */
|
|
3133
|
+
function hasOoxmlMacros(buf) {
|
|
3134
|
+
if (!isZipLike$1(buf))
|
|
3135
|
+
return false;
|
|
3136
|
+
return hasAsciiToken(buf, 'vbaProject.bin');
|
|
3137
|
+
}
|
|
3138
|
+
/** PDF risky features (/JavaScript, /OpenAction, /AA, /Launch) */
|
|
3139
|
+
function pdfRiskTokens(buf) {
|
|
3140
|
+
const tokens = ['/JavaScript', '/OpenAction', '/AA', '/Launch'];
|
|
3141
|
+
return tokens.filter(t => hasAsciiToken(buf, t));
|
|
3142
|
+
}
|
|
3143
|
+
const CommonHeuristicsScanner = {
|
|
3144
|
+
async scan(input) {
|
|
3145
|
+
const buf = Buffer.from(input);
|
|
3146
|
+
const matches = [];
|
|
3147
|
+
// Office macros (OLE / OOXML)
|
|
3148
|
+
if (isOleCfb(buf)) {
|
|
3149
|
+
matches.push({ rule: 'office_ole_container', severity: 'suspicious' });
|
|
3150
|
+
}
|
|
3151
|
+
if (hasOoxmlMacros(buf)) {
|
|
3152
|
+
matches.push({ rule: 'office_ooxml_macros', severity: 'suspicious' });
|
|
3153
|
+
}
|
|
3154
|
+
// PDF risky tokens
|
|
3155
|
+
if (isPDF(buf)) {
|
|
3156
|
+
const toks = pdfRiskTokens(buf);
|
|
3157
|
+
if (toks.length) {
|
|
3158
|
+
matches.push({
|
|
3159
|
+
rule: 'pdf_risky_actions',
|
|
3160
|
+
severity: 'suspicious',
|
|
3161
|
+
meta: { tokens: toks }
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
// Executable header
|
|
3166
|
+
if (isPeExecutable(buf)) {
|
|
3167
|
+
matches.push({ rule: 'pe_executable_signature', severity: 'suspicious' });
|
|
3168
|
+
}
|
|
3169
|
+
return matches;
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
|
|
3173
|
+
const SIG_CEN = 0x02014b50;
|
|
3174
|
+
const DEFAULTS = {
|
|
3175
|
+
maxEntries: 1000,
|
|
3176
|
+
maxTotalUncompressedBytes: 500 * 1024 * 1024,
|
|
3177
|
+
maxEntryNameLength: 255,
|
|
3178
|
+
maxCompressionRatio: 1000,
|
|
3179
|
+
eocdSearchWindow: 70000,
|
|
3180
|
+
};
|
|
3181
|
+
function r16(buf, off) {
|
|
3182
|
+
return buf.readUInt16LE(off);
|
|
3183
|
+
}
|
|
3184
|
+
function r32(buf, off) {
|
|
3185
|
+
return buf.readUInt32LE(off);
|
|
3186
|
+
}
|
|
3187
|
+
function isZipLike(buf) {
|
|
3188
|
+
// local file header at start is common
|
|
3189
|
+
return buf.length >= 4 && buf[0] === 0x50 && buf[1] === 0x4b && buf[2] === 0x03 && buf[3] === 0x04;
|
|
3190
|
+
}
|
|
3191
|
+
function lastIndexOfEOCD(buf, window) {
|
|
3192
|
+
const sig = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
|
|
3193
|
+
const start = Math.max(0, buf.length - window);
|
|
3194
|
+
return buf.lastIndexOf(sig, buf.length - 1, start);
|
|
3195
|
+
}
|
|
3196
|
+
function hasTraversal(name) {
|
|
3197
|
+
return name.includes('../') || name.includes('..\\') || name.startsWith('/') || /^[A-Za-z]:/.test(name);
|
|
3198
|
+
}
|
|
3199
|
+
function createZipBombGuard(opts = {}) {
|
|
3200
|
+
const cfg = { ...DEFAULTS, ...opts };
|
|
3201
|
+
return {
|
|
3202
|
+
async scan(input) {
|
|
3203
|
+
const buf = Buffer.from(input);
|
|
3204
|
+
const matches = [];
|
|
3205
|
+
if (!isZipLike(buf))
|
|
3206
|
+
return matches;
|
|
3207
|
+
// Find EOCD near the end
|
|
3208
|
+
const eocdPos = lastIndexOfEOCD(buf, cfg.eocdSearchWindow);
|
|
3209
|
+
if (eocdPos < 0 || eocdPos + 22 > buf.length) {
|
|
3210
|
+
// ZIP but no EOCD — malformed or polyglot → suspicious
|
|
3211
|
+
matches.push({ rule: 'zip_eocd_not_found', severity: 'suspicious' });
|
|
3212
|
+
return matches;
|
|
3213
|
+
}
|
|
3214
|
+
const totalEntries = r16(buf, eocdPos + 10);
|
|
3215
|
+
const cdSize = r32(buf, eocdPos + 12);
|
|
3216
|
+
const cdOffset = r32(buf, eocdPos + 16);
|
|
3217
|
+
// Bounds check
|
|
3218
|
+
if (cdOffset + cdSize > buf.length) {
|
|
3219
|
+
matches.push({ rule: 'zip_cd_out_of_bounds', severity: 'suspicious' });
|
|
3220
|
+
return matches;
|
|
3221
|
+
}
|
|
3222
|
+
// Iterate central directory entries
|
|
3223
|
+
let ptr = cdOffset;
|
|
3224
|
+
let seen = 0;
|
|
3225
|
+
let sumComp = 0;
|
|
3226
|
+
let sumUnc = 0;
|
|
3227
|
+
while (ptr + 46 <= cdOffset + cdSize && seen < totalEntries) {
|
|
3228
|
+
const sig = r32(buf, ptr);
|
|
3229
|
+
if (sig !== SIG_CEN)
|
|
3230
|
+
break; // stop if structure breaks
|
|
3231
|
+
const compSize = r32(buf, ptr + 20);
|
|
3232
|
+
const uncSize = r32(buf, ptr + 24);
|
|
3233
|
+
const fnLen = r16(buf, ptr + 28);
|
|
3234
|
+
const exLen = r16(buf, ptr + 30);
|
|
3235
|
+
const cmLen = r16(buf, ptr + 32);
|
|
3236
|
+
const nameStart = ptr + 46;
|
|
3237
|
+
const nameEnd = nameStart + fnLen;
|
|
3238
|
+
if (nameEnd > buf.length)
|
|
3239
|
+
break;
|
|
3240
|
+
const name = buf.toString('utf8', nameStart, nameEnd);
|
|
3241
|
+
sumComp += compSize;
|
|
3242
|
+
sumUnc += uncSize;
|
|
3243
|
+
seen++;
|
|
3244
|
+
if (name.length > cfg.maxEntryNameLength) {
|
|
3245
|
+
matches.push({ rule: 'zip_entry_name_too_long', severity: 'suspicious', meta: { name, length: name.length } });
|
|
3246
|
+
}
|
|
3247
|
+
if (hasTraversal(name)) {
|
|
3248
|
+
matches.push({ rule: 'zip_path_traversal_entry', severity: 'suspicious', meta: { name } });
|
|
3249
|
+
}
|
|
3250
|
+
// move to next entry
|
|
3251
|
+
ptr = nameEnd + exLen + cmLen;
|
|
3252
|
+
}
|
|
3253
|
+
if (seen !== totalEntries) {
|
|
3254
|
+
// central dir truncated/odd, still report what we found
|
|
3255
|
+
matches.push({ rule: 'zip_cd_truncated', severity: 'suspicious', meta: { seen, totalEntries } });
|
|
3256
|
+
}
|
|
3257
|
+
// Heuristics thresholds
|
|
3258
|
+
if (seen > cfg.maxEntries) {
|
|
3259
|
+
matches.push({ rule: 'zip_too_many_entries', severity: 'suspicious', meta: { seen, limit: cfg.maxEntries } });
|
|
3260
|
+
}
|
|
3261
|
+
if (sumUnc > cfg.maxTotalUncompressedBytes) {
|
|
3262
|
+
matches.push({
|
|
3263
|
+
rule: 'zip_total_uncompressed_too_large',
|
|
3264
|
+
severity: 'suspicious',
|
|
3265
|
+
meta: { totalUncompressed: sumUnc, limit: cfg.maxTotalUncompressedBytes }
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
if (sumComp === 0 && sumUnc > 0) {
|
|
3269
|
+
matches.push({ rule: 'zip_suspicious_ratio', severity: 'suspicious', meta: { ratio: Infinity } });
|
|
3270
|
+
}
|
|
3271
|
+
else if (sumComp > 0) {
|
|
3272
|
+
const ratio = sumUnc / Math.max(1, sumComp);
|
|
3273
|
+
if (ratio >= cfg.maxCompressionRatio) {
|
|
3274
|
+
matches.push({ rule: 'zip_suspicious_ratio', severity: 'suspicious', meta: { ratio, limit: cfg.maxCompressionRatio } });
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
return matches;
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
export { CommonHeuristicsScanner, createZipBombGuard, mapMatchesToVerdict, prefilterBrowser, scanFiles, scanFilesWithHeuristicsAndYara, scanFilesWithRemoteYara, scanFilesWithYara, useFileScanner, validateFile };
|
|
3104
3283
|
//# sourceMappingURL=pompelmi.esm.js.map
|