muaddib-scanner 2.10.71 → 2.10.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.10.71",
3
+ "version": "2.10.72",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -179,8 +179,21 @@ function isSuspectClassification(result) {
179
179
  return { suspect: true, tier: 3 };
180
180
  }
181
181
 
182
- // 2+ distinct types with non-passive types not in tier 2 active list -- tier 2
183
- return { suspect: true, tier: 2 };
182
+ // Fallback: 2+ distinct types with non-passive types not in tier 2 active list.
183
+ // Previously this returned tier 2 unconditionally, but that was permissive —
184
+ // packages with 2 LOW findings in uncategorized types (e.g., @eeacms/* with
185
+ // score 2 observed 2026-04-11) landed in tier 2 and flooded the deferred
186
+ // sandbox queue, starving legitimate T1b/T2 candidates of the dedicated
187
+ // deferred slot.
188
+ //
189
+ // A sandbox slot is only justified when there is real signal. Require at
190
+ // least one non-LOW finding to reach tier 2 via this fallback — otherwise
191
+ // downgrade to tier 3 (log only, no sandbox consumption).
192
+ const hasNonLowFinding = result.threats.some(t => t.severity !== 'LOW');
193
+ if (hasNonLowFinding) {
194
+ return { suspect: true, tier: 2 };
195
+ }
196
+ return { suspect: true, tier: 3 };
184
197
  }
185
198
 
186
199
  /**
@@ -26,6 +26,12 @@ const DEFERRED_TTL_MS = 24 * 60 * 60 * 1000; // 24h
26
26
  const DEFERRED_MAX_RETRIES = 2;
27
27
  const DEFERRED_WORKER_INTERVAL_MS = 30_000; // 30s
28
28
  const DEFERRED_STATE_FILE = path.join(__dirname, '..', '..', 'data', 'deferred-queue.json');
29
+ // Defense-in-depth: sandbox slot is precious. A T1b/T2 below this score
30
+ // threshold is almost certainly a classification fallback false positive
31
+ // (cf. classify.js:183 remediation) and should never consume the deferred
32
+ // slot. HIGH=10 pts is the intended T1b floor — values below 5 are LOW-only
33
+ // aggregates which carry no actionable sandbox signal.
34
+ const DEFERRED_MIN_SCORE = 5;
29
35
 
30
36
  // ── Mutable state ──
31
37
  const _deferredQueue = [];
@@ -51,6 +57,16 @@ function enqueueDeferred(item) {
51
57
  return false;
52
58
  }
53
59
 
60
+ // Defense-in-depth: block low-score items regardless of tier. With the
61
+ // classify.js:183 fallback fix in place, no legitimate enqueue should
62
+ // reach this function with score < DEFERRED_MIN_SCORE. Logging with
63
+ // console.error makes a future regression (new classification path that
64
+ // leaks low-score items) loud in operator logs.
65
+ if ((item.riskScore || 0) < DEFERRED_MIN_SCORE) {
66
+ console.error(`[DEFERRED] REJECTED: ${item.name}@${item.version} — score=${item.riskScore || 0} below minimum ${DEFERRED_MIN_SCORE} (possible classification regression)`);
67
+ return false;
68
+ }
69
+
54
70
  const key = `${item.name}@${item.version}`;
55
71
 
56
72
  // Dedup