import { useState, useEffect, useRef } from 'react'; import { useSearchParams, Link, useNavigate } from 'react-router-dom'; import { io } from 'socket.io-client'; import { useAuth } from '../components/AuthContext'; import { CodeBlock } from '../components/CodeBlock'; import { OrganizationSelector } from '../components/OrganizationSelector'; export const ScanResults = () => { const [searchParams, setSearchParams] = useSearchParams(); const scanIdFromUrl = searchParams.get('id'); const [scans, setScans] = useState([]); const [activeScanId, setActiveScanId] = useState(scanIdFromUrl); const [scan, setScan] = useState(null); const [vulnerabilities, setVulnerabilities] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalItems, setTotalItems] = useState(0); const [hasNext, setHasNext] = useState(false); const [hasPrev, setHasPrev] = useState(false); const [filterSeverity, setFilterSeverity] = useState('All'); const [selectedVuln, setSelectedVuln] = useState(null); const [resolvedVulns, setResolvedVulns] = useState(new Set()); const [exporting, setExporting] = useState(false); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [shareText, setShareText] = useState('Share'); const [liveLogs, setLiveLogs] = useState([]); useEffect(() => { if (!activeScanId || (scan && scan.status === 'completed')) return; const socket = io('/', { path: '/socket.io' }); socket.on('connect', () => { socket.emit('join_scan', { scan_id: activeScanId }); }); socket.on('scan_log', (data) => { setLiveLogs(prev => [...prev, data].slice(-100)); }); socket.on('vulnerability_found', (data) => { setVulnerabilities(prev => { const isDup = prev.some(v => v.title === data.title && v.category === data.category); if (!isDup) { setTotalItems(t => t + 1); return [data, ...prev]; } return prev; }); }); socket.on('scan_progress', (data) => { if (data.status === 'completed' || data.status === 'failed') { setScan(prev => prev ? {...prev, status: data.status} : prev); } }); return () => { socket.emit('leave_scan', { scan_id: activeScanId }); socket.disconnect(); }; }, [activeScanId, scan?.status]); const [sortColumn, setSortColumn] = useState('Severity'); const [sortDirection, setSortDirection] = useState('desc'); const savedScrollPositionRef = useRef(0); const handleOpenVulnDetail = (vuln) => { savedScrollPositionRef.current = window.scrollY || document.documentElement.scrollTop || 0; setSelectedVuln(vuln); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const handleBackToOverview = () => { const targetY = savedScrollPositionRef.current; setSelectedVuln(null); setTimeout(() => { window.scrollTo({ top: targetY, behavior: 'smooth' }); }, 30); }; const { token } = useAuth(); const navigate = useNavigate(); // Load basic scan histories and target scan session useEffect(() => { loadScanHistory(); }, [token]); useEffect(() => { if (activeScanId) { fetchScanData(activeScanId, currentPage); } }, [activeScanId, currentPage]); const loadScanHistory = async () => { try { const res = await fetch('/api/scans/history?limit=100', { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const data = await res.json(); setScans(data.scans || []); // If no scan ID was provided in URL, automatically select the most recent completed one if (!activeScanId && data.scans.length > 0) { const completedScan = data.scans.find(s => s.status === 'completed') || data.scans[0]; setActiveScanId(completedScan.id); } else if (!activeScanId && data.scans.length === 0) { setLoading(false); } } else { setLoading(false); } } catch (err) { console.error("Error loading scans history", err); setLoading(false); } }; // Sync activeScanId if the URL search parameter changes (e.g. clicking the sidebar link) useEffect(() => { if (scanIdFromUrl && scanIdFromUrl !== activeScanId) { setActiveScanId(scanIdFromUrl); } else if (!scanIdFromUrl && scans.length > 0) { const completedScan = scans.find(s => s.status === 'completed') || scans[0]; if (activeScanId !== completedScan.id) { setActiveScanId(completedScan.id); } } }, [scanIdFromUrl, scans]); const fetchScanData = async (id, page = 1) => { setLoading(true); try { const [scanRes, vulnsRes] = await Promise.all([ fetch(`/api/scans/${id}`, { headers: { 'Authorization': `Bearer ${token}` } }), fetch(`/api/scans/${id}/vulnerabilities?page=${page}&limit=50`, { headers: { 'Authorization': `Bearer ${token}` } }) ]); if (scanRes.ok && vulnsRes.ok) { const scanData = await scanRes.json(); const vulnsData = await vulnsRes.json(); // Deduplicate locally to prevent UI duplication if DB contains duplicates const uniqueVulnsMap = new Map(); (vulnsData.vulnerabilities || []).forEach(v => { const key = `${v.title}-${v.category}`; if (!uniqueVulnsMap.has(key)) { uniqueVulnsMap.set(key, v); } else { // Keep the one with higher severity if duplicates found const current = uniqueVulnsMap.get(key); const severityRank = { "Low": 0, "Medium": 1, "High": 2, "Critical": 3 }; if ((severityRank[v.severity] || 0) > (severityRank[current.severity] || 0)) { uniqueVulnsMap.set(key, v); } } }); setScan(scanData.scan); setVulnerabilities(Array.from(uniqueVulnsMap.values())); setCurrentPage(vulnsData.current_page || 1); setTotalPages(vulnsData.total_pages || 1); setTotalItems(vulnsData.total_items || uniqueVulnsMap.size); setHasNext(vulnsData.has_next || false); setHasPrev(vulnsData.has_prev || false); // Reset selected vuln when changing scans setSelectedVuln(null); } else { setError("Failed to fetch scan details."); } } catch (err) { console.error("Error fetching scan details", err); setError("Failed to connect to scanner service."); } finally { setLoading(false); } }; const handlePdfExport = async () => { if (!scan) return; setExporting(true); try { const res = await fetch(`/api/reports/${scan.id}/pdf`, { headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; let filename = ''; const disposition = res.headers.get('Content-Disposition'); if (disposition && disposition.includes('filename=')) { const match = disposition.match(/filename="?([^";]+)"?/); if (match && match[1]) { filename = match[1]; } } if (!filename) { const orgName = scan.org_name || scan.organization_name || 'Global'; const cleanOrg = orgName.replace(/[^\w]/g, '') || 'Organization'; const dateObj = new Date(scan.completed_at || scan.started_at || Date.now()); const day = String(dateObj.getDate()).padStart(2, '0'); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const year = dateObj.getFullYear(); filename = `LarShield_${cleanOrg}_Report_${day}${month}${year}.pdf`; } a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => window.URL.revokeObjectURL(url), 100); } else { setError("Failed to compile PDF Report. Server error."); } } catch (err) { console.error("PDF Export error", err); } finally { setExporting(false); } }; const toggleResolved = (vulnId) => { const updated = new Set(resolvedVulns); if (updated.has(vulnId)) { updated.delete(vulnId); } else { updated.add(vulnId); } setResolvedVulns(updated); }; const handleShare = () => { if (!scan?.id) return; const shareUrl = `${window.location.origin}/api/reports/${scan.id}/public-pdf`; navigator.clipboard.writeText(shareUrl); setShareText('Link Copied!'); setTimeout(() => setShareText('Share'), 2500); }; if (loading) { return (
sync Compiling Security Feed...
); } if (!scan) { return (
error

No scan sessions recorded.

Configure and run your first vulnerability probe using the New Scan panel.

Configure Security Scan
); } // Parse target domain name for clean UI const domain = scan.target_url.replace("https://", "").replace("http://", "").split("/")[0].split(":")[0]; // Distribution Chart logic const categoryCounts = vulnerabilities.reduce((acc, v) => { acc[v.category] = (acc[v.category] || 0) + 1; return acc; }, {}); const totalVulns = totalItems > 0 ? totalItems : vulnerabilities.length; const categoriesList = Object.keys(categoryCounts).map(cat => ({ name: cat, count: categoryCounts[cat], percentage: totalVulns > 0 ? Math.round((categoryCounts[cat] / totalVulns) * 100) : 0 })).sort((a, b) => b.count - a.count); // Severe counts const critCount = vulnerabilities.filter(v => v.severity === 'Critical').length; const highCount = vulnerabilities.filter(v => v.severity === 'High').length; const medCount = vulnerabilities.filter(v => v.severity === 'Medium').length; const lowCount = vulnerabilities.filter(v => v.severity === 'Low').length; // Filter logic const filteredVulns = filterSeverity === 'All' ? vulnerabilities : vulnerabilities.filter(v => v.severity === filterSeverity); const handleSort = (column) => { if (column === 'Sr. No.') return; if (sortColumn === column) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { setSortColumn(column); setSortDirection('desc'); // Default to descending to show highest threats first } }; const getSortedVulns = () => { return [...filteredVulns].sort((a, b) => { let aVal, bVal; switch (sortColumn) { case 'Vulnerability': aVal = a.title || ''; bVal = b.title || ''; break; case 'Severity': const rank = { "Low": 0, "Medium": 1, "High": 2, "Critical": 3 }; aVal = rank[a.severity] || 0; bVal = rank[b.severity] || 0; break; case 'Resource': aVal = a.category || ''; bVal = b.category || ''; break; case 'Score': aVal = a.cvss_score || 0; bVal = b.cvss_score || 0; break; case 'Status': aVal = resolvedVulns.has(a.id) ? 1 : 0; bVal = resolvedVulns.has(b.id) ? 1 : 0; break; default: return 0; } if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1; if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1; return 0; }); }; // SVG Gauge calculations const score = scan.security_score ?? 100; const dashOffset = 283 - (283 * score) / 100; // Evidence and payloads mapping const getProofOfDetection = (vuln) => { // Build proof from REAL scanner data first — never show fake fallback if real data exists const sections = []; if (vuln.request_details && vuln.request_details.trim()) { sections.push(`# Request Details\n${vuln.request_details.trim()}`); } if (vuln.payload && vuln.payload.trim()) { sections.push(`# Payload Used\n${vuln.payload.trim()}`); } if (vuln.response_details && vuln.response_details.trim()) { sections.push(`# Response Details\n${vuln.response_details.trim()}`); } if (vuln.evidence && vuln.evidence.trim()) { sections.push(`# Evidence\n${vuln.evidence.trim()}`); } if (vuln.exploit_poc && vuln.exploit_poc.trim()) { sections.push(`# Proof of Concept\n${vuln.exploit_poc.trim()}`); } if (sections.length > 0) return sections.join('\n\n'); // Only use synthetic fallback when NO real data at all if (vuln.category === 'Security Headers') { return `# Request Headers\nGET / HTTP/1.1\nHost: ${domain}\nUser-Agent: LarShield/2.0\n\n# Response Headers Analysis\nHTTP/1.1 200 OK\nServer: nginx\nContent-Type: text/html\n... [snip] ...\n\n[Detection] ${vuln.title}\nMissing or misconfigured attribute in server response.`; } if (vuln.category === 'SSL/TLS') { return `# TLS Handshake Probe\nopenssl s_client -connect ${domain}:443 -tls1_2\n\n# Protocol Analysis\nCONNECTED(00000003)\n[Detection] ${vuln.title}\nCertificate or protocol weakness verified during handshake negotiation.`; } if (vuln.title.includes('SQL') || vuln.category === 'Injection') { return `# Malicious Request Payload\nPOST /api/v1/query HTTP/1.1\nHost: ${domain}\nContent-Type: application/json\n\n{\n "input": "1' OR '1'='1' --"\n}\n\n# Response Analysis\nHTTP/1.1 500 Internal Server Error\n[Detection] ${vuln.title}\nDatabase error or behavioral delay confirmed injection execution.`; } if (vuln.title.includes('XSS') || vuln.title.includes('Cross-Site')) { return `# Payload Injection\nGET /search?q= HTTP/1.1\nHost: ${domain}\n\n# Response Analysis\nHTTP/1.1 200 OK\n[Detection] ${vuln.title}\nPayload reflected in DOM without sanitization.`; } // Default context-aware fallback return `# Automated Probe Log Target: ${domain} Category: ${vuln.category} Scanner Module: ${vuln.title} # Detection Output [System] Vulnerability confirmed via behavioral analysis and pattern matching. [Evidence] ${vuln.description.split('.')[0]}.`; }; const getCweId = (vuln) => { // Use real CWE IDs from database first if (vuln.cwe_ids && Array.isArray(vuln.cwe_ids) && vuln.cwe_ids.length > 0) { return vuln.cwe_ids[0]; } // Fallback to category-based inference if (vuln.title.includes("SQL Injection") || vuln.category === "Injection") return "CWE-89"; if (vuln.title.includes("XSS") || vuln.title.includes("Cross-Site Scripting")) return "CWE-79"; if (vuln.category === "Security Headers") return "CWE-693"; if (vuln.category === "SSL/TLS") return "CWE-311"; if (vuln.category === "Insecure Deserialization") return "CWE-502"; if (vuln.category === "SSRF") return "CWE-918"; if (vuln.category === "Path Traversal") return "CWE-22"; if (vuln.category === "Open Redirect") return "CWE-601"; if (vuln.category === "CSRF") return "CWE-352"; if (vuln.category === "Cookie Security") return "CWE-1004"; return "CWE-200"; }; // If a vulnerability is selected, render the high fidelity details view (da08996ed26048719f8bf496a07abc3b) if (selectedVuln) { const isResolved = resolvedVulns.has(selectedVuln.id); const cweId = getCweId(selectedVuln); const payloadCode = getProofOfDetection(selectedVuln); // Color theme classes matching severity let badgeBg = 'bg-surface-variant text-on-surface-variant border-outline-variant'; let iconName = 'info'; let severityLabel = selectedVuln.severity.toUpperCase(); if (selectedVuln.severity === 'Critical') { badgeBg = 'bg-error-container text-on-error-container border-error/20'; iconName = 'warning'; } else if (selectedVuln.severity === 'High') { badgeBg = 'bg-tertiary-container/15 text-tertiary border-tertiary/20'; iconName = 'warning'; } return (
{/* Breadcrumbs & Actions */}
chevron_right chevron_right {selectedVuln.title}
{/* Vulnerability Header */}
{iconName} {severityLabel}

