UNPKG

@turingpointde/cvss.js

Version:

A tiny library to work with cvss vectors

297 lines (265 loc) 7.96 kB
import { CvssVectorObject, DetailedVectorObject, MetricUnion } from "./types"; import { definitions as definitions3_0 } from "./cvss_3_0"; import { definitions as definitions4_0 } from "./cvss_4_0"; /** * Finds the vector's metric by it's abbreviation */ function findMetric(abbr: string, cvssVersion: string) { const definitions = cvssVersion === "4.0" ? definitions4_0 : definitions3_0; return definitions.definitions.find((def) => def.abbr === abbr); } /** * Finds the vector's value for a specific metric */ function findMetricValue<T extends MetricUnion>( abbr: string, vectorObject: CvssVectorObject ) { const definition = findMetric(abbr, vectorObject.CVSS); let value = definition?.metrics.find( (metric) => metric.abbr === vectorObject[definition.abbr] ); return value as T; } function roundUpApprox(num: number, precision: number) { precision = Math.pow(10, precision); return Math.ceil(num * precision) / precision; } function roundUpExact(num: number) { const int_input = Math.round(num * 100000); if (int_input % 10000 === 0) { return int_input / 100000; } else { return (Math.floor(int_input / 10000) + 1) / 10; } } /** * Retrieves an object of vector's metrics */ function getVectorObject(vector: string) { const vectorArray = vector.split("/"); const definitions = vector.includes("4.0") ? definitions4_0 : definitions3_0; const vectorObject = definitions.definitions .map((definition) => definition.abbr) .reduce((acc, curr) => { // @ts-expect-error acc[curr] = "X"; return acc; }, {} as CvssVectorObject); for (const entry of vectorArray) { const values = entry.split(":"); // @ts-expect-error vectorObject[values[0]] = values[1]; } return vectorObject; } /** * Returns a vector without undefined values */ function getCleanVectorString(vector: string) { const vectorArray = vector.split("/"); const cleanVectorArray: string[] = []; for (const entry of vectorArray) { const values = entry.split(":"); if (values[1] !== "X") cleanVectorArray.push(entry); } return cleanVectorArray.join("/"); } /** * Retrieves an object of vector's metrics */ function getDetailedVectorObject(vector: string) { const vectorArray = vector.split("/"); const vectorObject = vectorArray.reduce( (vectorObjectAccumulator, vectorItem, index) => { const values = vectorItem.split(":"); const metrics = { ...vectorObjectAccumulator.metrics }; if (index) { const vectorDef = findMetric(values[0], vectorArray[0].split(":")[1]); const detailedVectorObject = { name: vectorDef?.name, abbr: vectorDef?.abbr, fullName: `${vectorDef?.name} (${vectorDef?.abbr})`, value: vectorDef?.metrics.find((def) => def.abbr === values[1])?.name, valueAbbr: values[1], }; return Object.assign(vectorObjectAccumulator, { metrics: Object.assign(metrics, { [values[0].trim()]: detailedVectorObject, }), }); } else { return Object.assign(vectorObjectAccumulator, { [values[0].trim()]: values[1], }); } }, { metrics: {}, CVSS: "" } as DetailedVectorObject ); return vectorObject; } /** * Calculates the rating of the given vector */ function getRating(score: number) { let rating = "None"; if (score === 0) { rating = "None"; } else if (score <= 3.9) { rating = "Low"; } else if (score <= 6.9) { rating = "Medium"; } else if (score <= 8.9) { rating = "High"; } else { rating = "Critical"; } return rating; } /** * Checks whether the vector passed is valid */ function isVectorValid(vector: string) { const definitions = vector.includes("4.0") ? definitions4_0 : definitions3_0; /** * This function is used to scan the definitions file and join all * abbreviations in a format that RegExp understands. * * Exit example: * ((((((((((AV:[NALP]|AC:[LH])|PR:[NLH])|UI:[NR])|S:[UC])|C:[NLW])|I:[NLW])|A:[NLW])|E:[XUPFH])|RL:[XOTWU])|RC:[XURC]) */ const expression = definitions.definitions.reduce( (accumulator, currentValue, index) => { const serializedAbbr = `${ currentValue.abbr }:[${currentValue.metrics.reduce((accumulator2, currentValue2) => { return accumulator2 + currentValue2.abbr; }, "")}]`; if (index !== 0) { return `(${accumulator}|${serializedAbbr})`; } else { return serializedAbbr; } }, "" ); const totalExpressionVector = new RegExp( "^CVSS:(3.(0|1)|4.0)(/" + expression + ")+$" ); //Checks if the vector is in valid format if (!totalExpressionVector.test(vector)) { return false; } /** * Scans the definitions file and returns an array of each registered abbreviation * with its possible values. * * Exit example: * [/\/AV:[NALP]/g, /\/AC:[LH]/g, /\/PR:[NLH]/g, /\/UI:[NR]/g, /\/S:[UC]/g,] * * A / at the beginning serves for the algorithm not to confuse abbreviations as AC and C. */ const allExpressions = definitions.definitions.map((currentValue) => { return new RegExp( `/${currentValue.abbr}:[${currentValue.metrics.reduce( (accumulator2, currentValue2) => { return accumulator2 + currentValue2.abbr; }, "" )}]`, "g" ); }); for (const regex of allExpressions) { if ((vector.match(regex) || []).length > 1) { return false; } } /** * Scans the definitions file and returns the array of mandatory registered abbreviation * with its possible values. */ const mandatoryExpressions = definitions.definitions .filter((definition) => definition.mandatory) .map((currentValue) => { return new RegExp( `/${currentValue.abbr}:[${currentValue.metrics.reduce( (accumulator2, currentValue2) => { return accumulator2 + currentValue2.abbr; }, "" )}]`, "g" ); }); //Checks whether all mandatory parameters are present in the vector for (const regex of mandatoryExpressions) { if ((vector.match(regex) || []).length < 1) { return false; } } return true; } /** * This transforms an object in the format of getVectorObject() * and parses it to a CVSS comaptible string */ function parseVectorObjectToString(cvssInput: string | CvssVectorObject) { if (typeof cvssInput === "string") { return cvssInput; } let vectorString = `CVSS:${cvssInput["CVSS"]}/`; const definitions = cvssInput.CVSS === "4.0" ? definitions4_0 : definitions3_0; for (const entry of definitions["definitions"]) { const metric = entry.abbr; if (Object.prototype.hasOwnProperty.call(cvssInput, metric)) { vectorString += `${metric}:${cvssInput[metric]}/`; } } vectorString = vectorString.slice(0, -1); return vectorString; } /** * Updates the value of a singular metric and returns the updated clean vector string */ function updateVectorValue( vector: string, metric: keyof CvssVectorObject, value: string ) { const vectorObject = getVectorObject(vector); // @ts-expect-error vectorObject[metric] = value; const vectorString = parseVectorObjectToString(vectorObject); return getCleanVectorString(vectorString); } /** * Retrives the version from the vector string */ function getVersion(vector: string) { const version = vector.split("/"); if (version[0] === "CVSS:3.0") { return "3.0"; } else if (version[0] === "CVSS:3.1") { return "3.1"; } else if (version[0] === "CVSS:4.0") { return "4.0"; } else { return "Error"; } } export const util = { roundUpExact, roundUpApprox, getVectorObject, getDetailedVectorObject, findMetric, findMetricValue, getRating, updateVectorValue, isVectorValid, parseVectorObjectToString, getVersion, getCleanVectorString, };