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:
@@ -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 = {}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 (0–10) — 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">
|
||||
|
||||
Reference in New Issue
Block a user