{selectedVuln.title}

calendar_today Detected: {new Date(selectedVuln.detected_at).toLocaleDateString()}
dns Asset: {domain}
code {cweId}
speed CVSS: {selectedVuln.cvss_score}
{selectedVuln.owasp_category && (
security {selectedVuln.owasp_category}
)} {selectedVuln.confidence && (
verified {selectedVuln.confidence} confidence
)} {isResolved && (
check_circle Resolved
)}
{/* Main Details Grid */}
{/* Left Column (Description & Technicals) */}
{/* Description Section */}

description Description

{selectedVuln.description}

Impact Assessment: High probability of unauthorized exploitation. An attacker could bypass perimeter authentication protocols, compromise transport records, or escalate privileges within the application runtime context.
{/* Proof of Detection (Code Snippet) */}

terminal Proof of Detection

Engine Payload Audit Log
{/* Remediation */}

build Remediation Steps

Apply the following security configuration or architectural code adjustments to mitigate this exposure vector:

{selectedVuln.remediation}
{selectedVuln.remediation_code && selectedVuln.remediation_code.trim() && (
code Remediation Code Snippet
)}
{/* Right Column (Meta & References) */}
{/* Threat Gauge / Status Card */}

Exploitability

= 8.0 ? 'text-error' : selectedVuln.cvss_score >= 5.0 ? 'text-tertiary' : 'text-primary'} d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" fill="none" stroke="currentColor" strokeDasharray={`${selectedVuln.cvss_score * 10}, 100`} strokeWidth="3.5" strokeLinecap="round" >
{selectedVuln.cvss_score} CVSS v3

