@rohit_coder/cvss
Version:
The Common Vulnerability Scoring System ([CVSS](https://www.first.org/cvss/)) [base](https://www.first.org/cvss/specification-document#Base-Metrics) [score](https://www.first.org/cvss/specification-document#1-2-Scoring) calculator and validator library wr
1,324 lines (1,320 loc) • 54 kB
JavaScript
var BaseMetric;
(function (BaseMetric) {
BaseMetric["ATTACK_VECTOR"] = "AV";
BaseMetric["ATTACK_COMPLEXITY"] = "AC";
BaseMetric["PRIVILEGES_REQUIRED"] = "PR";
BaseMetric["USER_INTERACTION"] = "UI";
BaseMetric["SCOPE"] = "S";
BaseMetric["CONFIDENTIALITY"] = "C";
BaseMetric["INTEGRITY"] = "I";
BaseMetric["AVAILABILITY"] = "A";
})(BaseMetric || (BaseMetric = {}));
var TemporalMetric;
(function (TemporalMetric) {
TemporalMetric["EXPLOIT_CODE_MATURITY"] = "E";
TemporalMetric["REMEDIATION_LEVEL"] = "RL";
TemporalMetric["REPORT_CONFIDENCE"] = "RC";
})(TemporalMetric || (TemporalMetric = {}));
var EnvironmentalMetric;
(function (EnvironmentalMetric) {
EnvironmentalMetric["CONFIDENTIALITY_REQUIREMENT"] = "CR";
EnvironmentalMetric["INTEGRITY_REQUIREMENT"] = "IR";
EnvironmentalMetric["AVAILABILITY_REQUIREMENT"] = "AR";
EnvironmentalMetric["MODIFIED_ATTACK_VECTOR"] = "MAV";
EnvironmentalMetric["MODIFIED_ATTACK_COMPLEXITY"] = "MAC";
EnvironmentalMetric["MODIFIED_PRIVILEGES_REQUIRED"] = "MPR";
EnvironmentalMetric["MODIFIED_USER_INTERACTION"] = "MUI";
EnvironmentalMetric["MODIFIED_SCOPE"] = "MS";
EnvironmentalMetric["MODIFIED_CONFIDENTIALITY"] = "MC";
EnvironmentalMetric["MODIFIED_INTEGRITY"] = "MI";
EnvironmentalMetric["MODIFIED_AVAILABILITY"] = "MA";
})(EnvironmentalMetric || (EnvironmentalMetric = {}));
const baseMetrics = [
BaseMetric.ATTACK_VECTOR,
BaseMetric.ATTACK_COMPLEXITY,
BaseMetric.PRIVILEGES_REQUIRED,
BaseMetric.USER_INTERACTION,
BaseMetric.SCOPE,
BaseMetric.CONFIDENTIALITY,
BaseMetric.INTEGRITY,
BaseMetric.AVAILABILITY
];
const temporalMetrics = [
TemporalMetric.EXPLOIT_CODE_MATURITY,
TemporalMetric.REMEDIATION_LEVEL,
TemporalMetric.REPORT_CONFIDENCE
];
const environmentalMetrics = [
EnvironmentalMetric.AVAILABILITY_REQUIREMENT,
EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT,
EnvironmentalMetric.INTEGRITY_REQUIREMENT,
EnvironmentalMetric.MODIFIED_ATTACK_VECTOR,
EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY,
EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED,
EnvironmentalMetric.MODIFIED_USER_INTERACTION,
EnvironmentalMetric.MODIFIED_SCOPE,
EnvironmentalMetric.MODIFIED_CONFIDENTIALITY,
EnvironmentalMetric.MODIFIED_INTEGRITY,
EnvironmentalMetric.MODIFIED_AVAILABILITY
];
const baseMetricValues = {
[BaseMetric.ATTACK_VECTOR]: ['N', 'A', 'L', 'P'],
[BaseMetric.ATTACK_COMPLEXITY]: ['L', 'H'],
[BaseMetric.PRIVILEGES_REQUIRED]: ['N', 'L', 'H'],
[BaseMetric.USER_INTERACTION]: ['N', 'R'],
[BaseMetric.SCOPE]: ['U', 'C'],
[BaseMetric.CONFIDENTIALITY]: ['N', 'L', 'H'],
[BaseMetric.INTEGRITY]: ['N', 'L', 'H'],
[BaseMetric.AVAILABILITY]: ['N', 'L', 'H'], // None, Low, High
};
const temporalMetricValues = {
[TemporalMetric.EXPLOIT_CODE_MATURITY]: ['X', 'H', 'F', 'P', 'U'],
[TemporalMetric.REMEDIATION_LEVEL]: ['X', 'U', 'W', 'T', 'O'],
[TemporalMetric.REPORT_CONFIDENCE]: ['X', 'C', 'R', 'U']
};
const environmentalMetricValues = {
[EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: ['X', 'H', 'M', 'L'],
[EnvironmentalMetric.INTEGRITY_REQUIREMENT]: ['X', 'H', 'M', 'L'],
[EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: ['X', 'H', 'M', 'L'],
[EnvironmentalMetric.MODIFIED_ATTACK_VECTOR]: ['X', 'N', 'A', 'L', 'P'],
[EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY]: ['X', 'L', 'H'],
[EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED]: ['X', 'N', 'L', 'H'],
[EnvironmentalMetric.MODIFIED_USER_INTERACTION]: ['X', 'N', 'R'],
[EnvironmentalMetric.MODIFIED_SCOPE]: ['X', 'U', 'C'],
[EnvironmentalMetric.MODIFIED_CONFIDENTIALITY]: ['X', 'N', 'L', 'H'],
[EnvironmentalMetric.MODIFIED_INTEGRITY]: ['X', 'N', 'L', 'H'],
[EnvironmentalMetric.MODIFIED_AVAILABILITY]: ['X', 'N', 'L', 'H']
};
// Copyright FIRST, Red Hat, and contributors
// SPDX-License-Identifier: BSD-2-Clause
// CVSS v4.0 metrics ordering and valid values
const expectedMetricOrder = {
// Base (11 metrics)
"AV": ["N", "A", "L", "P"],
"AC": ["L", "H"],
"AT": ["N", "P"],
"PR": ["N", "L", "H"],
"UI": ["N", "P", "A"],
"VC": ["H", "L", "N"],
"VI": ["H", "L", "N"],
"VA": ["H", "L", "N"],
"SC": ["H", "L", "N"],
"SI": ["H", "L", "N"],
"SA": ["H", "L", "N"],
// Threat (1 metric)
"E": ["X", "A", "P", "U"],
// Environmental (14 metrics)
"CR": ["X", "H", "M", "L"],
"IR": ["X", "H", "M", "L"],
"AR": ["X", "H", "M", "L"],
"MAV": ["X", "N", "A", "L", "P"],
"MAC": ["X", "L", "H"],
"MAT": ["X", "N", "P"],
"MPR": ["X", "N", "L", "H"],
"MUI": ["X", "N", "P", "A"],
"MVC": ["X", "H", "L", "N"],
"MVI": ["X", "H", "L", "N"],
"MVA": ["X", "H", "L", "N"],
"MSC": ["X", "H", "L", "N"],
"MSI": ["X", "S", "H", "L", "N"],
"MSA": ["X", "S", "H", "L", "N"],
// Supplemental (6 metrics)
"S": ["X", "N", "P"],
"AU": ["X", "N", "Y"],
"R": ["X", "A", "U", "I"],
"V": ["X", "D", "C"],
"RE": ["X", "L", "M", "H"],
"U": ["X", "Clear", "Green", "Amber", "Red"],
};
// max severity distances in EQs MacroVectors (+1)
const maxSeverityV4 = {
"eq1": {
0: 1,
1: 4,
2: 5
},
"eq2": {
0: 1,
1: 2
},
"eq3eq6": {
0: { 0: 7, 1: 6 },
1: { 0: 8, 1: 8 },
2: { 1: 10 }
},
"eq4": {
0: 6,
1: 5,
2: 4
},
"eq5": {
0: 1,
1: 1,
2: 1
},
};
const cvssLookup_globalV4 = {
"000000": 10,
"000001": 9.9,
"000010": 9.8,
"000011": 9.5,
"000020": 9.5,
"000021": 9.2,
"000100": 10,
"000101": 9.6,
"000110": 9.3,
"000111": 8.7,
"000120": 9.1,
"000121": 8.1,
"000200": 9.3,
"000201": 9,
"000210": 8.9,
"000211": 8,
"000220": 8.1,
"000221": 6.8,
"001000": 9.8,
"001001": 9.5,
"001010": 9.5,
"001011": 9.2,
"001020": 9,
"001021": 8.4,
"001100": 9.3,
"001101": 9.2,
"001110": 8.9,
"001111": 8.1,
"001120": 8.1,
"001121": 6.5,
"001200": 8.8,
"001201": 8,
"001210": 7.8,
"001211": 7,
"001220": 6.9,
"001221": 4.8,
"002001": 9.2,
"002011": 8.2,
"002021": 7.2,
"002101": 7.9,
"002111": 6.9,
"002121": 5,
"002201": 6.9,
"002211": 5.5,
"002221": 2.7,
"010000": 9.9,
"010001": 9.7,
"010010": 9.5,
"010011": 9.2,
"010020": 9.2,
"010021": 8.5,
"010100": 9.5,
"010101": 9.1,
"010110": 9,
"010111": 8.3,
"010120": 8.4,
"010121": 7.1,
"010200": 9.2,
"010201": 8.1,
"010210": 8.2,
"010211": 7.1,
"010220": 7.2,
"010221": 5.3,
"011000": 9.5,
"011001": 9.3,
"011010": 9.2,
"011011": 8.5,
"011020": 8.5,
"011021": 7.3,
"011100": 9.2,
"011101": 8.2,
"011110": 8,
"011111": 7.2,
"011120": 7,
"011121": 5.9,
"011200": 8.4,
"011201": 7,
"011210": 7.1,
"011211": 5.2,
"011220": 5,
"011221": 3,
"012001": 8.6,
"012011": 7.5,
"012021": 5.2,
"012101": 7.1,
"012111": 5.2,
"012121": 2.9,
"012201": 6.3,
"012211": 2.9,
"012221": 1.7,
"100000": 9.8,
"100001": 9.5,
"100010": 9.4,
"100011": 8.7,
"100020": 9.1,
"100021": 8.1,
"100100": 9.4,
"100101": 8.9,
"100110": 8.6,
"100111": 7.4,
"100120": 7.7,
"100121": 6.4,
"100200": 8.7,
"100201": 7.5,
"100210": 7.4,
"100211": 6.3,
"100220": 6.3,
"100221": 4.9,
"101000": 9.4,
"101001": 8.9,
"101010": 8.8,
"101011": 7.7,
"101020": 7.6,
"101021": 6.7,
"101100": 8.6,
"101101": 7.6,
"101110": 7.4,
"101111": 5.8,
"101120": 5.9,
"101121": 5,
"101200": 7.2,
"101201": 5.7,
"101210": 5.7,
"101211": 5.2,
"101220": 5.2,
"101221": 2.5,
"102001": 8.3,
"102011": 7,
"102021": 5.4,
"102101": 6.5,
"102111": 5.8,
"102121": 2.6,
"102201": 5.3,
"102211": 2.1,
"102221": 1.3,
"110000": 9.5,
"110001": 9,
"110010": 8.8,
"110011": 7.6,
"110020": 7.6,
"110021": 7,
"110100": 9,
"110101": 7.7,
"110110": 7.5,
"110111": 6.2,
"110120": 6.1,
"110121": 5.3,
"110200": 7.7,
"110201": 6.6,
"110210": 6.8,
"110211": 5.9,
"110220": 5.2,
"110221": 3,
"111000": 8.9,
"111001": 7.8,
"111010": 7.6,
"111011": 6.7,
"111020": 6.2,
"111021": 5.8,
"111100": 7.4,
"111101": 5.9,
"111110": 5.7,
"111111": 5.7,
"111120": 4.7,
"111121": 2.3,
"111200": 6.1,
"111201": 5.2,
"111210": 5.7,
"111211": 2.9,
"111220": 2.4,
"111221": 1.6,
"112001": 7.1,
"112011": 5.9,
"112021": 3,
"112101": 5.8,
"112111": 2.6,
"112121": 1.5,
"112201": 2.3,
"112211": 1.3,
"112221": 0.6,
"200000": 9.3,
"200001": 8.7,
"200010": 8.6,
"200011": 7.2,
"200020": 7.5,
"200021": 5.8,
"200100": 8.6,
"200101": 7.4,
"200110": 7.4,
"200111": 6.1,
"200120": 5.6,
"200121": 3.4,
"200200": 7,
"200201": 5.4,
"200210": 5.2,
"200211": 4,
"200220": 4,
"200221": 2.2,
"201000": 8.5,
"201001": 7.5,
"201010": 7.4,
"201011": 5.5,
"201020": 6.2,
"201021": 5.1,
"201100": 7.2,
"201101": 5.7,
"201110": 5.5,
"201111": 4.1,
"201120": 4.6,
"201121": 1.9,
"201200": 5.3,
"201201": 3.6,
"201210": 3.4,
"201211": 1.9,
"201220": 1.9,
"201221": 0.8,
"202001": 6.4,
"202011": 5.1,
"202021": 2,
"202101": 4.7,
"202111": 2.1,
"202121": 1.1,
"202201": 2.4,
"202211": 0.9,
"202221": 0.4,
"210000": 8.8,
"210001": 7.5,
"210010": 7.3,
"210011": 5.3,
"210020": 6,
"210021": 5,
"210100": 7.3,
"210101": 5.5,
"210110": 5.9,
"210111": 4,
"210120": 4.1,
"210121": 2,
"210200": 5.4,
"210201": 4.3,
"210210": 4.5,
"210211": 2.2,
"210220": 2,
"210221": 1.1,
"211000": 7.5,
"211001": 5.5,
"211010": 5.8,
"211011": 4.5,
"211020": 4,
"211021": 2.1,
"211100": 6.1,
"211101": 5.1,
"211110": 4.8,
"211111": 1.8,
"211120": 2,
"211121": 0.9,
"211200": 4.6,
"211201": 1.8,
"211210": 1.7,
"211211": 0.7,
"211220": 0.8,
"211221": 0.2,
"212001": 5.3,
"212011": 2.4,
"212021": 1.4,
"212101": 2.4,
"212111": 1.2,
"212121": 0.5,
"212201": 1,
"212211": 0.3,
"212221": 0.1,
};
const maxComposed = {
// EQ1
"eq1": {
0: ["AV:N/PR:N/UI:N/"],
1: ["AV:A/PR:N/UI:N/", "AV:N/PR:L/UI:N/", "AV:N/PR:N/UI:P/"],
2: ["AV:P/PR:N/UI:N/", "AV:A/PR:L/UI:P/"]
},
// EQ2
"eq2": {
0: ["AC:L/AT:N/"],
1: ["AC:H/AT:N/", "AC:L/AT:P/"]
},
// EQ3+EQ6
"eq3": {
0: { "0": ["VC:H/VI:H/VA:H/CR:H/IR:H/AR:H/"], "1": ["VC:H/VI:H/VA:L/CR:M/IR:M/AR:H/", "VC:H/VI:H/VA:H/CR:M/IR:M/AR:M/"] },
1: { "0": ["VC:L/VI:H/VA:H/CR:H/IR:H/AR:H/", "VC:H/VI:L/VA:H/CR:H/IR:H/AR:H/"], "1": ["VC:L/VI:H/VA:L/CR:H/IR:M/AR:H/", "VC:L/VI:H/VA:H/CR:H/IR:M/AR:M/", "VC:H/VI:L/VA:H/CR:M/IR:H/AR:M/", "VC:H/VI:L/VA:L/CR:M/IR:H/AR:H/", "VC:L/VI:L/VA:H/CR:H/IR:H/AR:M/"] },
2: { "1": ["VC:L/VI:L/VA:L/CR:H/IR:H/AR:H/"] },
},
// EQ4
"eq4": {
0: ["SC:H/SI:S/SA:S/"],
1: ["SC:H/SI:H/SA:H/"],
2: ["SC:L/SI:L/SA:L/"]
},
// EQ5
"eq5": {
0: ["E:A/"],
1: ["E:P/"],
2: ["E:U/"],
},
};
const humanizeBaseMetric = (metric) => {
switch (metric) {
case BaseMetric.ATTACK_VECTOR:
return 'Attack Vector';
case BaseMetric.ATTACK_COMPLEXITY:
return 'Attack Complexity';
case BaseMetric.PRIVILEGES_REQUIRED:
return 'Privileges Required';
case BaseMetric.USER_INTERACTION:
return 'User Interaction';
case BaseMetric.SCOPE:
return 'Scope';
case BaseMetric.CONFIDENTIALITY:
return 'Confidentiality';
case BaseMetric.INTEGRITY:
return 'Integrity';
case BaseMetric.AVAILABILITY:
return 'Availability';
default:
return 'Unknown';
}
};
// eslint-disable-next-line complexity
const humanizeBaseMetricValue = (value, metric) => {
switch (value) {
case 'A':
return 'Adjacent';
case 'C':
return 'Changed';
case 'H':
return 'High';
case 'L':
return metric === BaseMetric.ATTACK_VECTOR ? 'Local' : 'Low';
case 'N':
return metric === BaseMetric.ATTACK_VECTOR ? 'Network' : 'None';
case 'P':
return 'Physical';
case 'R':
return 'Required';
case 'U':
return 'Unchanged';
default:
return 'Unknown';
}
};
/**
* Stringify a score into a qualitative severity rating string
* @param score
*/
const humanizeScore = (score) => score <= 0
? 'None'
: score <= 3.9
? 'Low'
: score <= 6.9
? 'Medium'
: score <= 8.9
? 'High'
: 'Critical';
const VERSION_REGEX = /^CVSS:(\d(?:\.\d)?)(.*)?$/;
const parseVersion = (cvssStr) => {
const versionRegexRes = VERSION_REGEX.exec(cvssStr);
return versionRegexRes && versionRegexRes[1];
};
const parseVector = (cvssStr) => {
const versionRegexRes = VERSION_REGEX.exec(cvssStr);
return versionRegexRes && versionRegexRes[2] && versionRegexRes[2].substr(1);
};
const parseMetrics = (vectorStr) => (vectorStr ? vectorStr.split('/') : []).map((metric) => {
if (!metric) {
return { key: '', value: '' };
}
const parts = metric.split(':');
return { key: parts[0], value: parts[1] };
});
const parseMetricsAsMap = (cvssStr) => parseMetrics(parseVector(cvssStr) || '').reduce((res, metric) => {
if (res.has(metric.key)) {
throw new Error(`Duplicated metric: "${metric.key}:${metric.value || ''}"`);
}
return res.set(metric.key, metric.value);
}, new Map());
/**
* Validates a CVSS vector string according to expected metrics and mandatory requirements.
* @param {string} vectorString - The CVSS vector string to validate.
* @returns {{ valid: boolean, error?: string, selectedMetrics?: Record<string, string> }}
* Returns an object indicating whether the vector is valid, with an error message if invalid.
*/
const validateVectorV4 = (vectorString) => {
let metrics = vectorString ? vectorString.split("/") : [];
const prefix = metrics[0];
if (prefix !== "CVSS:4.0") {
return { valid: false, error: "Invalid vector prefix" };
}
metrics.shift();
let expectedIndex = 0;
const toSelect = {};
const expectedEntries = Object.entries(expectedMetricOrder);
const mandatoryMetrics = ['AV', 'AC', 'AT', 'PR', 'UI', 'VC', 'VI', 'VA', 'SC', 'SI', 'SA'];
for (const metric of metrics) {
const [key, value] = metric.split(":");
const expectedEntry = expectedEntries.find(entry => entry[0] === key);
if (key in toSelect) {
return { valid: false, error: `Invalid vector, repeated metric: ${key}` };
}
while (expectedIndex < expectedEntries.length && expectedEntries[expectedIndex][0] !== key) {
expectedIndex++;
}
if (expectedIndex >= expectedEntries.length) {
return { valid: false, error: `Invalid vector, metric out of sequence: ${key}` };
}
if (!expectedEntry) {
return { valid: false, error: `Invalid vector, unexpected metric: ${key}` };
}
if (!expectedEntry[1].includes(value)) {
return { valid: false, error: `Invalid vector, for key ${key}, value ${value} is not in [${expectedEntry[1]}]` };
}
toSelect[key] = value;
}
const missingMandatoryMetrics = mandatoryMetrics.filter(metric => !(metric in toSelect));
if (missingMandatoryMetrics.length > 0) {
return { valid: false, error: `Invalid vector, missing mandatory metrics: ${missingMandatoryMetrics.join(', ')}` };
}
return { valid: true, selectedMetrics: toSelect };
};
const validateVersion = (versionStr) => {
if (!versionStr) {
throw new Error('Invalid CVSS string. Example: CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L');
}
if (versionStr !== '3.0' && versionStr !== '3.1' && versionStr !== '4.0') {
throw new Error(`Unsupported CVSS version: ${versionStr}. Only 3.0 and 3.1 are supported`);
}
};
const validateVector = (vectorStr) => {
if (!vectorStr || vectorStr.includes('//')) {
throw new Error('Invalid CVSS string. Example: CVSS:3.0/AV:A/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:L');
}
};
const checkUnknownMetrics = (metricsMap, knownMetrics) => {
const allKnownMetrics = knownMetrics || [
...baseMetrics,
...temporalMetrics,
...environmentalMetrics
];
[...metricsMap.keys()].forEach((userMetric) => {
if (!allKnownMetrics.includes(userMetric)) {
throw new Error(`Unknown CVSS metric "${userMetric}". Allowed metrics: ${allKnownMetrics.join(', ')}`);
}
});
};
const checkMandatoryMetrics = (metricsMap, metrics = baseMetrics) => {
metrics.forEach((metric) => {
if (!metricsMap.has(metric)) {
// eslint-disable-next-line max-len
throw new Error(`Missing mandatory CVSS metric ${metrics} (${humanizeBaseMetric(metric)})`);
}
});
};
const checkMetricsValues = (metricsMap, metrics, metricsValues) => {
metrics.forEach((metric) => {
const userValue = metricsMap.get(metric);
if (!userValue) {
return;
}
if (!metricsValues[metric].includes(userValue)) {
const allowedValuesHumanized = metricsValues[metric]
.map((value) => `${value} (${humanizeBaseMetricValue(value, metric)})`)
.join(', ');
throw new Error(`Invalid value for CVSS metric ${metric} (${humanizeBaseMetric(metric)})${userValue ? `: ${userValue}` : ''}. Allowed values: ${allowedValuesHumanized}`);
}
});
};
/**
* Validate that the given string is a valid cvss vector
* @param cvssStr
*/
const validate = (cvssStr) => {
if (!cvssStr || !cvssStr.startsWith('CVSS:')) {
throw new Error('CVSS vector must start with "CVSS:"');
}
if (cvssStr.startsWith('CVSS:4')) {
validateVectorV4(cvssStr);
}
const allKnownMetrics = [
...baseMetrics,
...temporalMetrics,
...environmentalMetrics
];
const allKnownMetricsValues = {
...baseMetricValues,
...temporalMetricValues,
...environmentalMetricValues
};
const versionStr = parseVersion(cvssStr);
validateVersion(versionStr);
const vectorStr = parseVector(cvssStr);
validateVector(vectorStr);
const metricsMap = parseMetricsAsMap(cvssStr);
checkMandatoryMetrics(metricsMap);
checkUnknownMetrics(metricsMap, allKnownMetrics);
checkMetricsValues(metricsMap, allKnownMetrics, allKnownMetricsValues);
const isTemporal = [...metricsMap.keys()].some((metric) => temporalMetrics.includes(metric));
const isEnvironmental = [...metricsMap.keys()].some((metric) => environmentalMetrics.includes(metric));
return {
metricsMap,
isTemporal,
isEnvironmental,
versionStr
};
};
const detectCvssVersion = (cvssString) => {
const versionPrefix = cvssString.split('/')[0];
if (versionPrefix.startsWith('CVSS:4.0'))
return '4.0';
if (versionPrefix.startsWith('CVSS:3.'))
return '3.1';
throw new Error(`Unsupported CVSS version: ${versionPrefix}`);
};
// https://www.first.org/cvss/v3.1/specification-document#7-4-Metric-Values
const baseMetricValueScores = {
[BaseMetric.ATTACK_VECTOR]: { N: 0.85, A: 0.62, L: 0.55, P: 0.2 },
[BaseMetric.ATTACK_COMPLEXITY]: { L: 0.77, H: 0.44 },
[BaseMetric.PRIVILEGES_REQUIRED]: null,
[BaseMetric.USER_INTERACTION]: { N: 0.85, R: 0.62 },
[BaseMetric.SCOPE]: { U: 0, C: 0 },
[BaseMetric.CONFIDENTIALITY]: { N: 0, L: 0.22, H: 0.56 },
[BaseMetric.INTEGRITY]: { N: 0, L: 0.22, H: 0.56 },
[BaseMetric.AVAILABILITY]: { N: 0, L: 0.22, H: 0.56 }
};
const temporalMetricValueScores = {
[TemporalMetric.EXPLOIT_CODE_MATURITY]: {
X: 1,
U: 0.91,
F: 0.97,
P: 0.94,
H: 1
},
[TemporalMetric.REMEDIATION_LEVEL]: { X: 1, O: 0.95, T: 0.96, W: 0.97, U: 1 },
[TemporalMetric.REPORT_CONFIDENCE]: { X: 1, U: 0.92, R: 0.96, C: 1 }
};
const environmentalMetricValueScores = {
[EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: {
M: 1,
L: 0.5,
H: 1.5,
X: 1
},
[EnvironmentalMetric.INTEGRITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 },
[EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: {
M: 1,
L: 0.5,
H: 1.5,
X: 1
},
[EnvironmentalMetric.MODIFIED_ATTACK_VECTOR]: baseMetricValueScores[BaseMetric.ATTACK_VECTOR],
[EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY]: baseMetricValueScores[BaseMetric.ATTACK_COMPLEXITY],
[EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED]: null,
[EnvironmentalMetric.MODIFIED_USER_INTERACTION]: baseMetricValueScores[BaseMetric.USER_INTERACTION],
[EnvironmentalMetric.MODIFIED_SCOPE]: baseMetricValueScores[BaseMetric.SCOPE],
[EnvironmentalMetric.MODIFIED_CONFIDENTIALITY]: baseMetricValueScores[BaseMetric.CONFIDENTIALITY],
[EnvironmentalMetric.MODIFIED_INTEGRITY]: baseMetricValueScores[BaseMetric.INTEGRITY],
[EnvironmentalMetric.MODIFIED_AVAILABILITY]: baseMetricValueScores[BaseMetric.AVAILABILITY]
};
const getPrivilegesRequiredNumericValue = (value, scopeValue) => {
if (scopeValue !== 'U' && scopeValue !== 'C' && scopeValue !== 'X') {
throw new Error(`Unknown Scope value: ${scopeValue}`);
}
switch (value) {
case 'N':
return 0.85;
case 'L':
return scopeValue === 'U' ? 0.62 : 0.68;
case 'H':
return scopeValue === 'U' ? 0.27 : 0.5;
default:
throw new Error(`Unknown PrivilegesRequired value: ${value}`);
}
};
const getMetricValue = (metric, metricsMap) => {
if (!metricsMap.has(metric)) {
console.log("metricsMap", metricsMap);
throw new Error(`Missing metric: ${metric}`);
}
return metricsMap.get(metric);
};
const getMetricNumericValue = (metric, metricsMap) => {
const value = getMetricValue(metric || TemporalMetric || EnvironmentalMetric, metricsMap);
if (metric === BaseMetric.PRIVILEGES_REQUIRED) {
return getPrivilegesRequiredNumericValue(value, getMetricValue(BaseMetric.SCOPE, metricsMap));
}
if (metric === EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED) {
return getPrivilegesRequiredNumericValue(value, getMetricValue(EnvironmentalMetric.MODIFIED_SCOPE, metricsMap));
}
const score = {
...baseMetricValueScores,
...temporalMetricValueScores,
...environmentalMetricValueScores
}[metric];
if (!score) {
throw new Error(`Internal error. Missing metric score: ${metric}`);
}
return score[value];
};
// ISS = 1 - [ (1 - Confidentiality) × (1 - Integrity) × (1 - Availability) ]
const calculateIss = (metricsMap) => {
const confidentiality = getMetricNumericValue(BaseMetric.CONFIDENTIALITY, metricsMap);
const integrity = getMetricNumericValue(BaseMetric.INTEGRITY, metricsMap);
const availability = getMetricNumericValue(BaseMetric.AVAILABILITY, metricsMap);
return 1 - (1 - confidentiality) * (1 - integrity) * (1 - availability);
};
// https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations
// MISS = Minimum ( 1 - [ (1 - ConfidentialityRequirement × ModifiedConfidentiality) × (1 - IntegrityRequirement × ModifiedIntegrity) × (1 - AvailabilityRequirement × ModifiedAvailability) ], 0.915)
const calculateMiss = (metricsMap) => {
const rConfidentiality = getMetricNumericValue(EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, metricsMap);
const mConfidentiality = getMetricNumericValue(EnvironmentalMetric.MODIFIED_CONFIDENTIALITY, metricsMap);
const rIntegrity = getMetricNumericValue(EnvironmentalMetric.INTEGRITY_REQUIREMENT, metricsMap);
const mIntegrity = getMetricNumericValue(EnvironmentalMetric.MODIFIED_INTEGRITY, metricsMap);
const rAvailability = getMetricNumericValue(EnvironmentalMetric.AVAILABILITY_REQUIREMENT, metricsMap);
const mAvailability = getMetricNumericValue(EnvironmentalMetric.MODIFIED_AVAILABILITY, metricsMap);
return Math.min(1 -
(1 - rConfidentiality * mConfidentiality) *
(1 - rIntegrity * mIntegrity) *
(1 - rAvailability * mAvailability), 0.915);
};
// https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations
// Impact =
// If Scope is Unchanged 6.42 × ISS
// If Scope is Changed 7.52 × (ISS - 0.029) - 3.25 × (ISS - 0.02)^15
const calculateImpact = (metricsMap, iss) => metricsMap.get(BaseMetric.SCOPE) === 'U'
? 6.42 * iss
: 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15);
// https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations
// ModifiedImpact =
// If ModifiedScope is Unchanged 6.42 × MISS
// If ModifiedScope is Changed 7.52 × (MISS - 0.029) - 3.25 × (MISS × 0.9731 - 0.02)^13
// ModifiedExploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction
// Note : Math.pow is 15 in 3.0 but 13 in 3.1
const calculateModifiedImpact = (metricsMap, miss, versionStr) => metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U'
? 6.42 * miss
: 7.52 * (miss - 0.029) -
3.25 *
Math.pow(miss * (versionStr === '3.0' ? 1 : 0.9731) - 0.02, versionStr === '3.0' ? 15 : 13);
// https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations
// Exploitability = 8.22 × AttackVector × AttackComplexity × PrivilegesRequired × UserInteraction
const calculateExploitability = (metricsMap) => 8.22 *
getMetricNumericValue(BaseMetric.ATTACK_VECTOR, metricsMap) *
getMetricNumericValue(BaseMetric.ATTACK_COMPLEXITY, metricsMap) *
getMetricNumericValue(BaseMetric.PRIVILEGES_REQUIRED, metricsMap) *
getMetricNumericValue(BaseMetric.USER_INTERACTION, metricsMap);
// https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations
// Exploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction
const calculateModifiedExploitability = (metricsMap) => 8.22 *
getMetricNumericValue(EnvironmentalMetric.MODIFIED_ATTACK_VECTOR, metricsMap) *
getMetricNumericValue(EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY, metricsMap) *
getMetricNumericValue(EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED, metricsMap) *
getMetricNumericValue(EnvironmentalMetric.MODIFIED_USER_INTERACTION, metricsMap);
// https://www.first.org/cvss/v3.1/specification-document#Appendix-A---Floating-Point-Rounding
const roundUp = (input) => {
const intInput = Math.round(input * 100000);
return intInput % 10000 === 0
? intInput / 100000
: (Math.floor(intInput / 10000) + 1) / 10;
};
const modifiedMetricsMap = {
MAV: BaseMetric.ATTACK_VECTOR,
MAC: BaseMetric.ATTACK_COMPLEXITY,
MPR: BaseMetric.PRIVILEGES_REQUIRED,
MUI: BaseMetric.USER_INTERACTION,
MS: BaseMetric.SCOPE,
MC: BaseMetric.CONFIDENTIALITY,
MI: BaseMetric.INTEGRITY,
MA: BaseMetric.AVAILABILITY
};
// When Modified Temporal metric value is 'Not Defined' ('X'), which is the default value,
// then Base metric value should be used.
const populateTemporalMetricDefaults = (metricsMap) => {
[...temporalMetrics].forEach((metric) => {
if (!metricsMap.has(metric)) {
metricsMap.set(metric, 'X');
}
});
return metricsMap;
};
const populateEnvironmentalMetricDefaults = (metricsMap) => {
[...environmentalMetrics].forEach((metric) => {
if (!metricsMap.has(metric)) {
metricsMap.set(metric, 'X');
}
if (metricsMap.get(metric) === 'X') {
metricsMap.set(metric, metricsMap.has(modifiedMetricsMap[metric])
? metricsMap.get(modifiedMetricsMap[metric])
: 'X');
}
});
return metricsMap;
};
// https://www.first.org/cvss/v3.1/specification-document#7-1-Base-Metrics-Equations
// If Impact <= 0 => 0; else
// If Scope is Unchanged => Roundup (Minimum [(Impact + Exploitability), 10])
// If Scope is Changed => Roundup (Minimum [1.08 × (Impact + Exploitability), 10])
const calculateBaseResult = (cvssString) => {
const { metricsMap } = validate(cvssString);
const iss = calculateIss(metricsMap);
const impact = calculateImpact(metricsMap, iss);
const exploitability = calculateExploitability(metricsMap);
const scopeUnchanged = metricsMap.get(BaseMetric.SCOPE) === 'U';
const score = impact <= 0
? 0
: scopeUnchanged
? roundUp(Math.min(impact + exploitability, 10))
: roundUp(Math.min(1.08 * (impact + exploitability), 10));
return {
score,
metricsMap,
impact: impact <= 0 ? 0 : roundUp(impact),
exploitability: impact <= 0 ? 0 : roundUp(exploitability)
};
};
const calculateBaseScore = (cvssString) => {
const version = detectCvssVersion(cvssString);
if (version === '4.0') {
return calculateBaseScoreV4(cvssString);
}
else if (version === '3.1') {
const { score } = calculateBaseResult(cvssString);
return score;
}
else {
throw new Error(`Unsupported CVSS version: ${version}`);
}
};
// https://www.first.org/cvss/v3.1/specification-document#7-3-Environmental-Metrics-Equations
// If ModifiedImpact <= 0 => 0; else
// If ModifiedScope is Unchanged => Roundup (Roundup [Minimum ([ModifiedImpact + ModifiedExploitability], 10)] × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
// If ModifiedScope is Changed => Roundup (Roundup [Minimum (1.08 × [ModifiedImpact + ModifiedExploitability], 10)] × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
const calculateEnvironmentalResult = (cvssString) => {
const validationResult = validate(cvssString);
const { versionStr } = validationResult;
let { metricsMap } = validationResult;
metricsMap = populateTemporalMetricDefaults(metricsMap);
metricsMap = populateEnvironmentalMetricDefaults(metricsMap);
const miss = calculateMiss(metricsMap);
const impact = calculateModifiedImpact(metricsMap, miss, versionStr);
const exploitability = calculateModifiedExploitability(metricsMap);
const scopeUnchanged = metricsMap.get(EnvironmentalMetric.MODIFIED_SCOPE) === 'U';
const score = impact <= 0
? 0
: scopeUnchanged
? roundUp(roundUp(Math.min(impact + exploitability, 10)) *
getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) *
getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap) *
getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap))
: roundUp(roundUp(Math.min(1.08 * (impact + exploitability), 10)) *
getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) *
getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap) *
getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap));
return {
score,
metricsMap,
impact: impact <= 0 ? 0 : roundUp(impact),
exploitability: impact <= 0 ? 0 : roundUp(exploitability)
};
};
const calculateEnvironmentalScore = (cvssString) => {
const { score } = calculateEnvironmentalResult(cvssString);
return score;
};
// https://www.first.org/cvss/v3.1/specification-document#7-2-Temporal-Metrics-Equations
// Roundup (BaseScore × ExploitCodeMaturity × RemediationLevel × ReportConfidence)
const calculateTemporalResult = (cvssString) => {
let { metricsMap } = validate(cvssString);
// populate temp metrics if not provided
metricsMap = populateTemporalMetricDefaults(metricsMap);
const { score, impact, exploitability } = calculateBaseResult(cvssString);
const tempScore = roundUp(score *
getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap) *
getMetricNumericValue(TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) *
getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap));
return {
score: tempScore,
metricsMap,
impact,
exploitability
};
};
const calculateTemporalScore = (cvssString) => {
const { score } = calculateTemporalResult(cvssString);
return score;
};
// V4 Logic goes here ///
/**
* Parses a CVSS vector string and adds default "X" values for unspecified metrics.
* @param {string} vector - The CVSS vector string to parse.
* @returns {Record<string, string>} - An object representing the parsed and completed metrics.
*/
const parseVectorV4 = (vector) => {
const metrics = vector.split('/');
const cvssSelected = {};
// Remove CVSS:4.0 prefix
metrics.shift();
metrics.forEach(metric => {
const [key, value] = metric.split(':');
cvssSelected[key] = value;
});
if (!("E" in cvssSelected)) {
cvssSelected["E"] = "X";
}
if (!("CR" in cvssSelected)) {
cvssSelected["CR"] = "X";
}
if (!("IR" in cvssSelected)) {
cvssSelected["IR"] = "X";
}
if (!("AR" in cvssSelected)) {
cvssSelected["AR"] = "X";
}
return cvssSelected;
};
/**
* Calculates a qualitative severity score based on the numeric CVSS score.
* @param {number} score - The numeric CVSS score.
* @returns {string} - The qualitative score: "None", "Low", "Medium", "High", or "Critical".
*/
const calculateQualScore = (score) => {
if (score === 0)
return "None";
if (score < 4.0)
return "Low";
if (score < 7.0)
return "Medium";
if (score < 9.0)
return "High";
return "Critical";
};
/**
* Calculates the base CVSS score for version 4.0 using a vector string.
* @param {string} vectorString - The CVSS vector string to calculate the score for.
* @returns {number} - The calculated base CVSS score.
* @throws {Error} - Throws an error if the vector is invalid.
*/
const calculateBaseScoreV4 = (vectorString) => {
const vvres = validateVectorV4(vectorString);
if (!vvres.valid) {
throw new Error(vvres.error);
}
const cvssSelected = parseVectorV4(vectorString);
const macrov = macroVector(cvssSelected);
let score = cvss_score(cvssSelected, cvssLookup_globalV4, maxSeverityV4, macrov);
return score;
};
const cvss_score = (cvssSelected, lookup, maxSeverityData, macroVectorResult) => {
// The following defines the index of each metric's values.
// It is used when looking for the highest vector part of the
// combinations produced by the MacroVector respective highest vectors.
let AV_levels = { "N": 0.0, "A": 0.1, "L": 0.2, "P": 0.3 };
let PR_levels = { "N": 0.0, "L": 0.1, "H": 0.2 };
let UI_levels = { "N": 0.0, "P": 0.1, "A": 0.2 };
let AC_levels = { 'L': 0.0, 'H': 0.1 };
let AT_levels = { 'N': 0.0, 'P': 0.1 };
let VC_levels = { 'H': 0.0, 'L': 0.1, 'N': 0.2 };
let VI_levels = { 'H': 0.0, 'L': 0.1, 'N': 0.2 };
let VA_levels = { 'H': 0.0, 'L': 0.1, 'N': 0.2 };
let SC_levels = { 'H': 0.1, 'L': 0.2, 'N': 0.3 };
let SI_levels = { 'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3 };
let SA_levels = { 'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3 };
let CR_levels = { 'H': 0.0, 'M': 0.1, 'L': 0.2 };
let IR_levels = { 'H': 0.0, 'M': 0.1, 'L': 0.2 };
let AR_levels = { 'H': 0.0, 'M': 0.1, 'L': 0.2 };
//let E_levels = { 'U': 0.2, 'P': 0.1, 'A': 0 }
// Exception for no impact on system (shortcut)
if (["VC", "VI", "VA", "SC", "SI", "SA"].every((metric) => m(cvssSelected, metric) == "N")) {
return 0.0;
}
let value = lookup[macroVectorResult];
// 1. For each of the EQs:
// a. The maximal scoring difference is determined as the difference
// between the current MacroVector and the lower MacroVector.
// i. If there is no lower MacroVector the available distance is
// set to NaN and then ignored in the further calculations.
let eq1 = parseInt(macroVectorResult[0]);
let eq2 = parseInt(macroVectorResult[1]);
let eq3 = parseInt(macroVectorResult[2]);
let eq4 = parseInt(macroVectorResult[3]);
let eq5 = parseInt(macroVectorResult[4]);
let eq6 = parseInt(macroVectorResult[5]);
// compute next lower macro, it can also not exist
let eq1_next_lower_macro = `${eq1 + 1}${eq2}${eq3}${eq4}${eq5}${eq6}`;
let eq2_next_lower_macro = `${eq1}${eq2 + 1}${eq3}${eq4}${eq5}${eq6}`;
let eq3eq6_next_lower_macro = "";
let eq3eq6_next_lower_macro_left = "";
let eq3eq6_next_lower_macro_right = "";
// eq3 and eq6 are related
if (eq3 == 1 && eq6 == 1) {
// 11 --> 21
eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6 + 1}`;
}
else if (eq3 == 0 && eq6 == 1) {
// 01 --> 11
eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6}`;
}
else if (eq3 == 1 && eq6 == 0) {
// 10 --> 11
eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3}${eq4}${eq5}${eq6 + 1}`;
}
else if (eq3 == 0 && eq6 == 0) {
// 00 --> 01
// 00 --> 10
eq3eq6_next_lower_macro_left = `${eq1}${eq2}${eq3}${eq4}${eq5}${eq6 + 1}`;
eq3eq6_next_lower_macro_right = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6}`;
}
else {
// 21 --> 32 (do not exist)
eq3eq6_next_lower_macro = `${eq1}${eq2}${eq3 + 1}${eq4}${eq5}${eq6 + 1}`;
}
let eq4_next_lower_macro = `${eq1}${eq2}${eq3}${eq4 + 1}${eq5}${eq6}`;
let eq5_next_lower_macro = `${eq1}${eq2}${eq3}${eq4}${eq5 + 1}${eq6}`;
// get their score, if the next lower macro score do not exist the result is NaN
let score_eq1_next_lower_macro = lookup[eq1_next_lower_macro];
let score_eq2_next_lower_macro = lookup[eq2_next_lower_macro];
let score_eq3eq6_next_lower_macro = NaN;
if (eq3 == 0 && eq6 == 0) {
// multiple path take the one with higher score
let score_eq3eq6_next_lower_macro_left = lookup[eq3eq6_next_lower_macro_left];
let score_eq3eq6_next_lower_macro_right = lookup[eq3eq6_next_lower_macro_right];
score_eq3eq6_next_lower_macro_left = lookup[eq3eq6_next_lower_macro_left];
if (score_eq3eq6_next_lower_macro_left > score_eq3eq6_next_lower_macro_right) {
score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_left;
}
else {
score_eq3eq6_next_lower_macro = score_eq3eq6_next_lower_macro_right;
}
}
else {
score_eq3eq6_next_lower_macro = lookup[eq3eq6_next_lower_macro];
}
let score_eq4_next_lower_macro = lookup[eq4_next_lower_macro];
let score_eq5_next_lower_macro = lookup[eq5_next_lower_macro];
// b. The severity distance of the to-be scored vector from a
// highest severity vector in the same MacroVector is determined.
let eq1_maxes = getEQMaxes(macroVectorResult, 1);
let eq2_maxes = getEQMaxes(macroVectorResult, 2);
// @ts-ignore
let eq3_eq6_maxes = getEQMaxes(macroVectorResult, 3)[macroVectorResult[5]];
let eq4_maxes = getEQMaxes(macroVectorResult, 4);
let eq5_maxes = getEQMaxes(macroVectorResult, 5);
// compose them
// Compose them
const max_vectors = [];
for (let eq1_max of eq1_maxes) {
for (let eq2_max of eq2_maxes) {
for (let eq3_eq6_max of eq3_eq6_maxes) {
for (let eq4_max of eq4_maxes) {
for (let eq5_max of eq5_maxes) {
max_vectors.push(eq1_max + eq2_max + eq3_eq6_max + eq4_max + eq5_max);
}
}
}
}
}
//console.log(max_vectors)
// Find the max vector to use i.e. one in the combination of all the highests
// that is greater or equal (severity distance) than the to-be scored vector.
let severity_distance_AV = 0;
let severity_distance_PR = 0;
let severity_distance_UI = 0;
let severity_distance_AC = 0;
let severity_distance_AT = 0;
let severity_distance_VC = 0;
let severity_distance_VI = 0;
let severity_distance_VA = 0;
let severity_distance_SC = 0;
let severity_distance_SI = 0;
let severity_distance_SA = 0;
let severity_distance_CR = 0;
let severity_distance_IR = 0;
let severity_distance_AR = 0;
for (let i = 0; i < max_vectors.length; i++) {
let max_vector = max_vectors[i];
severity_distance_AV =
AV_levels[m(cvssSelected, "AV")] -
AV_levels[extractValueMetric("AV", max_vector)];
severity_distance_PR =
PR_levels[m(cvssSelected, "PR")] -
PR_levels[extractValueMetric("PR", max_vector)];
severity_distance_UI =
UI_levels[m(cvssSelected, "UI")] -
UI_levels[extractValueMetric("UI", max_vector)];
severity_distance_AC =
AC_levels[m(cvssSelected, "AC")] -
AC_levels[extractValueMetric("AC", max_vector)];
severity_distance_AT =
AT_levels[m(cvssSelected, "AT")] -
AT_levels[extractValueMetric("AT", max_vector)];
severity_distance_VC =
VC_levels[m(cvssSelected, "VC")] -
VC_levels[extractValueMetric("VC", max_vector)];
severity_distance_VI =
VI_levels[m(cvssSelected, "VI")] -
VI_levels[extractValueMetric("VI", max_vector)];
severity_distance_VA =
VA_levels[m(cvssSelected, "VA")] -
VA_levels[extractValueMetric("VA", max_vector)];
severity_distance_SC =
SC_levels[m(cvssSelected, "SC")] -
SC_levels[extractValueMetric("SC", max_vector)];
severity_distance_SI =
SI_levels[m(cvssSelected, "SI")] -
SI_levels[extractValueMetric("SI", max_vector)];
severity_distance_SA =
SA_levels[m(cvssSelected, "SA")] -
SA_levels[extractValueMetric("SA", max_vector)];
severity_distance_CR =
CR_levels[m(cvssSelected, "CR")] -
CR_levels[extractValueMetric("CR", max_vector)];
severity_distance_IR =
IR_levels[m(cvssSelected, "IR")] -
IR_levels[extractValueMetric("IR", max_vector)];
severity_distance_AR =
AR_levels[m(cvssSelected, "AR")] -
AR_levels[extractValueMetric("AR", max_vector)];
// if any is less than zero this is not the right max
if ([severity_distance_AV, severity_distance_PR, severity_distance_UI, severity_distance_AC, severity_distance_AT, severity_distance_VC, severity_distance_VI, severity_distance_VA, severity_distance_SC, severity_distance_SI, severity_distance_SA, severity_distance_CR, severity_distance_IR, severity_distance_AR].some((met) => met < 0)) {
continue;
}
// if multiple maxes exist to reach it it is enough the first one
break;
}
let current_severity_distance_eq1 = severity_distance_AV + severity_distance_PR + severity_distance_UI;
let current_severity_distance_eq2 = severity_distance_AC + severity_distance_AT;
let current_severity_distance_eq3eq6 = severity_distance_VC + severity_distance_VI + severity_distance_VA + severity_distance_CR + severity_distance_IR + severity_distance_AR;
let current_severity_distance_eq4 = severity_distance_SC + severity_distance_SI + severity_distance_SA;
// let current_severity_distance_eq5 = 0
let step = 0.1;
// if the next lower macro score do not exist the result is Nan
// Rename to maximal scoring difference (aka MSD)
let available_distance_eq1 = value - score_eq1_next_lower_macro;
let available_distance_eq2 = value - score_eq2_next_lower_macro;
let available_distance_eq3eq6 = value - score_eq3eq6_next_lower_macro;
let available_distance_eq4 = value - score_eq4_next_lower_macro;
let available_distance_eq5 = value - score_eq5_next_lower_macro;
let percent_to_next_eq1_severity = 0;
let percent_to_next_eq2_severity = 0;
let percent_to_next_eq3eq6_severity = 0;
let percent_to_next_eq4_severity = 0;
let percent_to_next_eq5_severity = 0;
// some of them do not exist, we will find them by retrieving the score. If score null then do not exist
let n_existing_lower = 0;
let normalized_severity_eq1 = 0;
let normalized_severity_eq2 = 0;
let normalized_severity_eq3eq6 = 0;
let normalized_severity_eq4 = 0;
let normalized_severity_eq5 = 0;
// multiply by step because distance is pure
let maxSeverity_eq1 = maxSeverityData["eq1"][eq1] * step;
let maxSeverity_eq2 = maxSeverityData["eq2"][eq2] * step;
let maxSeverity_eq3eq6 = maxSeverityData["eq3eq6"][eq3][eq6] * step;
let maxSeverity_eq4 = maxSeverityData["eq4"][eq4] * step;
// c. The proportion of the distance is determined by dividing
// the severity distance of the to-be-scored vector by the depth
// of the MacroVector.
// d. The maximal scoring difference is multiplied by the proportion of
// distance.
if (!isNaN(available_distance_eq1)) {
n_existing_lower = n_existing_lower + 1;
percent_to_next_eq1_severity = (current_severity_distance_eq1) / maxSeverity_eq1;
normalized_severity_eq1 = available_distance_eq1 * percent_to_next_eq1_severity;
}
if (!isNaN(available_distance_eq2)) {
n_existing_lower = n_existing_lower + 1;
percent_to_next_eq2_severity = (current_severity_distance_eq2) / maxSeverity_eq2;
normalized_severity_eq2 = available_distance_eq2 * percent_to_next_eq2_severity;
}
if (!isNaN(available_distance_eq3eq6)) {
n_existing_lower = n_existing_lower + 1;
percent_to_next_eq3eq6_severity = (current_severity_distance_eq3eq6) / maxSeverity_eq3eq6;
normalized_severity_eq3eq6 = available_distance_eq3eq6 * percent_to_next_eq3eq6_severity;
}
if (!isNaN(available_distance_eq4)) {
n_existing_lower = n_existing_lower + 1;
percent_to_next_eq4_severity = (current_severity_distance_eq4) / maxSeverity_eq4;
normalized_severity_eq4 = available_distance_eq4 * percent_to_next_eq4_severity;
}
if (!isNaN(available_distance_eq5)) {
// for eq5 is always 0 the percentage
n_existing_lower = n_existing_lower + 1;
percent_to_next_eq5_severity = 0;
normalized_severity_eq5 = available_distance_eq5 * percent_to_next_eq5_severity;
}
// 2. The mean of the above computed proportional distances is computed.
let mean_distance = 0;
if (n_existing_lower == 0) {
mean_distance = 0;
}
else { // sometimes we need to go up but there is nothing there, or down but there is nothing there so it's a change of 0.
mean_distance = (normalized_severity_eq1 + normalized_severity_eq2 + normalized_severity_eq3eq6 + normalized_severity_eq4 + normalized_severity_eq5) / n_existing_lower;
}
// 3. The score of the vector is the score of the MacroVector
// (i.e. the score of the highest severity vector) minus the mean
// distance so computed. This score is rounded to one decimal place.
value -= mean_distance;
if (value < 0) {
value = 0.0;
}
if (value > 10) {
value = 10.0;
}
return Math.round(value * 10) / 10;
};
const getEQMaxes = (lookup, eq) => {
// @ts-ignore
return maxComposed[`eq${eq}`][lookup[eq - 1]];
};
const extractValueMetric = (metric, str) => {
// indexOf gives first index of the metric, we then need to go over its size
let extracted = str.slice(str.indexOf(metric) + metric.length + 1);
// remove what follow
let metric_val = "";
if (extracted.indexOf('/') > 0) {
metric_val = extracted.substring(0, extracted.indexOf('/'));
}
else {
// case where it is the last metric so no ending /
metric_val = extracted;
}
return metric_val;
};
const m = (cvssSelected, metric) => {
let selected = cvssSelected[metric];
// If E=X it will default to the worst case i.e. E=A
if (metric == "E" && selected == "X") {
return "A";
}
// If CR=X, IR=X or AR=X they will default to the worst case i.e. CR=H, IR=H and AR=H
if (metric == "CR" && selected == "X") {
return "H";
}
// IR:X is the same as IR:H
if (metric == "IR" && selected == "X") {
return "H";
}
// AR:X is the same as AR:H
if (metric == "AR" && selected == "X") {
return "H";
}
// All other environmental metrics just overwrite base score values,
// so if they’re not defined just use the base score value.
if (Object.keys(cvssSelected).includes("M" + metric)) {
let modified_selected = cvssSelected["M" + metric];
if (modified_selected != "X") {
return modified_selected;
}
}
return selected;
};
const macroVector = (cvssSelected) => {
// EQ1: 0-AV:N and PR:N and UI:N
// 1-(AV:N or PR:N or UI:N) and not (AV:N and PR:N and UI:N) and not AV:P
// 2-AV:P or not(AV:N or PR:N or UI:N)
let eq1 = 0;
let eq2 = 0;
let eq3 = 0;
let eq4 = 0;
let eq5 = 0;
let eq