muaddib-scanner 2.10.27 → 2.10.29
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 +1 -1
- package/src/monitor/daemon.js +2 -2
- package/src/monitor/queue.js +4 -4
- package/src/monitor/webhook.js +94 -4
package/package.json
CHANGED
package/src/monitor/daemon.js
CHANGED
|
@@ -165,7 +165,7 @@ async function startMonitor(options, stats, dailyAlerts, recentlyScanned, downlo
|
|
|
165
165
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
166
166
|
|
|
167
167
|
// Initial poll + scan
|
|
168
|
-
await poll(scanQueue, stats);
|
|
168
|
+
await poll(state, scanQueue, stats);
|
|
169
169
|
saveState(state, stats);
|
|
170
170
|
await processQueue(scanQueue, stats, dailyAlerts, recentlyScanned, downloadsCache, sandboxAvailableRef.value);
|
|
171
171
|
|
|
@@ -173,7 +173,7 @@ async function startMonitor(options, stats, dailyAlerts, recentlyScanned, downlo
|
|
|
173
173
|
while (running) {
|
|
174
174
|
await sleep(POLL_INTERVAL);
|
|
175
175
|
if (!running) break;
|
|
176
|
-
await poll(scanQueue, stats);
|
|
176
|
+
await poll(state, scanQueue, stats);
|
|
177
177
|
saveState(state, stats);
|
|
178
178
|
await processQueue(scanQueue, stats, dailyAlerts, recentlyScanned, downloadsCache, sandboxAvailableRef.value);
|
|
179
179
|
|
package/src/monitor/queue.js
CHANGED
|
@@ -863,10 +863,10 @@ async function resolveTarballAndScan(item, stats, dailyAlerts, recentlyScanned,
|
|
|
863
863
|
// Run all 4 temporal checks in parallel — each is independent.
|
|
864
864
|
// With metadata cache (temporal-analysis.js), the 4 modules share 1 HTTP request.
|
|
865
865
|
const [tempRes, astRes, pubRes, maintRes] = await Promise.allSettled([
|
|
866
|
-
runTemporalCheck(item.name),
|
|
867
|
-
runTemporalAstCheck(item.name),
|
|
868
|
-
runTemporalPublishCheck(item.name),
|
|
869
|
-
runTemporalMaintainerCheck(item.name)
|
|
866
|
+
runTemporalCheck(item.name, dailyAlerts),
|
|
867
|
+
runTemporalAstCheck(item.name, dailyAlerts),
|
|
868
|
+
runTemporalPublishCheck(item.name, dailyAlerts),
|
|
869
|
+
runTemporalMaintainerCheck(item.name, dailyAlerts)
|
|
870
870
|
]);
|
|
871
871
|
temporalResult = tempRes.status === 'fulfilled' ? tempRes.value : null;
|
|
872
872
|
astResult = astRes.status === 'fulfilled' ? astRes.value : null;
|
package/src/monitor/webhook.js
CHANGED
|
@@ -744,6 +744,37 @@ function buildCanaryExfiltrationWebhookEmbed(packageName, version, exfiltrations
|
|
|
744
744
|
* @param {Object} stats - In-memory stats object (scanned, clean, suspect, errors, errorsByType, totalTimeMs, suspectByTier, mlFiltered)
|
|
745
745
|
* @param {Array} dailyAlerts - In-memory daily alerts array
|
|
746
746
|
*/
|
|
747
|
+
/**
|
|
748
|
+
* Load yesterday's persisted report metrics for J-1 comparison.
|
|
749
|
+
* @returns {Object|null} yesterday's raw metrics or null if unavailable
|
|
750
|
+
*/
|
|
751
|
+
function loadYesterdayMetrics() {
|
|
752
|
+
try {
|
|
753
|
+
// Use Paris timezone to match persistDailyReport() which uses getParisDateString()
|
|
754
|
+
const todayParis = getParisDateString(); // YYYY-MM-DD in Europe/Paris
|
|
755
|
+
const [y, m, d] = todayParis.split('-').map(Number);
|
|
756
|
+
const yesterday = new Date(y, m - 1, d);
|
|
757
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
758
|
+
const yStr = yesterday.toISOString().slice(0, 10);
|
|
759
|
+
const filePath = path.join(DAILY_REPORTS_LOG_DIR, `${yStr}.json`);
|
|
760
|
+
if (!fs.existsSync(filePath)) return null;
|
|
761
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
762
|
+
return data.metrics || null;
|
|
763
|
+
} catch {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Format a delta with sign: "+1200" or "-50" or "=0"
|
|
770
|
+
*/
|
|
771
|
+
function formatDelta(current, previous) {
|
|
772
|
+
const d = current - previous;
|
|
773
|
+
if (d > 0) return `+${d}`;
|
|
774
|
+
if (d < 0) return `${d}`;
|
|
775
|
+
return '=0';
|
|
776
|
+
}
|
|
777
|
+
|
|
747
778
|
function buildDailyReportEmbed(stats, dailyAlerts) {
|
|
748
779
|
// Use in-memory stats (accumulated since last reset, restored from disk on restart)
|
|
749
780
|
// instead of disk-based daily entries which can undercount due to UTC/Paris date mismatch
|
|
@@ -766,6 +797,56 @@ function buildDailyReportEmbed(stats, dailyAlerts) {
|
|
|
766
797
|
// Avg scan time from in-memory stats
|
|
767
798
|
const avg = stats.scanned > 0 ? (stats.totalTimeMs / stats.scanned / 1000).toFixed(1) : '0.0';
|
|
768
799
|
|
|
800
|
+
// --- Coverage estimation ---
|
|
801
|
+
// changesStreamPackages = total versions seen from npm changes stream (≈ published today)
|
|
802
|
+
const published = stats.changesStreamPackages || 0;
|
|
803
|
+
const coverageText = published > 0
|
|
804
|
+
? `${stats.scanned}/${published} (${(stats.scanned / published * 100).toFixed(0)}%)`
|
|
805
|
+
: `${stats.scanned} scanned`;
|
|
806
|
+
|
|
807
|
+
// --- Timeouts ---
|
|
808
|
+
const staticTimeouts = (stats.errorsByType && stats.errorsByType.static_timeout) || 0;
|
|
809
|
+
const httpTimeouts = (stats.errorsByType && stats.errorsByType.timeout) || 0;
|
|
810
|
+
const timeoutPct = stats.scanned > 0 ? (staticTimeouts / stats.scanned * 100) : 0;
|
|
811
|
+
const timeoutWarning = timeoutPct > 15 ? ' \u26a0\ufe0f' : '';
|
|
812
|
+
const timeoutText = `Static: ${staticTimeouts}/${stats.scanned} (${timeoutPct.toFixed(1)}%)${timeoutWarning}\nHTTP: ${httpTimeouts}`;
|
|
813
|
+
|
|
814
|
+
// --- J-1 trends ---
|
|
815
|
+
const yesterday = loadYesterdayMetrics();
|
|
816
|
+
let trendsText = 'No data (first day or missing)';
|
|
817
|
+
if (yesterday) {
|
|
818
|
+
const dScanned = formatDelta(stats.scanned, yesterday.scanned || 0);
|
|
819
|
+
const dSuspect = formatDelta(stats.suspect, yesterday.suspect || 0);
|
|
820
|
+
const dErrors = formatDelta(stats.errors, yesterday.errors || 0);
|
|
821
|
+
trendsText = `${dScanned} scanned, ${dSuspect} suspects, ${dErrors} errors`;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// --- ML stats ---
|
|
825
|
+
let mlText;
|
|
826
|
+
try {
|
|
827
|
+
const { isModelAvailable } = require('../ml/classifier.js');
|
|
828
|
+
if (isModelAvailable()) {
|
|
829
|
+
mlText = stats.mlFiltered > 0 ? `${stats.mlFiltered} filtered` : '0 filtered';
|
|
830
|
+
} else {
|
|
831
|
+
mlText = 'No model loaded';
|
|
832
|
+
}
|
|
833
|
+
} catch {
|
|
834
|
+
mlText = 'No model loaded';
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// --- System health ---
|
|
838
|
+
const uptimeSec = Math.floor(process.uptime());
|
|
839
|
+
const uptimeH = Math.floor(uptimeSec / 3600);
|
|
840
|
+
const uptimeM = Math.floor((uptimeSec % 3600) / 60);
|
|
841
|
+
const heapMB = (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(0);
|
|
842
|
+
let jsonlInfo = '';
|
|
843
|
+
try {
|
|
844
|
+
const { getStats: getTrainingStats } = require('../ml/jsonl-writer.js');
|
|
845
|
+
const jStats = getTrainingStats();
|
|
846
|
+
jsonlInfo = ` | JSONL: ${jStats.recordCount} records (${jStats.fileSizeMB}MB)`;
|
|
847
|
+
} catch { /* non-fatal */ }
|
|
848
|
+
const healthText = `Up ${uptimeH}h${uptimeM}m | Heap ${heapMB}MB${jsonlInfo}`;
|
|
849
|
+
|
|
769
850
|
const now = new Date();
|
|
770
851
|
const readableTime = now.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
|
|
771
852
|
|
|
@@ -774,13 +855,16 @@ function buildDailyReportEmbed(stats, dailyAlerts) {
|
|
|
774
855
|
title: '\uD83D\uDCCA MUAD\'DIB Daily Report',
|
|
775
856
|
color: 0x3498db,
|
|
776
857
|
fields: [
|
|
777
|
-
{ name: '
|
|
858
|
+
{ name: 'Coverage', value: coverageText, inline: true },
|
|
778
859
|
{ name: 'Clean', value: `${stats.clean}`, inline: true },
|
|
779
860
|
{ name: 'Suspects', value: `${stats.suspect}`, inline: true },
|
|
780
861
|
{ name: 'Errors', value: formatErrorBreakdown(stats.errors, stats.errorsByType), inline: true },
|
|
781
862
|
{ name: 'Avg Scan Time', value: `${avg}s/pkg`, inline: true },
|
|
782
|
-
|
|
783
|
-
{ name: '
|
|
863
|
+
{ name: 'Timeouts', value: timeoutText, inline: true },
|
|
864
|
+
{ name: 'vs Yesterday', value: trendsText, inline: false },
|
|
865
|
+
{ name: 'ML', value: mlText, inline: true },
|
|
866
|
+
{ name: 'Top Suspects', value: top3Text, inline: false },
|
|
867
|
+
{ name: 'System', value: healthText, inline: false }
|
|
784
868
|
],
|
|
785
869
|
footer: {
|
|
786
870
|
text: `MUAD'DIB - Daily summary | ${readableTime}`
|
|
@@ -822,6 +906,8 @@ async function sendDailyReport(stats, dailyAlerts, recentlyScanned, downloadsCac
|
|
|
822
906
|
errorsByType: { ...stats.errorsByType },
|
|
823
907
|
avgScanTimeMs: stats.scanned > 0 ? Math.round(stats.totalTimeMs / stats.scanned) : 0,
|
|
824
908
|
suspectByTier: { ...stats.suspectByTier },
|
|
909
|
+
mlFiltered: stats.mlFiltered || 0,
|
|
910
|
+
changesStreamPackages: stats.changesStreamPackages || 0,
|
|
825
911
|
topSuspects: dailyAlerts.slice().sort((a, b) => b.findingsCount - a.findingsCount).slice(0, 10)
|
|
826
912
|
});
|
|
827
913
|
|
|
@@ -856,6 +942,8 @@ async function sendDailyReport(stats, dailyAlerts, recentlyScanned, downloadsCac
|
|
|
856
942
|
stats.errorsByType.other = 0;
|
|
857
943
|
stats.totalTimeMs = 0;
|
|
858
944
|
stats.mlFiltered = 0;
|
|
945
|
+
stats.changesStreamPackages = 0;
|
|
946
|
+
stats.rssFallbackCount = 0;
|
|
859
947
|
dailyAlerts.length = 0;
|
|
860
948
|
recentlyScanned.clear();
|
|
861
949
|
alertedPackageRules.clear();
|
|
@@ -1051,5 +1139,7 @@ module.exports = {
|
|
|
1051
1139
|
buildReportFromDisk,
|
|
1052
1140
|
buildReportEmbedFromDisk,
|
|
1053
1141
|
sendReportNow,
|
|
1054
|
-
getReportStatus
|
|
1142
|
+
getReportStatus,
|
|
1143
|
+
loadYesterdayMetrics,
|
|
1144
|
+
formatDelta
|
|
1055
1145
|
};
|