muaddib-scanner 2.11.78 → 2.11.80

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.78",
3
+ "version": "2.11.80",
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-08T18:21:33.065Z",
3
+ "timestamp": "2026-06-10T12:26:12.478Z",
4
4
  "threats": [
5
5
  {
6
6
  "type": "string_mutation_obfuscation",
@@ -1115,6 +1115,12 @@ function computeLedgerRollup(sinceTs, opts = {}) {
1115
1115
  const scannedKeys = new Set();
1116
1116
  const droppedKeys = new Set();
1117
1117
  let exactVanished = true;
1118
+ // Distinct package NAMES (version-collapsed) for honest coverage. A package is
1119
+ // "covered" if at least one of its versions reached a real scan (non-dropped).
1120
+ // Bounded: names are only added while underCap, so |names| ≤ |keys| ≤ MAX_ROLLUP_KEYS.
1121
+ // Exactness mirrors exactVanished (false iff the cap was hit mid-window).
1122
+ const allNames = new Set();
1123
+ const scannedNames = new Set();
1118
1124
 
1119
1125
  _iterateJsonlSync(file, (e) => {
1120
1126
  if (!e || !e.name) return;
@@ -1140,11 +1146,11 @@ function computeLedgerRollup(sinceTs, opts = {}) {
1140
1146
  const underCap = exactVanished && (scannedKeys.size + droppedKeys.size) < MAX_ROLLUP_KEYS;
1141
1147
  if (outcome === 'dropped') {
1142
1148
  dropped++; ecoNode.dropped++;
1143
- if (underCap) droppedKeys.add(key); else exactVanished = false;
1149
+ if (underCap) { droppedKeys.add(key); allNames.add(e.name); } else exactVanished = false;
1144
1150
  } else {
1145
1151
  scanned++; ecoNode.scanned++;
1146
1152
  if (outcome === 'suspect' || outcome === 'confirmed') { alerted++; ecoNode.alerted++; }
1147
- if (underCap) scannedKeys.add(key); else exactVanished = false;
1153
+ if (underCap) { scannedKeys.add(key); allNames.add(e.name); scannedNames.add(e.name); } else exactVanished = false;
1148
1154
  }
1149
1155
  });
1150
1156
 
@@ -1164,6 +1170,13 @@ function computeLedgerRollup(sinceTs, opts = {}) {
1164
1170
  alerted,
1165
1171
  // NOT a TPR — see the HONEST METRIC NOTE above. null when nothing was scanned.
1166
1172
  alertRate: scanned > 0 ? alerted / scanned : null,
1173
+ // Honest, version-collapsed coverage: distinct package names seen vs scanned.
1174
+ // Bounded ≤100% by construction (scannedNames ⊆ allNames). Unlike the raw
1175
+ // event-count coverage in the embed, this is immune to version-spam inflation
1176
+ // (e.g. a package publishing thousands of versions counts once).
1177
+ distinctPackages: allNames.size,
1178
+ distinctScanned: scannedNames.size,
1179
+ distinctCoverage: allNames.size > 0 ? scannedNames.size / allNames.size : null,
1167
1180
  byOutcome,
1168
1181
  byEcosystem
1169
1182
  };
@@ -1027,25 +1027,41 @@ function buildDailyReportEmbed(stats, dailyAlerts, ledgerRollup) {
1027
1027
  // Avg scan time from in-memory stats
1028
1028
  const avg = stats.scanned > 0 ? (stats.totalTimeMs / stats.scanned / 1000).toFixed(1) : '0.0';
1029
1029
 
1030
- // --- Coverage estimation ---
1031
- // Numerator: unique (ecosystem, name, version) tuples that reached a scan
1032
- // attempt (post-dedup). Denominator: raw publish events seen on either
1033
- // changes stream BEFORE per-package filtering, plus npm catch-up gaps and
1034
- // PyPI publish events that survived per-(name,version) dedup. This stays
1035
- // bounded near 100% — old "scanned/changesStreamPackages" was racing PyPI
1036
- // scans and ATO burst extras against an npm-only denominator.
1030
+ // --- Phase 0b: per-scan ledger rollup (resolved early so Coverage can use it) ---
1031
+ // Caller may pass a precomputed rollup (sendDailyReport does, to persist the same
1032
+ // numbers it displays); undefined compute here; explicit null → omit the section.
1033
+ const ledger = ledgerRollup !== undefined ? ledgerRollup : safeLedgerRollup();
1034
+
1035
+ // --- Coverage ---
1036
+ // HEADLINE: honest, version-collapsed coverage from the scan-ledger — distinct
1037
+ // package NAMES actually scanned vs distinct names seen (scanned + dropped) in
1038
+ // the window. Bounded ≤100% by construction and immune to version-spam (a
1039
+ // package publishing thousands of versions counts once). The raw publish-event
1040
+ // ratio is kept as a SECONDARY line for continuity but is no longer the headline:
1041
+ // it races re-scans / PyPI / burst extras against an npm-only event denominator
1042
+ // and routinely exceeds 100% (see AUDIT 4 — daily-reports-analysis.md).
1037
1043
  const attempted = stats.uniqueScanAttempts || 0;
1038
1044
  const npmPub = stats.npmPublishEventsSeen || 0;
1039
1045
  const pypiPub = stats.pypiChangelogPackages || 0;
1040
1046
  const published = npmPub + pypiPub;
1041
- const coverageRatio = published > 0 ? (attempted / published * 100).toFixed(0) : '0';
1042
1047
  const catchupSkipped = (stats.npmCatchupSkippedSeqs || 0) + (stats.pypiCatchupSkippedEvents || 0);
1043
1048
  const opsSuffix = catchupSkipped > 0
1044
1049
  ? `\nOps: ${stats.scanned} | Catch-up skip: ${catchupSkipped}`
1045
1050
  : `\nOps: ${stats.scanned}`;
1046
- const coverageText = published > 0
1047
- ? `${attempted}/${published} (${coverageRatio}%)${opsSuffix}`
1048
- : `${attempted} attempted${opsSuffix}`;
1051
+ let coverageText;
1052
+ if (ledger && ledger.distinctPackages > 0 && ledger.distinctCoverage != null) {
1053
+ const pct = (ledger.distinctCoverage * 100).toFixed(0);
1054
+ const approx = ledger.exactVanished === false ? '~' : '';
1055
+ coverageText = `${ledger.distinctScanned}/${ledger.distinctPackages} pkgs (${approx}${pct}%)`;
1056
+ if (published > 0) coverageText += `\nRaw events: ${attempted}/${published}`;
1057
+ coverageText += opsSuffix;
1058
+ } else if (published > 0) {
1059
+ // Fallback: ledger unavailable (first boot / empty ledger) → legacy event ratio.
1060
+ const coverageRatio = (attempted / published * 100).toFixed(0);
1061
+ coverageText = `${attempted}/${published} (${coverageRatio}%)${opsSuffix}`;
1062
+ } else {
1063
+ coverageText = `${attempted} attempted${opsSuffix}`;
1064
+ }
1049
1065
 
1050
1066
  // --- Timeouts ---
1051
1067
  const staticTimeouts = (stats.errorsByType && stats.errorsByType.static_timeout) || 0;
@@ -1108,9 +1124,7 @@ function buildDailyReportEmbed(stats, dailyAlerts, ledgerRollup) {
1108
1124
  const healthText = `Up ${uptimeH}h${uptimeM}m | Heap ${heapMB}MB${jsonlInfo}`;
1109
1125
 
1110
1126
  // --- Phase 0b: per-scan ledger rollup (operational coverage) ---
1111
- // Caller may pass a precomputed rollup (sendDailyReport does, to persist the same
1112
- // numbers it displays); undefined → compute here; explicit null → omit the section.
1113
- const ledger = ledgerRollup !== undefined ? ledgerRollup : safeLedgerRollup();
1127
+ // `ledger` was resolved above (Coverage uses it). explicit null omit the section.
1114
1128
  const ledgerField = formatLedgerField(ledger);
1115
1129
 
1116
1130
  const now = new Date();
@@ -1207,6 +1221,12 @@ async function sendDailyReport(stats, dailyAlerts, recentlyScanned, downloadsCac
1207
1221
  deferredProcessed: stats.deferredProcessed || 0,
1208
1222
  deferredExpired: stats.deferredExpired || 0,
1209
1223
  changesStreamPackages: stats.changesStreamPackages || 0,
1224
+ // Honest version-collapsed coverage (AUDIT 4): top-level mirror of the
1225
+ // ledger fields so trend analysis can read them without descending into
1226
+ // metrics.ledger. null when the ledger window was empty.
1227
+ distinctPackages: ledgerRollup ? (ledgerRollup.distinctPackages ?? null) : null,
1228
+ distinctScanned: ledgerRollup ? (ledgerRollup.distinctScanned ?? null) : null,
1229
+ distinctCoverage: ledgerRollup ? (ledgerRollup.distinctCoverage ?? null) : null,
1210
1230
  restartsToday: stats.restartsToday || 0,
1211
1231
  temporalLoadShed: stats.temporalLoadShed || 0,
1212
1232
  queueHardDrops: stats.queueHardDrops || 0,