fix(nessus): bug fixes from code review

- plugin_cvss() now uses _plugin_attrs() helper (eliminates duplicate
  inline path extraction — single place to update if Nessus changes payload)
- non_cve_skipped initialised to 0 in stats dict so the field is always
  present in the sync response even when every finding has a CVE
- vuln detail page: show title + description separately instead of
  title || description (description was silently hidden when title existed)
- vuln detail page: add "Detection Sources" card — source badges (WAZUH /
  NESSUS), cross-confirmed indicator, first_detected_by, Nessus plugin ID
  (links to tenable.com/plugins), and Tenable VPR score with colour coding
This commit is contained in:
2026-05-14 17:38:05 +02:00
parent 0011a6364d
commit d903ca41cb
3 changed files with 63 additions and 7 deletions
+1 -5
View File
@@ -503,11 +503,7 @@ class NessusClient:
``pluginattributes`` directly. Check both shapes so we don't miss
the score on either format.
"""
attrs = (
(plugin_payload.get("info") or {})
.get("plugindescription", {})
.get("pluginattributes", {})
)
attrs = NessusClient._plugin_attrs(plugin_payload)
risk_info = attrs.get("risk_information") or {}
if not isinstance(risk_info, dict):
risk_info = {}
+1 -1
View File
@@ -177,6 +177,7 @@ def run_nessus_sync(
"vulns_merged": 0,
"vulns_unchanged": 0,
"vulns_marked_patched": 0,
"non_cve_skipped": 0,
"unmatched_hosts": [],
"errors": [],
}
@@ -277,7 +278,6 @@ def run_nessus_sync(
# informational plugins). Per product decision we only track
# CVE-tagged vulns in VulnCheck — pseudo-CVE imports were
# noisy and inflated the count.
stats.setdefault("non_cve_skipped", 0)
stats["non_cve_skipped"] += 1
continue
+61 -1
View File
@@ -124,8 +124,11 @@ export default function VulnerabilityDetailPage() {
<DocumentTextIcon className="h-5 w-5 text-gray-500" />
Description
</h2>
{vuln.title && (
<p className="font-semibold text-gray-800 mb-2">{vuln.title}</p>
)}
<p className="text-gray-700 leading-relaxed">
{vuln.title || vuln.description || 'No description available.'}
{vuln.description || (!vuln.title ? 'No description available.' : '')}
</p>
</div>
@@ -401,6 +404,63 @@ export default function VulnerabilityDetailPage() {
)}
</div>
{/* Detection Sources (Nessus / Wazuh multi-scanner) */}
{((vuln.sources && vuln.sources.length > 0) || vuln.nessus_plugin_id || vuln.nessus_vpr_score != null) && (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-bold text-gray-900 mb-4">Detection Sources</h2>
{/* Source badges */}
<div className="flex flex-wrap gap-1.5 mb-3">
{(vuln.sources || []).map(src => {
const cls = src === 'wazuh'
? 'bg-green-100 text-green-700 border-green-200'
: src === 'nessus'
? 'bg-purple-100 text-purple-700 border-purple-200'
: 'bg-gray-100 text-gray-600 border-gray-200';
return (
<span key={src} className={`inline-flex items-center rounded border px-2 py-0.5 text-xs font-bold uppercase ${cls}`}>{src}</span>
);
})}
{vuln.cross_confirmed && (
<span className="inline-flex items-center rounded border border-emerald-200 bg-emerald-100 px-2 py-0.5 text-xs font-bold text-emerald-700"
title="Reported by multiple independent scanners — highest confidence">
Cross-confirmed
</span>
)}
</div>
<div className="space-y-1.5 text-sm font-mono">
{vuln.first_detected_by && (
<div className="flex justify-between text-xs">
<span className="text-gray-500">First detected by</span>
<span className="font-semibold uppercase">{vuln.first_detected_by}</span>
</div>
)}
{vuln.nessus_plugin_id && (
<div className="flex justify-between text-xs">
<span className="text-gray-500">Nessus Plugin ID</span>
<a
href={`https://www.tenable.com/plugins/nessus/${vuln.nessus_plugin_id}`}
target="_blank"
rel="noopener noreferrer"
className="text-purple-600 hover:underline"
>
#{vuln.nessus_plugin_id}
</a>
</div>
)}
{vuln.nessus_vpr_score != null && (
<div className="flex justify-between text-xs items-center">
<span className="text-gray-500" title="Tenable's Vulnerability Priority Rating (010) — proprietary commercial score">
Tenable VPR
</span>
<span className={`font-bold ${vuln.nessus_vpr_score >= 9 ? 'text-red-600' : vuln.nessus_vpr_score >= 7 ? 'text-orange-500' : vuln.nessus_vpr_score >= 4 ? 'text-yellow-600' : 'text-green-600'}`}>
{vuln.nessus_vpr_score.toFixed(1)} / 10
</span>
</div>
)}
</div>
</div>
)}
{/* Timeline */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">