UNPKG

@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

132 lines (131 loc) 5.69 kB
import { baseMetricValues, baseMetrics, environmentalMetricValues, environmentalMetrics, expectedMetricOrder, temporalMetricValues, temporalMetrics } from './models'; import { humanizeBaseMetric, humanizeBaseMetricValue } from './humanizer'; import { parseMetricsAsMap, parseVector, parseVersion } from './parser'; /** * 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. */ export 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 }; }; export 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 */ export 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 }; };