/* eslint-disable import/extensions */ import { ArrowPathIcon } from '@heroicons/react/24/outline'; import { Cvss3P1 } from 'ae-cvss-calculator'; import { t } from 'i18next'; import React, { useEffect, useState } from 'react'; import { toast } from 'sonner'; import PrimaryButton from '@/components/button/PrimaryButton'; import { postDescriptionCVSS } from '@/services/vulnerabilities'; import MetricGroup from './metricGroup'; import ScoreBox from './scoreBox'; type CVSSProp = { handleCvssChange: (value: string) => void; cvssStringInitial?: string; handleCvssRecomendation: () => string; }; const generateCVSSVector = ( AV: string, AC: string, PR: string, UI: string, S: string, C: string, I: string, A: string, ): string => { const cvssComponents = { AV, AC, PR, UI, S, C, I, A, }; const componentMapping: Record = { AV: 'AV', AC: 'AC', PR: 'PR', UI: 'UI', S: 'S', C: 'C', I: 'I', A: 'A', }; const valueMapping: Record = { Network: 'N', 'Adjacent Network': 'A', Local: 'L', Physical: 'P', Low: 'L', High: 'H', None: 'N', Required: 'R', Unchanged: 'U', Changed: 'C', }; let vectorString = 'CVSS:3.1/'; Object.entries(cvssComponents).forEach(([key, value], index) => { if (value) { if (index > 0) { vectorString += '/'; } vectorString += `${componentMapping[key]}:${valueMapping[value]}`; } }); return vectorString; }; const CVSSCalculator: React.FC = ({ cvssStringInitial, handleCvssChange, handleCvssRecomendation, }) => { const [AV, setAV] = useState(''); const [AC, setAC] = useState(''); const [PR, setPR] = useState(''); const [UI, setUI] = useState(''); const [S, setS] = useState(''); const [C, setC] = useState(''); const [I, setI] = useState(''); const [A, setA] = useState(''); const [recommendedAV, setRecomendedAV] = useState(''); const [recommendedAC, setRecomendedAC] = useState(''); const [recommendedPR, setRecomendedPR] = useState(''); const [recommendedUI, setRecomendedUI] = useState(''); const [recommendedS, setRecomendedS] = useState(''); const [recommendedC, setRecomendedC] = useState(''); const [recommendedI, setRecomendedI] = useState(''); const [recommendedA, setRecomendedA] = useState(''); const [changed, setChanged] = useState(false); const noneLowHighOptions = [ { label: t('cvss.none'), value: 'None' }, { label: t('cvss.low'), value: 'Low' }, { label: t('cvss.high'), value: 'High' }, ]; const avOptions = [ { label: t('cvss.network'), value: 'Network' }, { label: t('cvss.adjacentNetwork'), value: 'Adjacent Network' }, { label: t('cvss.local'), value: 'Local' }, { label: t('cvss.physical'), value: 'Physical' }, ]; const acOptions = [ { label: t('cvss.low'), value: 'Low' }, { label: t('cvss.high'), value: 'High' }, ]; const uiOptions = [ { label: t('cvss.none'), value: 'None' }, { label: t('cvss.required'), value: 'Required' }, ]; const scopeOptions = [ { label: t('cvss.unchanged'), value: 'Unchanged' }, { label: t('cvss.changed'), value: 'Changed' }, ]; const valueMappings: Record> = { AV: { N: 'Network', A: 'Adjacent Network', L: 'Local', P: 'Physical' }, AC: { H: 'High', L: 'Low' }, PR: { N: 'None', L: 'Low', H: 'High' }, UI: { N: 'None', R: 'Required' }, S: { U: 'Unchanged', C: 'Changed' }, C: { H: 'High', L: 'Low', N: 'None' }, I: { H: 'High', L: 'Low', N: 'None' }, A: { H: 'High', L: 'Low', N: 'None' }, }; const parseCVSSVector = (vector: string) => { const parts = vector.split('/').slice(1); parts.forEach(part => { if (part === '') { setChanged(true); return; } const [key, value] = part.split(':'); const mappedValue = valueMappings[key][value] || ''; switch (key) { case 'AV': setAV(mappedValue); break; case 'AC': setAC(mappedValue); break; case 'PR': setPR(mappedValue); break; case 'UI': setUI(mappedValue); break; case 'S': setS(mappedValue); break; case 'C': setC(mappedValue); break; case 'I': setI(mappedValue); break; case 'A': setA(mappedValue); break; default: break; } }); setChanged(true); }; const parseCVSSRecommendationVector = (vector: string) => { const parts = vector.split('/').slice(1); parts.forEach(part => { const [key, value] = part.split(':'); const mappedValue = valueMappings[key][value] || ''; switch (key) { case 'AV': setRecomendedAV(mappedValue); break; case 'AC': setRecomendedAC(mappedValue); break; case 'PR': setRecomendedPR(mappedValue); break; case 'UI': setRecomendedUI(mappedValue); break; case 'S': setRecomendedS(mappedValue); break; case 'C': setRecomendedC(mappedValue); break; case 'I': setRecomendedI(mappedValue); break; case 'A': setRecomendedA(mappedValue); break; default: break; } }); setChanged(true); }; const [currentScore, setCurrentScore] = useState(0); const [isComplete, setIsComplete] = useState(false); useEffect(() => { if (cvssStringInitial) { parseCVSSVector(cvssStringInitial); } else { setChanged(true); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (changed) { const cvssVector = generateCVSSVector(AV, AC, PR, UI, S, C, I, A); handleCvssChange(cvssVector); if (cvssVector.length === 44) { const cvss3 = new Cvss3P1(cvssVector); setIsComplete(true); setCurrentScore(cvss3.calculateExactOverallScore()); } else { setIsComplete(false); } } }, [AV, AC, PR, UI, S, C, I, A, changed, handleCvssChange]); const cleanRecommendations = () => { setRecomendedAV(''); setRecomendedAC(''); setRecomendedPR(''); setRecomendedUI(''); setRecomendedS(''); setRecomendedC(''); setRecomendedI(''); setRecomendedA(''); }; const [isLoadingRecommendation, setIsLoadingRecommendation] = useState(false); const recommendCVSS = async () => { const vulnDescription = handleCvssRecomendation(); if (vulnDescription === '') { return; } setIsLoadingRecommendation(true); try { const cvssString = (await postDescriptionCVSS({ vuln: vulnDescription })) .result; parseCVSSRecommendationVector(cvssString); } catch (err) { toast.error(t('errorRecommendingCVSS')); } finally { setIsLoadingRecommendation(false); } }; return (

{t('cvss.title')}


{t('recommendCVSS')} {isLoadingRecommendation ? ( ) : null}
{t('cleanRecommendations')}
); }; export default CVSSCalculator;