{selectedVuln.cvss_score >= 9.0 ? 'Highly critical exploit vectors. Direct patching demanded.' : selectedVuln.cvss_score >= 7.0 ? 'High sensitivity breach threat. Prioritize scheduling.' : 'Moderate security policy alignment recommendation.'}

{/* Environment Context */}

Context

Target
{domain}
Category
{selectedVuln.category}
{selectedVuln.owasp_category && (
OWASP
{selectedVuln.owasp_category}
)}
Confidence
{selectedVuln.confidence || 'Medium'}
CVSS v3
= 9 ? 'text-error' : selectedVuln.cvss_score >= 7 ? 'text-tertiary' : 'text-on-surface' }`}>{selectedVuln.cvss_score}
{selectedVuln.cwe_ids && selectedVuln.cwe_ids.length > 0 && (
CWE IDs
{selectedVuln.cwe_ids.join(', ')}
)}
{/* References */}

References

); } // Render scan dashboard view (eb15a48970e543afbfe15786d831c1c4) return (
{error && (
error {error}
)} {scan.status !== 'completed' && scan.status !== 'failed' && (
terminal

Live Scan Console

● RUNNING
{liveLogs.length === 0 && (
Initializing scanner engines... waiting for output...
)} {liveLogs.map((log, i) => { // Log might be an object or string const msg = typeof log === 'object' ? `[${log.level || 'INFO'}] ${log.message}` : log; const isError = msg.includes('ERROR') || msg.includes('CRITICAL'); const isVuln = msg.includes('Vulnerability') || msg.includes('Found'); return (
{msg}
); })} {/* Auto-scroll anchor */}
el?.scrollIntoView({ behavior: 'smooth' })} />
)} {/* Page Header */}
Vulnerabilities chevron_right {domain}

Scan Results Report

Completed: {new Date(scan.completed_at || scan.started_at).toLocaleString()} • Duration: { scan.completed_at && scan.started_at ? (() => { const diff = new Date(scan.completed_at) - new Date(scan.started_at); const minutes = Math.floor(diff / 60000); const seconds = Math.floor((diff % 60000) / 1000); return `${minutes}m ${seconds}s`; })() : 'N/A' }

{/* Target Scan Switcher */} {scans.length > 1 && ( )}
{/* Top Row: Full Width Score & Summary Cards */}
{/* Overall Score Card */}
{score} /100

Security Score

{score >= 80 ? 'Your environment is relatively secure. Fix remaining vulnerability warnings to perfect score.' : 'System vulnerabilities pose risk. Action recommended immediately.'}

{/* Risk Indicator Card: Total */}
Total bug_report
{totalVulns}
{/* Risk Indicator Card: Critical */}
Critical error
{critCount}
{/* Risk Indicator Card: High */}
High warning
{highCount}
{/* Risk Indicator Card: Medium */}
Medium info
{medCount}
{/* Risk Indicator Card: Low */}
Low shield
{lowCount}
{/* Bento Grid Metrics Layout */}
{/* Left Column (Vulnerability Distribution & Findings Table) */}
{/* Vulnerability Distribution Chart Card */}

Vulnerability Distribution

{categoriesList.length === 0 ? (
No vulnerabilities detected to categorize.
) : ( categoriesList.map((cat, idx) => { const colors = ['bg-primary', 'bg-secondary', 'bg-tertiary', 'bg-slate-400']; const barColor = colors[idx % colors.length]; return (
{cat.name} {cat.count} finding{cat.count > 1 ? 's' : ''} ({cat.percentage}%)
); }) )}
{/* Detailed Findings Table Card */}

Detailed Findings ({filteredVulns.length})

{/* Severity filter button filters list */}
{['All', 'Critical', 'High', 'Medium', 'Low'].map(sev => { const isFiltered = filterSeverity === sev; return ( ); })}
{['Vulnerability', 'Severity', 'Resource', 'Score', 'Status'].map(h => ( ))} {filteredVulns.length === 0 ? ( ) : ( getSortedVulns().map((v, index) => { const isResolved = resolvedVulns.has(v.id); let sevBadge = 'bg-surface-variant text-on-surface-variant border-outline-variant'; let sevIcon = 'info'; if (v.severity === 'Critical') { sevBadge = 'bg-error-container text-on-error-container border-error/10'; sevIcon = 'error'; } else if (v.severity === 'High') { sevBadge = 'bg-tertiary-container/15 text-tertiary border-tertiary/20'; sevIcon = 'warning'; } else if (v.severity === 'Medium') { sevBadge = 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20'; sevIcon = 'warning'; } return ( handleOpenVulnDetail(v)} className="border-b border-outline-variant hover:bg-surface-container-low transition-colors group cursor-pointer" > ); }) )}
Sr. No. handleSort(h)} className="p-md font-label-sm text-label-sm text-on-surface-variant uppercase tracking-wider font-semibold cursor-pointer hover:bg-surface-container-high transition-colors group" >
{h} {sortColumn === h && sortDirection === 'desc' ? 'arrow_downward' : 'arrow_upward'}
No vulnerability findings match the selected severity category.
{index + 1} {v.title} {sevIcon} {v.severity} {v.category} {v.cvss_score} {isResolved ? ( check_circle Resolved ) : ( pending Open )}
Page {currentPage} of {totalPages}
{/* Right Column (AI Advisory & Action Center) */}
psychiatry

Recommendations

{vulnerabilities.filter(v => v.severity === 'Critical' || v.severity === 'High').slice(0, 3).map((v) => (

{v.title}

{v.description}

))} {vulnerabilities.filter(v => v.severity === 'Critical' || v.severity === 'High').length === 0 && (
Great! No Critical or High vulnerabilities remaining to fix.
)}
); };