pompelmi 0.14.0-dev.26 → 0.15.0-dev.27

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.
@@ -3193,7 +3193,8 @@ function isZipLike(buf) {
3193
3193
  function lastIndexOfEOCD(buf, window) {
3194
3194
  const sig = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
3195
3195
  const start = Math.max(0, buf.length - window);
3196
- return buf.lastIndexOf(sig, buf.length - 1, start);
3196
+ const idx = buf.lastIndexOf(sig, Math.min(buf.length - sig.length, buf.length - 1));
3197
+ return idx >= start ? idx : -1;
3197
3198
  }
3198
3199
  function hasTraversal(name) {
3199
3200
  return name.includes('../') || name.includes('..\\') || name.startsWith('/') || /^[A-Za-z]:/.test(name);
@@ -3210,7 +3211,7 @@ function createZipBombGuard(opts = {}) {
3210
3211
  const eocdPos = lastIndexOfEOCD(buf, cfg.eocdSearchWindow);
3211
3212
  if (eocdPos < 0 || eocdPos + 22 > buf.length) {
3212
3213
  // ZIP but no EOCD — malformed or polyglot → suspicious
3213
- matches.push({ rule: 'zip_eocd_not_found', severity: 'suspicious' });
3214
+ matches.push({ rule: 'zip_eocd_not_found', severity: 'medium' });
3214
3215
  return matches;
3215
3216
  }
3216
3217
  const totalEntries = r16(buf, eocdPos + 10);
@@ -3218,7 +3219,7 @@ function createZipBombGuard(opts = {}) {
3218
3219
  const cdOffset = r32(buf, eocdPos + 16);
3219
3220
  // Bounds check
3220
3221
  if (cdOffset + cdSize > buf.length) {
3221
- matches.push({ rule: 'zip_cd_out_of_bounds', severity: 'suspicious' });
3222
+ matches.push({ rule: 'zip_cd_out_of_bounds', severity: 'medium' });
3222
3223
  return matches;
3223
3224
  }
3224
3225
  // Iterate central directory entries
@@ -3244,36 +3245,36 @@ function createZipBombGuard(opts = {}) {
3244
3245
  sumUnc += uncSize;
3245
3246
  seen++;
3246
3247
  if (name.length > cfg.maxEntryNameLength) {
3247
- matches.push({ rule: 'zip_entry_name_too_long', severity: 'suspicious', meta: { name, length: name.length } });
3248
+ matches.push({ rule: 'zip_entry_name_too_long', severity: 'medium', meta: { name, length: name.length } });
3248
3249
  }
3249
3250
  if (hasTraversal(name)) {
3250
- matches.push({ rule: 'zip_path_traversal_entry', severity: 'suspicious', meta: { name } });
3251
+ matches.push({ rule: 'zip_path_traversal_entry', severity: 'medium', meta: { name } });
3251
3252
  }
3252
3253
  // move to next entry
3253
3254
  ptr = nameEnd + exLen + cmLen;
3254
3255
  }
3255
3256
  if (seen !== totalEntries) {
3256
3257
  // central dir truncated/odd, still report what we found
3257
- matches.push({ rule: 'zip_cd_truncated', severity: 'suspicious', meta: { seen, totalEntries } });
3258
+ matches.push({ rule: 'zip_cd_truncated', severity: 'medium', meta: { seen, totalEntries } });
3258
3259
  }
3259
3260
  // Heuristics thresholds
3260
3261
  if (seen > cfg.maxEntries) {
3261
- matches.push({ rule: 'zip_too_many_entries', severity: 'suspicious', meta: { seen, limit: cfg.maxEntries } });
3262
+ matches.push({ rule: 'zip_too_many_entries', severity: 'medium', meta: { seen, limit: cfg.maxEntries } });
3262
3263
  }
3263
3264
  if (sumUnc > cfg.maxTotalUncompressedBytes) {
3264
3265
  matches.push({
3265
3266
  rule: 'zip_total_uncompressed_too_large',
3266
- severity: 'suspicious',
3267
+ severity: 'medium',
3267
3268
  meta: { totalUncompressed: sumUnc, limit: cfg.maxTotalUncompressedBytes }
3268
3269
  });
3269
3270
  }
3270
3271
  if (sumComp === 0 && sumUnc > 0) {
3271
- matches.push({ rule: 'zip_suspicious_ratio', severity: 'suspicious', meta: { ratio: Infinity } });
3272
+ matches.push({ rule: 'zip_suspicious_ratio', severity: 'medium', meta: { ratio: Infinity } });
3272
3273
  }
3273
3274
  else if (sumComp > 0) {
3274
3275
  const ratio = sumUnc / Math.max(1, sumComp);
3275
3276
  if (ratio >= cfg.maxCompressionRatio) {
3276
- matches.push({ rule: 'zip_suspicious_ratio', severity: 'suspicious', meta: { ratio, limit: cfg.maxCompressionRatio } });
3277
+ matches.push({ rule: 'zip_suspicious_ratio', severity: 'medium', meta: { ratio, limit: cfg.maxCompressionRatio } });
3277
3278
  }
3278
3279
  }
3279
3280
  return matches;
@@ -3281,8 +3282,34 @@ function createZipBombGuard(opts = {}) {
3281
3282
  };
3282
3283
  }
3283
3284
 
3285
+ const MB = 1024 * 1024;
3286
+ const DEFAULT_POLICY = {
3287
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
3288
+ allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
3289
+ maxFileSizeBytes: 20 * MB,
3290
+ timeoutMs: 5000,
3291
+ concurrency: 4,
3292
+ failClosed: true
3293
+ };
3294
+ function definePolicy(input = {}) {
3295
+ const p = { ...DEFAULT_POLICY, ...input };
3296
+ if (!Array.isArray(p.includeExtensions))
3297
+ throw new TypeError('includeExtensions must be string[]');
3298
+ if (!Array.isArray(p.allowedMimeTypes))
3299
+ throw new TypeError('allowedMimeTypes must be string[]');
3300
+ if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
3301
+ throw new TypeError('maxFileSizeBytes must be > 0');
3302
+ if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
3303
+ throw new TypeError('timeoutMs must be > 0');
3304
+ if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
3305
+ throw new TypeError('concurrency must be > 0');
3306
+ return p;
3307
+ }
3308
+
3284
3309
  exports.CommonHeuristicsScanner = CommonHeuristicsScanner;
3310
+ exports.DEFAULT_POLICY = DEFAULT_POLICY;
3285
3311
  exports.createZipBombGuard = createZipBombGuard;
3312
+ exports.definePolicy = definePolicy;
3286
3313
  exports.mapMatchesToVerdict = mapMatchesToVerdict;
3287
3314
  exports.prefilterBrowser = prefilterBrowser;
3288
3315
  exports.scanFiles = scanFiles;