muaddib-scanner 2.11.95 → 2.11.96

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.11.95",
3
+ "version": "2.11.96",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "target": "node_modules",
3
- "timestamp": "2026-06-11T13:00:12.001Z",
3
+ "timestamp": "2026-06-11T13:24:06.648Z",
4
4
  "threats": [
5
5
  {
6
6
  "type": "string_mutation_obfuscation",
@@ -83,9 +83,24 @@ function _writeEntries(file, entries) {
83
83
  fs.renameSync(tmp, file);
84
84
  }
85
85
 
86
+ // Conservative upper bound on a spilled line's byte size (the SPILL_FIELDS
87
+ // record + ts + newline run ~150-250 bytes). Used for the O(1) stat-gated
88
+ // compaction trigger below — overestimating only makes compaction fire a bit
89
+ // late, never early, so the file ceiling stays ~MUADDIB_SPILL_MAX.
90
+ const SPILL_LINE_BYTES_EST = 256;
91
+
86
92
  /**
87
93
  * Append evicted queue items to the backlog. Never throws; on write failure the
88
94
  * caller's fallback is the pre-spill behavior (drop, ledgered).
95
+ *
96
+ * HOT PATH — runs INSIDE the EMERGENCY memory handler (evictFromScanQueueBulk),
97
+ * so it MUST be append-only and allocation-free beyond the write buffer. The
98
+ * 2026-06-11 freeze was caused by calling _compactBacklog (which reads + parses
99
+ * the WHOLE backlog) on every spill: a large allocation during a reclaim stall
100
+ * that wedged the handler before it could free RSS. Compaction now fires ONLY
101
+ * when a cheap statSync shows the file is genuinely near the cap (normally
102
+ * never during an EMERGENCY — the backlog is far below the byte budget there),
103
+ * and the calm-time drain also keeps it bounded.
89
104
  * @param {Array<object>} items evicted scan-queue items
90
105
  * @returns {number} how many items were actually persisted
91
106
  */
@@ -107,7 +122,11 @@ function spillItems(items) {
107
122
  written++;
108
123
  }
109
124
  if (buf) fs.appendFileSync(file, buf, 'utf8');
110
- _compactBacklog(file);
125
+ // O(1) stat-gated compaction: only read+rewrite the file when it is actually
126
+ // near the cap. NO whole-file read on the normal EMERGENCY spill path.
127
+ let size = 0;
128
+ try { size = fs.statSync(file).size; } catch { /* fresh file */ }
129
+ if (size > _maxEntries() * SPILL_LINE_BYTES_EST) _compactBacklog(file);
111
130
  } catch {
112
131
  return 0; // degrade to drop-with-ledger at the call site
113
132
  }