feat: improve archive finding clarity with finding IDs, historical severity labels, and related active finding indicators

This commit is contained in:
jramos
2026-04-15 10:18:19 -06:00
parent 732873dd6a
commit 938dda400a
6 changed files with 439 additions and 7 deletions

View File

@@ -3,6 +3,37 @@ const express = require('express');
const VALID_STATES = ['ACTIVE', 'ARCHIVED', 'RETURNED', 'CLOSED'];
/**
* Find the most severe active finding related to an archived finding.
*
* A match requires:
* - Exact hostname match (case-sensitive)
* - The archive title is a case-insensitive substring of the active title, or vice versa
* - The active finding ID differs from the archive's finding_id
*
* @param {Object} archive - Archive record from ivanti_finding_archives
* @param {Array} activeFindings - Parsed entries from ivanti_findings_cache
* @returns {{ id: string, title: string, severity: number } | null}
*/
function findRelatedActive(archive, activeFindings) {
const archiveTitle = (archive.finding_title || '').toLowerCase();
const matches = activeFindings.filter(f => {
if (f.hostName !== archive.host_name) return false;
if (f.id === archive.finding_id) return false;
const activeTitle = (f.title || '').toLowerCase();
if (!archiveTitle.includes(activeTitle) && !activeTitle.includes(archiveTitle)) return false;
return true;
});
if (matches.length === 0) return null;
const best = matches.reduce((a, b) => (b.severity > a.severity ? b : a));
return { id: best.id, title: best.title, severity: best.severity };
}
function createIvantiArchiveRouter(db, requireAuth) {
const router = express.Router();
@@ -45,7 +76,37 @@ function createIvantiArchiveRouter(db, requireAuth) {
});
});
res.json({ archives, total: archives.length });
// Fetch and parse active findings cache for related-finding enrichment
let activeFindings = [];
try {
const cacheRow = await new Promise((resolve, reject) => {
db.get(
'SELECT findings_json FROM ivanti_findings_cache WHERE id = 1',
(err, row) => {
if (err) reject(err);
else resolve(row);
}
);
});
if (cacheRow && cacheRow.findings_json) {
activeFindings = JSON.parse(cacheRow.findings_json);
}
} catch (cacheErr) {
console.warn('Failed to load findings cache for related-active matching:', cacheErr);
}
if (!Array.isArray(activeFindings)) {
activeFindings = [];
}
// Enrich each archive record with related active finding info
const enrichedArchives = archives.map(archive => ({
...archive,
related_active: findRelatedActive(archive, activeFindings)
}));
res.json({ archives: enrichedArchives, total: enrichedArchives.length });
} catch (err) {
console.error('Archive list error:', err);
res.status(500).json({ error: 'Failed to fetch archive records' });