UNPKG

@neuralegion/cvss

Version:

The Common Vulnerability Scoring System ([CVSS](https://www.first.org/cvss/)) [score](https://www.first.org/cvss/specification-document#1-2-Scoring) calculator and validator library written in [TypeScript](https://www.typescriptlang.org/).

1,168 lines (1,156 loc) 55.5 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.cvss = {})); })(this, (function (exports) { 'use strict'; exports.BaseMetric = void 0; (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"; })(exports.BaseMetric || (exports.BaseMetric = {})); exports.TemporalMetric = void 0; (function (TemporalMetric) { TemporalMetric["EXPLOIT_CODE_MATURITY"] = "E"; TemporalMetric["REMEDIATION_LEVEL"] = "RL"; TemporalMetric["REPORT_CONFIDENCE"] = "RC"; })(exports.TemporalMetric || (exports.TemporalMetric = {})); exports.EnvironmentalMetric = void 0; (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"; })(exports.EnvironmentalMetric || (exports.EnvironmentalMetric = {})); const baseMetrics$1 = [ exports.BaseMetric.ATTACK_VECTOR, exports.BaseMetric.ATTACK_COMPLEXITY, exports.BaseMetric.PRIVILEGES_REQUIRED, exports.BaseMetric.USER_INTERACTION, exports.BaseMetric.SCOPE, exports.BaseMetric.CONFIDENTIALITY, exports.BaseMetric.INTEGRITY, exports.BaseMetric.AVAILABILITY ]; const temporalMetrics$1 = [ exports.TemporalMetric.EXPLOIT_CODE_MATURITY, exports.TemporalMetric.REMEDIATION_LEVEL, exports.TemporalMetric.REPORT_CONFIDENCE ]; const environmentalMetrics$1 = [ exports.EnvironmentalMetric.AVAILABILITY_REQUIREMENT, exports.EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, exports.EnvironmentalMetric.INTEGRITY_REQUIREMENT, exports.EnvironmentalMetric.MODIFIED_ATTACK_VECTOR, exports.EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY, exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED, exports.EnvironmentalMetric.MODIFIED_USER_INTERACTION, exports.EnvironmentalMetric.MODIFIED_SCOPE, exports.EnvironmentalMetric.MODIFIED_CONFIDENTIALITY, exports.EnvironmentalMetric.MODIFIED_INTEGRITY, exports.EnvironmentalMetric.MODIFIED_AVAILABILITY ]; const baseMetricValues$1 = { [exports.BaseMetric.ATTACK_VECTOR]: ['N', 'A', 'L', 'P'], [exports.BaseMetric.ATTACK_COMPLEXITY]: ['L', 'H'], [exports.BaseMetric.PRIVILEGES_REQUIRED]: ['N', 'L', 'H'], [exports.BaseMetric.USER_INTERACTION]: ['N', 'R'], [exports.BaseMetric.SCOPE]: ['U', 'C'], [exports.BaseMetric.CONFIDENTIALITY]: ['N', 'L', 'H'], [exports.BaseMetric.INTEGRITY]: ['N', 'L', 'H'], [exports.BaseMetric.AVAILABILITY]: ['N', 'L', 'H'] }; const temporalMetricValues$1 = { [exports.TemporalMetric.EXPLOIT_CODE_MATURITY]: ['X', 'H', 'F', 'P', 'U'], [exports.TemporalMetric.REMEDIATION_LEVEL]: ['X', 'U', 'W', 'T', 'O'], [exports.TemporalMetric.REPORT_CONFIDENCE]: ['X', 'C', 'R', 'U'] }; const environmentalMetricValues$1 = { [exports.EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: ['X', 'H', 'M', 'L'], [exports.EnvironmentalMetric.INTEGRITY_REQUIREMENT]: ['X', 'H', 'M', 'L'], [exports.EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: ['X', 'H', 'M', 'L'], [exports.EnvironmentalMetric.MODIFIED_ATTACK_VECTOR]: ['X', 'N', 'A', 'L', 'P'], [exports.EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY]: ['X', 'L', 'H'], [exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED]: ['X', 'N', 'L', 'H'], [exports.EnvironmentalMetric.MODIFIED_USER_INTERACTION]: ['X', 'N', 'R'], [exports.EnvironmentalMetric.MODIFIED_SCOPE]: ['X', 'U', 'C'], [exports.EnvironmentalMetric.MODIFIED_CONFIDENTIALITY]: ['X', 'N', 'L', 'H'], [exports.EnvironmentalMetric.MODIFIED_INTEGRITY]: ['X', 'N', 'L', 'H'], [exports.EnvironmentalMetric.MODIFIED_AVAILABILITY]: ['X', 'N', 'L', 'H'] }; 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].substring(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$1 = (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()); // https://www.first.org/cvss/v3.1/specification-document#7-4-Metric-Values const baseMetricValueScores$1 = { [exports.BaseMetric.ATTACK_VECTOR]: { N: 0.85, A: 0.62, L: 0.55, P: 0.2 }, [exports.BaseMetric.ATTACK_COMPLEXITY]: { L: 0.77, H: 0.44 }, [exports.BaseMetric.PRIVILEGES_REQUIRED]: null, [exports.BaseMetric.USER_INTERACTION]: { N: 0.85, R: 0.62 }, [exports.BaseMetric.SCOPE]: { U: 0, C: 0 }, [exports.BaseMetric.CONFIDENTIALITY]: { N: 0, L: 0.22, H: 0.56 }, [exports.BaseMetric.INTEGRITY]: { N: 0, L: 0.22, H: 0.56 }, [exports.BaseMetric.AVAILABILITY]: { N: 0, L: 0.22, H: 0.56 } }; const temporalMetricValueScores$1 = { [exports.TemporalMetric.EXPLOIT_CODE_MATURITY]: { X: 1, U: 0.91, F: 0.97, P: 0.94, H: 1 }, [exports.TemporalMetric.REMEDIATION_LEVEL]: { X: 1, O: 0.95, T: 0.96, W: 0.97, U: 1 }, [exports.TemporalMetric.REPORT_CONFIDENCE]: { X: 1, U: 0.92, R: 0.96, C: 1 } }; const environmentalMetricValueScores$1 = { [exports.EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 }, [exports.EnvironmentalMetric.INTEGRITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 }, [exports.EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: { M: 1, L: 0.5, H: 1.5, X: 1 }, [exports.EnvironmentalMetric.MODIFIED_ATTACK_VECTOR]: baseMetricValueScores$1[exports.BaseMetric.ATTACK_VECTOR], [exports.EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY]: baseMetricValueScores$1[exports.BaseMetric.ATTACK_COMPLEXITY], [exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED]: null, [exports.EnvironmentalMetric.MODIFIED_USER_INTERACTION]: baseMetricValueScores$1[exports.BaseMetric.USER_INTERACTION], [exports.EnvironmentalMetric.MODIFIED_SCOPE]: baseMetricValueScores$1[exports.BaseMetric.SCOPE], [exports.EnvironmentalMetric.MODIFIED_CONFIDENTIALITY]: baseMetricValueScores$1[exports.BaseMetric.CONFIDENTIALITY], [exports.EnvironmentalMetric.MODIFIED_INTEGRITY]: baseMetricValueScores$1[exports.BaseMetric.INTEGRITY], [exports.EnvironmentalMetric.MODIFIED_AVAILABILITY]: baseMetricValueScores$1[exports.BaseMetric.AVAILABILITY] }; const getPrivilegesRequiredNumericValue = (value, scopeValue) => { if (scopeValue !== 'U' && scopeValue !== 'C') { 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)) { throw new Error(`Missing metric: ${metric}`); } return metricsMap.get(metric); }; const getMetricNumericValue$1 = (metric, metricsMap) => { const value = getMetricValue(metric || exports.TemporalMetric || exports.EnvironmentalMetric, metricsMap); if (metric === exports.BaseMetric.PRIVILEGES_REQUIRED) { return getPrivilegesRequiredNumericValue(value, getMetricValue(exports.BaseMetric.SCOPE, metricsMap)); } if (metric === exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED) { return getPrivilegesRequiredNumericValue(value, getMetricValue(exports.EnvironmentalMetric.MODIFIED_SCOPE, metricsMap)); } const score = { ...baseMetricValueScores$1, ...temporalMetricValueScores$1, ...environmentalMetricValueScores$1 }[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$1(exports.BaseMetric.CONFIDENTIALITY, metricsMap); const integrity = getMetricNumericValue$1(exports.BaseMetric.INTEGRITY, metricsMap); const availability = getMetricNumericValue$1(exports.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$1(exports.EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, metricsMap); const mConfidentiality = getMetricNumericValue$1(exports.EnvironmentalMetric.MODIFIED_CONFIDENTIALITY, metricsMap); const rIntegrity = getMetricNumericValue$1(exports.EnvironmentalMetric.INTEGRITY_REQUIREMENT, metricsMap); const mIntegrity = getMetricNumericValue$1(exports.EnvironmentalMetric.MODIFIED_INTEGRITY, metricsMap); const rAvailability = getMetricNumericValue$1(exports.EnvironmentalMetric.AVAILABILITY_REQUIREMENT, metricsMap); const mAvailability = getMetricNumericValue$1(exports.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(exports.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-0/specification-document#8-1-Base // ModifiedImpact = // If ModifiedScope is Unchanged 6.42 × MISS // If ModifiedScope is Changed 7.52 × (MISS - 0.029) - 3.25 × (MISS - 0.02)^15 // ModifiedExploitability = 8.22 × ModifiedAttackVector × ModifiedAttackComplexity × ModifiedPrivilegesRequired × ModifiedUserInteraction const calculateModifiedImpactV3 = (metricsMap, miss) => metricsMap.get(exports.EnvironmentalMetric.MODIFIED_SCOPE) === 'U' ? 6.42 * miss : 7.52 * (miss - 0.029) - 3.25 * Math.pow(miss * 1 - 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 const calculateModifiedImpactV31 = (metricsMap, miss) => metricsMap.get(exports.EnvironmentalMetric.MODIFIED_SCOPE) === 'U' ? 6.42 * miss : 7.52 * (miss - 0.029) - 3.25 * Math.pow(miss * 0.9731 - 0.02, 13); const calculateModifiedImpact = (metricsMap, miss, versionStr) => versionStr === '3.0' ? calculateModifiedImpactV3(metricsMap, miss) : calculateModifiedImpactV31(metricsMap, miss); // 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$1(exports.BaseMetric.ATTACK_VECTOR, metricsMap) * getMetricNumericValue$1(exports.BaseMetric.ATTACK_COMPLEXITY, metricsMap) * getMetricNumericValue$1(exports.BaseMetric.PRIVILEGES_REQUIRED, metricsMap) * getMetricNumericValue$1(exports.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$1(exports.EnvironmentalMetric.MODIFIED_ATTACK_VECTOR, metricsMap) * getMetricNumericValue$1(exports.EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY, metricsMap) * getMetricNumericValue$1(exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED, metricsMap) * getMetricNumericValue$1(exports.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 round$1 = (input) => { const intInput = Math.round(input * 100000); return Math.round(intInput / 10000) / 10; }; const modifiedMetricsMap = { MAV: exports.BaseMetric.ATTACK_VECTOR, MAC: exports.BaseMetric.ATTACK_COMPLEXITY, MPR: exports.BaseMetric.PRIVILEGES_REQUIRED, MUI: exports.BaseMetric.USER_INTERACTION, MS: exports.BaseMetric.SCOPE, MC: exports.BaseMetric.CONFIDENTIALITY, MI: exports.BaseMetric.INTEGRITY, MA: exports.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$1 = (metricsMap) => { [...temporalMetrics$1].forEach((metric) => { if (!metricsMap.has(metric)) { metricsMap.set(metric, 'X'); } }); return metricsMap; }; const populateEnvironmentalMetricDefaults$1 = (metricsMap) => { [...environmentalMetrics$1].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; }; class CvssV3Calculator { calculate(cvssString) { const metricsMap = parseMetricsAsMap$1(cvssString); const versionStr = parseVersion(cvssString); const baseResult = this.calculateBaseScore(metricsMap, versionStr); const temporalResult = this.calculateTemporalScore(baseResult); const environmentalResult = this.calculateEnvironmentalScore(metricsMap, versionStr); return { ...baseResult, ...temporalResult, ...environmentalResult }; } /** * Calculate the base score for a CVSS v3.x string */ calculateBaseScore(metricsMap, versionStr) { const iss = calculateIss(metricsMap); const impact = calculateImpact(metricsMap, iss); const exploitability = calculateExploitability(metricsMap); const scopeUnchanged = metricsMap.get(exports.BaseMetric.SCOPE) === 'U'; const baseScore = impact <= 0 ? 0 : scopeUnchanged ? roundUp(Math.min(impact + exploitability, 10)) : roundUp(Math.min(1.08 * (impact + exploitability), 10)); return { version: versionStr, baseScore, baseImpact: impact <= 0 ? 0 : round$1(impact), baseExploitability: exploitability <= 0 ? 0 : round$1(exploitability), metrics: metricsMap }; } /** * Calculate the temporal score for a CVSS v3.x string */ calculateTemporalScore(baseResult) { // populate temp metrics const metricsMap = populateTemporalMetricDefaults$1(baseResult.metrics); const temporalScore = roundUp(baseResult.baseScore * getMetricNumericValue$1(exports.TemporalMetric.REPORT_CONFIDENCE, metricsMap) * getMetricNumericValue$1(exports.TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue$1(exports.TemporalMetric.REMEDIATION_LEVEL, metricsMap)); return { temporalScore }; } /** * Calculate the environmental score for a CVSS v3.x string */ calculateEnvironmentalScore(metricsMap, versionStr) { metricsMap = populateTemporalMetricDefaults$1(metricsMap); metricsMap = populateEnvironmentalMetricDefaults$1(metricsMap); const miss = calculateMiss(metricsMap); const impact = calculateModifiedImpact(metricsMap, miss, versionStr); const exploitability = calculateModifiedExploitability(metricsMap); const scopeUnchanged = metricsMap.get(exports.EnvironmentalMetric.MODIFIED_SCOPE) === 'U'; const environmentalScore = impact <= 0 ? 0 : scopeUnchanged ? roundUp(roundUp(Math.min(impact + exploitability, 10)) * getMetricNumericValue$1(exports.TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue$1(exports.TemporalMetric.REMEDIATION_LEVEL, metricsMap) * getMetricNumericValue$1(exports.TemporalMetric.REPORT_CONFIDENCE, metricsMap)) : roundUp(roundUp(Math.min(1.08 * (impact + exploitability), 10)) * getMetricNumericValue$1(exports.TemporalMetric.EXPLOIT_CODE_MATURITY, metricsMap) * getMetricNumericValue$1(exports.TemporalMetric.REMEDIATION_LEVEL, metricsMap) * getMetricNumericValue$1(exports.TemporalMetric.REPORT_CONFIDENCE, metricsMap)); return { environmentalScore, modifiedImpact: impact <= 0 ? 0 : round$1(impact), modifiedExploitability: exploitability <= 0 ? 0 : round$1(exploitability) }; } } var BaseMetric; (function (BaseMetric) { BaseMetric["ACCESS_VECTOR"] = "AV"; BaseMetric["ACCESS_COMPLEXITY"] = "AC"; BaseMetric["AUTHENTICATION"] = "Au"; BaseMetric["CONFIDENTIALITY_IMPACT"] = "C"; BaseMetric["INTEGRITY_IMPACT"] = "I"; BaseMetric["AVAILABILITY_IMPACT"] = "A"; })(BaseMetric || (BaseMetric = {})); var TemporalMetric; (function (TemporalMetric) { TemporalMetric["EXPLOITABILITY"] = "E"; TemporalMetric["REMEDIATION_LEVEL"] = "RL"; TemporalMetric["REPORT_CONFIDENCE"] = "RC"; })(TemporalMetric || (TemporalMetric = {})); var EnvironmentalMetric; (function (EnvironmentalMetric) { EnvironmentalMetric["COLLATERAL_DAMAGE_POTENTIAL"] = "CDP"; EnvironmentalMetric["TARGET_DISTRIBUTION"] = "TD"; EnvironmentalMetric["CONFIDENTIALITY_REQUIREMENT"] = "CR"; EnvironmentalMetric["INTEGRITY_REQUIREMENT"] = "IR"; EnvironmentalMetric["AVAILABILITY_REQUIREMENT"] = "AR"; })(EnvironmentalMetric || (EnvironmentalMetric = {})); const baseMetrics = [ BaseMetric.ACCESS_VECTOR, BaseMetric.ACCESS_COMPLEXITY, BaseMetric.AUTHENTICATION, BaseMetric.CONFIDENTIALITY_IMPACT, BaseMetric.INTEGRITY_IMPACT, BaseMetric.AVAILABILITY_IMPACT ]; const temporalMetrics = [ TemporalMetric.EXPLOITABILITY, TemporalMetric.REMEDIATION_LEVEL, TemporalMetric.REPORT_CONFIDENCE ]; const environmentalMetrics = [ EnvironmentalMetric.COLLATERAL_DAMAGE_POTENTIAL, EnvironmentalMetric.TARGET_DISTRIBUTION, EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, EnvironmentalMetric.INTEGRITY_REQUIREMENT, EnvironmentalMetric.AVAILABILITY_REQUIREMENT ]; const baseMetricValues = { [BaseMetric.ACCESS_VECTOR]: ['L', 'A', 'N'], [BaseMetric.ACCESS_COMPLEXITY]: ['H', 'M', 'L'], [BaseMetric.AUTHENTICATION]: ['M', 'S', 'N'], [BaseMetric.CONFIDENTIALITY_IMPACT]: ['N', 'P', 'C'], [BaseMetric.INTEGRITY_IMPACT]: ['N', 'P', 'C'], [BaseMetric.AVAILABILITY_IMPACT]: ['N', 'P', 'C'] }; const temporalMetricValues = { [TemporalMetric.EXPLOITABILITY]: ['U', 'POC', 'F', 'H', 'ND'], [TemporalMetric.REMEDIATION_LEVEL]: ['OF', 'TF', 'W', 'U', 'ND'], [TemporalMetric.REPORT_CONFIDENCE]: ['UC', 'UR', 'C', 'ND'] }; const environmentalMetricValues = { [EnvironmentalMetric.COLLATERAL_DAMAGE_POTENTIAL]: [ 'N', 'L', 'LM', 'MH', 'H', 'ND' ], [EnvironmentalMetric.TARGET_DISTRIBUTION]: ['N', 'L', 'M', 'H', 'ND'], [EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: ['L', 'M', 'H', 'ND'], [EnvironmentalMetric.INTEGRITY_REQUIREMENT]: ['L', 'M', 'H', 'ND'], [EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: ['L', 'M', 'H', 'ND'] }; const baseMetricValueScores = { [BaseMetric.ACCESS_VECTOR]: { L: 0.395, A: 0.646, N: 1.0 }, [BaseMetric.ACCESS_COMPLEXITY]: { H: 0.35, M: 0.61, L: 0.71 }, [BaseMetric.AUTHENTICATION]: { M: 0.45, S: 0.56, N: 0.704 }, [BaseMetric.CONFIDENTIALITY_IMPACT]: { N: 0, P: 0.275, C: 0.66 }, [BaseMetric.INTEGRITY_IMPACT]: { N: 0, P: 0.275, C: 0.66 }, [BaseMetric.AVAILABILITY_IMPACT]: { N: 0, P: 0.275, C: 0.66 } }; const temporalMetricValueScores = { [TemporalMetric.EXPLOITABILITY]: { ND: 1.0, U: 0.85, POC: 0.9, F: 0.95, H: 1.0 }, [TemporalMetric.REMEDIATION_LEVEL]: { ND: 1.0, OF: 0.87, TF: 0.9, W: 0.95, U: 1.0 }, [TemporalMetric.REPORT_CONFIDENCE]: { ND: 1.0, UC: 0.9, UR: 0.95, C: 1.0 } }; const environmentalMetricValueScores = { [EnvironmentalMetric.COLLATERAL_DAMAGE_POTENTIAL]: { ND: 0, N: 0, L: 0.1, LM: 0.3, MH: 0.4, H: 0.5 }, [EnvironmentalMetric.TARGET_DISTRIBUTION]: { ND: 1.0, N: 0, L: 0.25, M: 0.75, H: 1.0 }, [EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT]: { ND: 1.0, L: 0.5, M: 1.0, H: 1.51 }, [EnvironmentalMetric.INTEGRITY_REQUIREMENT]: { ND: 1.0, L: 0.5, M: 1.0, H: 1.51 }, [EnvironmentalMetric.AVAILABILITY_REQUIREMENT]: { ND: 1.0, L: 0.5, M: 1.0, H: 1.51 } }; const getMetricNumericValue = (metric, metricsMap) => { if (!metricsMap.has(metric)) { throw new Error(`Missing metric: ${metric}`); } const score = { ...baseMetricValueScores, ...temporalMetricValueScores, ...environmentalMetricValueScores }[metric]; if (!score) { throw new Error(`Internal error. Missing metric score: ${metric}`); } return score[metricsMap.get(metric)]; }; const round = (input) => Math.round(input * 10) / 10; const populateTemporalMetricDefaults = (metricsMap) => { [...temporalMetrics].forEach((metric) => { if (!metricsMap.has(metric)) { metricsMap.set(metric, 'ND'); } }); return metricsMap; }; const populateEnvironmentalMetricDefaults = (metricsMap) => { [...environmentalMetrics].forEach((metric) => { if (!metricsMap.has(metric)) { metricsMap.set(metric, 'ND'); } }); return metricsMap; }; class CvssV2Calculator { calculate(cvssString) { const metricsMap = parseMetricsAsMap$1(cvssString); const baseResult = this.calculateBaseScore(metricsMap); const temporalResult = this.calculateTemporalScore(baseResult, metricsMap); const environmentalResult = this.calculateEnvironmentalScore(baseResult, metricsMap); return { ...baseResult, ...temporalResult, ...environmentalResult, version: '2.0', metrics: metricsMap }; } calculateBaseScore(metricsMap) { const impact = this.calculateImpact(metricsMap); const exploitability = this.calculateExploitability(metricsMap); const fImpact = impact === 0 ? 0 : 1.176; const baseScore = (0.6 * impact + 0.4 * exploitability - 1.5) * fImpact; return { baseScore: round(baseScore), baseImpact: round(impact), baseExploitability: round(exploitability) }; } calculateImpact(metricsMap) { const c = getMetricNumericValue(BaseMetric.CONFIDENTIALITY_IMPACT, metricsMap); const i = getMetricNumericValue(BaseMetric.INTEGRITY_IMPACT, metricsMap); const a = getMetricNumericValue(BaseMetric.AVAILABILITY_IMPACT, metricsMap); return 10.41 * (1 - (1 - c) * (1 - i) * (1 - a)); } calculateExploitability(metricsMap) { const av = getMetricNumericValue(BaseMetric.ACCESS_VECTOR, metricsMap); const ac = getMetricNumericValue(BaseMetric.ACCESS_COMPLEXITY, metricsMap); const au = getMetricNumericValue(BaseMetric.AUTHENTICATION, metricsMap); return 20 * av * ac * au; } calculateTemporalScore(baseResult, metricsMap) { populateTemporalMetricDefaults(metricsMap); const e = getMetricNumericValue(TemporalMetric.EXPLOITABILITY, metricsMap); const rl = getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap); const rc = getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap); const temporalScore = round(baseResult.baseScore * e * rl * rc); return { temporalScore }; } calculateEnvironmentalScore(baseResult, metricsMap) { populateEnvironmentalMetricDefaults(metricsMap); const cdp = getMetricNumericValue(EnvironmentalMetric.COLLATERAL_DAMAGE_POTENTIAL, metricsMap); const td = getMetricNumericValue(EnvironmentalMetric.TARGET_DISTRIBUTION, metricsMap); // Adjusted Impact const c = getMetricNumericValue(BaseMetric.CONFIDENTIALITY_IMPACT, metricsMap); const i = getMetricNumericValue(BaseMetric.INTEGRITY_IMPACT, metricsMap); const a = getMetricNumericValue(BaseMetric.AVAILABILITY_IMPACT, metricsMap); const cr = getMetricNumericValue(EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT, metricsMap); const ir = getMetricNumericValue(EnvironmentalMetric.INTEGRITY_REQUIREMENT, metricsMap); const ar = getMetricNumericValue(EnvironmentalMetric.AVAILABILITY_REQUIREMENT, metricsMap); const adjustedImpact = Math.min(10, 10.41 * (1 - (1 - c * cr) * (1 - i * ir) * (1 - a * ar))); // Adjusted Base const fImpact = adjustedImpact === 0 ? 0 : 1.176; const adjustedBase = round((0.6 * adjustedImpact + 0.4 * baseResult.baseExploitability - 1.5) * fImpact); // Adjusted Temporal const e = getMetricNumericValue(TemporalMetric.EXPLOITABILITY, metricsMap); const rl = getMetricNumericValue(TemporalMetric.REMEDIATION_LEVEL, metricsMap); const rc = getMetricNumericValue(TemporalMetric.REPORT_CONFIDENCE, metricsMap); const adjustedTemporal = round(adjustedBase * e * rl * rc); const environmentalScore = round((adjustedTemporal + (10 - adjustedTemporal) * cdp) * td); return { environmentalScore }; } } const createCvssCalculator = (version) => { switch (version) { case '2.0': return new CvssV2Calculator(); case '3.0': case '3.1': return new CvssV3Calculator(); default: throw new Error(`Unsupported CVSS version: ${version}`); } }; const validateVector = (vectorStr) => { if (!vectorStr || vectorStr.includes('//')) { throw new Error('Invalid CVSS string'); } }; const checkUnknownMetrics = (metricsMap, knownMetrics) => { [...metricsMap.keys()].forEach((userMetric) => { if (!knownMetrics.includes(userMetric)) { throw new Error(`Unknown CVSS metric "${userMetric}". Allowed metrics: ${knownMetrics.join(', ')}`); } }); }; const checkMandatoryMetrics = (metricsMap, metrics, humanizer) => { metrics.forEach((metric) => { if (!metricsMap.has(metric)) { const metricName = humanizer ? humanizer.humanizeMetric(metric) : metric; throw new Error(`Missing mandatory CVSS metric ${metricName}`); } }); }; const checkMetricsValues = (metricsMap, metrics, metricsValues, humanizer) => { metrics.forEach((metric) => { const userValue = metricsMap.get(metric); if (!userValue) { return; } if (!metricsValues[metric].includes(userValue)) { let errorMsg = ''; if (humanizer) { const allowedValuesHumanized = metricsValues[metric] .map((value) => `${value} (${humanizer.humanizeMetricValue(value, metric)})`) .join(', '); errorMsg = `Invalid value for CVSS metric ${metric} (${humanizer.humanizeMetric(metric)})${userValue ? `: ${userValue}` : ''}. Allowed values: ${allowedValuesHumanized}`; } else { const allowedValues = metricsValues[metric].join(', '); errorMsg = `Invalid value for CVSS metric ${metric}: ${userValue}. Allowed values: ${allowedValues}`; } throw new Error(errorMsg); } }); }; const validateByKnownMaps = (cvssStr, validateVersion, metrics, knownMetricsValues, humanizer) => { if (!cvssStr || !cvssStr.startsWith('CVSS:')) { throw new Error('CVSS vector must start with "CVSS:"'); } const versionStr = parseVersion(cvssStr); validateVersion(versionStr); const vectorStr = parseVector(cvssStr); validateVector(vectorStr); const allMetrics = [ ...metrics.base, ...metrics.temporal, ...metrics.environmental ]; const metricsMap = parseMetricsAsMap$1(cvssStr); checkMandatoryMetrics(metricsMap, metrics.base, humanizer); checkUnknownMetrics(metricsMap, allMetrics); checkMetricsValues(metricsMap, allMetrics, knownMetricsValues, humanizer); const isTemporal = [...metricsMap.keys()].some((metric) => metrics.temporal.includes(metric)); const isEnvironmental = [...metricsMap.keys()].some((metric) => metrics.environmental.includes(metric)); return { metricsMap, isTemporal, isEnvironmental, versionStr }; }; // eslint-disable-next-line complexity const humanizeMetric$1 = (metric) => { switch (metric) { case BaseMetric.ACCESS_VECTOR: return 'Access Vector'; case BaseMetric.ACCESS_COMPLEXITY: return 'Access Complexity'; case BaseMetric.AUTHENTICATION: return 'Authentication'; case BaseMetric.CONFIDENTIALITY_IMPACT: return 'Confidentiality Impact'; case BaseMetric.INTEGRITY_IMPACT: return 'Integrity Impact'; case BaseMetric.AVAILABILITY_IMPACT: return 'Availability Impact'; case TemporalMetric.EXPLOITABILITY: return 'Exploitability'; case TemporalMetric.REMEDIATION_LEVEL: return 'Remediation Level'; case TemporalMetric.REPORT_CONFIDENCE: return 'Report Confidence'; case EnvironmentalMetric.COLLATERAL_DAMAGE_POTENTIAL: return 'Collateral Damage Potential'; case EnvironmentalMetric.TARGET_DISTRIBUTION: return 'Target Distribution'; case EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT: return 'Confidentiality Requirement'; case EnvironmentalMetric.INTEGRITY_REQUIREMENT: return 'Integrity Requirement'; case EnvironmentalMetric.AVAILABILITY_REQUIREMENT: return 'Availability Requirement'; default: return 'Unknown'; } }; // eslint-disable-next-line complexity const humanizeMetricValue$1 = (value, metric) => { switch (metric) { case BaseMetric.ACCESS_VECTOR: switch (value) { case 'L': return 'Local'; case 'A': return 'Adjacent Network'; case 'N': return 'Network'; } break; case BaseMetric.ACCESS_COMPLEXITY: switch (value) { case 'H': return 'High'; case 'M': return 'Medium'; case 'L': return 'Low'; } break; case BaseMetric.AUTHENTICATION: switch (value) { case 'M': return 'Multiple'; case 'S': return 'Single'; case 'N': return 'None'; } break; case BaseMetric.CONFIDENTIALITY_IMPACT: case BaseMetric.INTEGRITY_IMPACT: case BaseMetric.AVAILABILITY_IMPACT: switch (value) { case 'N': return 'None'; case 'P': return 'Partial'; case 'C': return 'Complete'; } break; case TemporalMetric.EXPLOITABILITY: switch (value) { case 'U': return 'Unproven that exploit exists'; case 'POC': return 'Proof of concept code'; case 'F': return 'Functional exploit exists'; case 'H': return 'High'; case 'ND': return 'Not Defined'; } break; case TemporalMetric.REMEDIATION_LEVEL: switch (value) { case 'OF': return 'Official fix'; case 'TF': return 'Temporary fix'; case 'W': return 'Workaround'; case 'U': return 'Unavailable'; case 'ND': return 'Not Defined'; } break; case TemporalMetric.REPORT_CONFIDENCE: switch (value) { case 'UC': return 'Unconfirmed'; case 'UR': return 'Uncorroborated'; case 'C': return 'Confirmed'; case 'ND': return 'Not Defined'; } break; case EnvironmentalMetric.COLLATERAL_DAMAGE_POTENTIAL: switch (value) { case 'N': return 'None'; case 'L': return 'Low (light loss)'; case 'LM': return 'Low-Medium'; case 'MH': return 'Medium-High'; case 'H': return 'High (catastrophic loss)'; case 'ND': return 'Not Defined'; } break; case EnvironmentalMetric.TARGET_DISTRIBUTION: switch (value) { case 'N': return 'None [0%]'; case 'L': return 'Low [0-25%]'; case 'M': return 'Medium [26-75%]'; case 'H': return 'High [76-100%]'; case 'ND': return 'Not Defined'; } break; case EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT: case EnvironmentalMetric.INTEGRITY_REQUIREMENT: case EnvironmentalMetric.AVAILABILITY_REQUIREMENT: switch (value) { case 'L': return 'Low'; case 'M': return 'Medium'; case 'H': return 'High'; case 'ND': return 'Not Defined'; } break; } return 'Unknown'; }; const validateVersion$2 = (versionStr) => { if (!versionStr) { throw new Error('Invalid CVSS string. Example: CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:C'); } if (versionStr !== '2.0') { throw new Error(`Unsupported CVSS version: ${versionStr}. Only 2.0 is supported by this validator.`); } }; const validate$2 = (cvssStr) => validateByKnownMaps(cvssStr, validateVersion$2, { base: baseMetrics, temporal: temporalMetrics, environmental: environmentalMetrics }, { ...baseMetricValues, ...temporalMetricValues, ...environmentalMetricValues }, { humanizeMetric: humanizeMetric$1, humanizeMetricValue: humanizeMetricValue$1 }); // eslint-disable-next-line complexity const humanizeMetric = (metric) => { switch (metric) { case exports.BaseMetric.ATTACK_VECTOR: return 'Attack Vector'; case exports.BaseMetric.ATTACK_COMPLEXITY: return 'Attack Complexity'; case exports.BaseMetric.PRIVILEGES_REQUIRED: return 'Privileges Required'; case exports.BaseMetric.USER_INTERACTION: return 'User Interaction'; case exports.BaseMetric.SCOPE: return 'Scope'; case exports.BaseMetric.CONFIDENTIALITY: return 'Confidentiality'; case exports.BaseMetric.INTEGRITY: return 'Integrity'; case exports.BaseMetric.AVAILABILITY: return 'Availability'; case exports.TemporalMetric.EXPLOIT_CODE_MATURITY: return 'Exploit Code Maturity'; case exports.TemporalMetric.REMEDIATION_LEVEL: return 'Remediation Level'; case exports.TemporalMetric.REPORT_CONFIDENCE: return 'Report Confidence'; case exports.EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT: return 'Confidentiality Requirement'; case exports.EnvironmentalMetric.INTEGRITY_REQUIREMENT: return 'Integrity Requirement'; case exports.EnvironmentalMetric.AVAILABILITY_REQUIREMENT: return 'Availability Requirement'; case exports.EnvironmentalMetric.MODIFIED_ATTACK_VECTOR: return 'Modified Attack Vector'; case exports.EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY: return 'Modified Attack Complexity'; case exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED: return 'Modified Privileges Required'; case exports.EnvironmentalMetric.MODIFIED_USER_INTERACTION: return 'Modified User Interaction'; case exports.EnvironmentalMetric.MODIFIED_SCOPE: return 'Modified Scope'; case exports.EnvironmentalMetric.MODIFIED_CONFIDENTIALITY: return 'Modified Confidentiality'; case exports.EnvironmentalMetric.MODIFIED_INTEGRITY: return 'Modified Integrity'; case exports.EnvironmentalMetric.MODIFIED_AVAILABILITY: return 'Modified Availability'; default: return 'Unknown'; } }; // eslint-disable-next-line complexity const humanizeMetricValue = (value, metric) => { switch (metric) { case exports.BaseMetric.ATTACK_VECTOR: switch (value) { case 'N': return 'Network'; case 'A': return 'Adjacent'; case 'L': return 'Local'; case 'P': return 'Physical'; } break; case exports.BaseMetric.ATTACK_COMPLEXITY: switch (value) { case 'L': return 'Low'; case 'H': return 'High'; } break; case exports.BaseMetric.PRIVILEGES_REQUIRED: switch (value) { case 'N': return 'None'; case 'L': return 'Low'; case 'H': return 'High'; } break; case exports.BaseMetric.USER_INTERACTION: switch (value) { case 'N': return 'None'; case 'R': return 'Required'; } break; case exports.BaseMetric.SCOPE: switch (value) { case 'U': return 'Unchanged'; case 'C': return 'Changed'; } break; case exports.BaseMetric.CONFIDENTIALITY: case exports.BaseMetric.INTEGRITY: case exports.BaseMetric.AVAILABILITY: switch (value) { case 'N': return 'None'; case 'L': return 'Low'; case 'H': return 'High'; } break; case exports.TemporalMetric.EXPLOIT_CODE_MATURITY: switch (value) { case 'X': return 'Not Defined'; case 'U': return 'Unproven'; case 'P': return 'Proof-of-Concept'; case 'F': return 'Functional'; case 'H': return 'High'; } break; case exports.TemporalMetric.REMEDIATION_LEVEL: switch (value) { case 'X': return 'Not Defined'; case 'O': return 'Official Fix'; case 'T': return 'Temporary Fix'; case 'W': return 'Workaround'; case 'U': return 'Unavailable'; } break; case exports.TemporalMetric.REPORT_CONFIDENCE: switch (value) { case 'X': return 'Not Defined'; case 'U': return 'Unknown'; case 'R': return 'Reasonable'; case 'C': return 'Confirmed'; } break; case exports.EnvironmentalMetric.CONFIDENTIALITY_REQUIREMENT: case exports.EnvironmentalMetric.INTEGRITY_REQUIREMENT: case exports.EnvironmentalMetric.AVAILABILITY_REQUIREMENT: switch (value) { case 'X': return 'Not Defined'; case 'L': return 'Low'; case 'M': return 'Medium'; case 'H': return 'High'; } break; case exports.EnvironmentalMetric.MODIFIED_ATTACK_VECTOR: switch (value) { case 'X': return 'Not Defined'; case 'N': return 'Network'; case 'A': return 'Adjacent Network'; case 'L': return 'Local'; case 'P': return 'Physical'; } break; case exports.EnvironmentalMetric.MODIFIED_ATTACK_COMPLEXITY: switch (value) { case 'X': return 'Not Defined'; case 'L': return 'Low'; case 'H': return 'High'; } break; case exports.EnvironmentalMetric.MODIFIED_PRIVILEGES_REQUIRED: switch (value) { case 'X': return 'Not Defined'; case 'N': return 'None'; case 'L': return 'Low'; case 'H': return 'High'; } break; case exports.EnvironmentalMetric.MODIFIED_USER_INTERACTION: switch (value) { case 'X': return 'Not Defined'; case 'N': return 'None'; case 'R': return 'Required'; } break; case exports.EnvironmentalMetric.MODIFIED_SCOPE: switch (value) { case 'X': return 'Not Defined'; case 'U': return 'Unchanged'; case 'C': return 'Changed'; } break; case exports.EnvironmentalMetric.MODIFIED_CONFIDENTIALITY: case exports.EnvironmentalMetric.MODIFIED_INTEGRITY: case exports.EnvironmentalMetric.MODIFIED_AVAILABILITY: switch (value) { case 'X': return 'Not Defined'; case 'N': return 'None'; case 'L': return 'Low'; case 'H': return 'High'; } break; } return 'Unknown'; }; // legacy, before introduction of Temporal and Environmental metrics support const humanizeBaseMetric = (metric) => humanizeMetric(metric); // legacy, before introduction of Temporal and Environmental metrics support const humanizeBaseMetricValue = (value, metric) => humanizeMetricValue(value, metric); const validateVersion$1 = (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') { throw new Error(`Unsupported CVSS version: ${versionStr}. Only 3.0 and 3.1 are supported by this validator.`); } }; const validate$1 = (cvssStr) => validateByKnownMaps(cvssStr, validateVersion$1, { base: baseMetrics$1, temporal: temporalMetrics$1, environmental: environmentalMetrics$1 }, { ...baseMetricValues$1, ...temporalMetricValues$1, ...environmentalMetricValues$1 }, { humanizeMetric, humanizeMetricValue }); const validate = (cvssString) => { if (!cvssString || !cvssString.startsWith('CVSS:')) { throw new Error('CVSS vector must start with "CVSS:"'); } const versionStr = parseVersion(cvssString); const validateString = versionStr === '2.0' ? validate$2 : validate$1; validateString(cvssString); }; function calculateCvss(cvssString) { const version = parseVersion(cvssString); if (!version) { throw new Error('Invalid CVSS string: