Skip to content

Commit

Permalink
Reapply "Merge pull request #60 from RedHatProductSecurity/feature/ad…
Browse files Browse the repository at this point in the history
…d-testing"

This reverts commit bea2884.
  • Loading branch information
superbuggy committed Sep 7, 2024
1 parent bea2884 commit f7c34e7
Show file tree
Hide file tree
Showing 13 changed files with 584,917 additions and 54 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Run Tests
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install modules
run: yarn
- name: Run tests
run: yarn test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
node_modules/
.DS_Store
113 changes: 59 additions & 54 deletions cvss40.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ class Vector {
},
// Environmental (14 metrics)
ENVIRONMENTAL: {
"CR": ["X", "H", "M", "L"],
"IR": ["X", "H", "M", "L"],
"AR": ["X", "H", "M", "L"],
"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"],
Expand All @@ -88,12 +88,12 @@ class Vector {
},
// Supplemental (6 metrics)
SUPPLEMENTAL: {
"S": ["X", "N", "P"],
"S": ["X", "N", "P"],
"AU": ["X", "N", "Y"],
"R": ["X", "A", "U", "I"],
"V": ["X", "D", "C"],
"R": ["X", "A", "U", "I"],
"V": ["X", "D", "C"],
"RE": ["X", "L", "M", "H"],
"U": ["X", "Clear", "Green", "Amber", "Red"],
"U": ["X", "Clear", "Green", "Amber", "Red"],
}
};

Expand Down Expand Up @@ -381,39 +381,48 @@ class Vector {

// Check if the prefix is correct
if (metrics.shift() !== "CVSS:4.0") {
console.error("Error: invalid vector, missing CVSS v4.0 prefix from vector: " + vector);
return false;
throw new Error(`Invalid vector, missing \`CVSS:4.0 prefix\` from vector: \`${vector}\``);
}

const expectedMetrics = Object.entries(Vector.ALL_METRICS);
let mandatoryMetricIndex = 0;
const malformedMetric = metrics.find(metric => metric.split(':').length !== 2);

for (let metric of metrics) {
const [key, value] = metric.split(':');
if (malformedMetric) {
throw new Error(`Invalid vector, malformed substring \`${malformedMetric}\` in vector: \`${vector}\``);
}

// Check if there are too many metric values
if (!expectedMetrics[mandatoryMetricIndex]) {
console.error("Error: invalid vector, too many metric values");
return false;
}
const metricsLookup = metrics.reduce((lookup, metric) => {
const [metricType, metricValue] = metric.split(':');
lookup[metricType] = metricValue;
return lookup;
}, {});

// Find the current expected metric
while (expectedMetrics[mandatoryMetricIndex] && expectedMetrics[mandatoryMetricIndex][0] !== key) {
// Check for missing mandatory metrics
if (mandatoryMetricIndex < 11) {
console.error("Error: invalid vector, missing mandatory metrics");
return false;
}
mandatoryMetricIndex++;
}
const requiredMetrics = Object.keys(Vector.METRICS.BASE);

if (!requiredMetrics.every(metricType => metricType in metricsLookup)) {
throw new Error(`Invalid CVSS v4.0 vector: Missing required metrics in \`${vector}\``);
}

// Check if the value is valid for the given metric
if (!expectedMetrics[mandatoryMetricIndex][1].includes(value)) {
console.error(`Error: invalid vector, for key ${key}, value ${value} is not in ${expectedMetrics[mandatoryMetricIndex][1]}`);
return false;
if (metrics.length > Object.keys(metricsLookup).length) {
throw new Error(`Invalid CVSS v4.0 vector: Duplicated metric types in \`${vector}\``);
}

const definedMetrics = Vector.ALL_METRICS;

if (metrics.length > Object.keys(definedMetrics).length) {
// This was here before but probably is impossible to reach because of the previous checks for duplicated and undefined keys
throw new Error(`Invalid CVSS v4.0 vector: Unknown/excessive metric types in \`${vector}\``);
}

for (let [metricType, metricValue] of Object.entries(metricsLookup)) {

if (!metricType in Vector.ALL_METRICS) {
throw new Error(`Invalid CVSS v4.0 vector: Unknown metric \`${metricType}\` in \`${vector}\``);
}

mandatoryMetricIndex++;
// Check if the value is valid for the given metric type
if (!definedMetrics[metricType].includes(metricValue)) {
throw new Error(`Invalid CVSS v4.0 vector \`${vector}\`: For metricType \`${metricType}\`, value \`${metricValue}\` is invalid. Valid, defined metric values for \`${metricType}\` are: ${definedMetrics[metricType]}.`);
}
}

return true;
Expand All @@ -437,13 +446,10 @@ class Vector {
*/
updateMetricsFromVectorString(vector) {
if (!vector) {
throw new Error("The vector string cannot be null, undefined, or empty.");
throw new Error(`The vector string cannot be null, undefined, or empty in ${vector}`);
}

// Validate the CVSS v4.0 string vector
if (!this.validateStringVector(vector)) {
throw new Error("Invalid CVSS v4.0 vector: " + vector);
}
this.validateStringVector(vector);

let metrics = vector.split('/');

Expand Down Expand Up @@ -780,21 +786,21 @@ class CVSS40 {
// It is used when looking for the highest vector part of the
// combinations produced by the MacroVector respective highest
static METRIC_LEVELS = {
"AV": {"N": 0.0, "A": 0.1, "L": 0.2, "P": 0.3},
"PR": {"N": 0.0, "L": 0.1, "H": 0.2},
"UI": {"N": 0.0, "P": 0.1, "A": 0.2},
"AC": {'L': 0.0, 'H': 0.1},
"AT": {'N': 0.0, 'P': 0.1},
"VC": {'H': 0.0, 'L': 0.1, 'N': 0.2},
"VI": {'H': 0.0, 'L': 0.1, 'N': 0.2},
"VA": {'H': 0.0, 'L': 0.1, 'N': 0.2},
"SC": {'H': 0.1, 'L': 0.2, 'N': 0.3},
"SI": {'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3},
"SA": {'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3},
"CR": {'H': 0.0, 'M': 0.1, 'L': 0.2},
"IR": {'H': 0.0, 'M': 0.1, 'L': 0.2},
"AR": {'H': 0.0, 'M': 0.1, 'L': 0.2},
"E": {'U': 0.2, 'P': 0.1, 'A': 0}
"AV": { "N": 0.0, "A": 0.1, "L": 0.2, "P": 0.3 },
"PR": { "N": 0.0, "L": 0.1, "H": 0.2 },
"UI": { "N": 0.0, "P": 0.1, "A": 0.2 },
"AC": { 'L': 0.0, 'H': 0.1 },
"AT": { 'N': 0.0, 'P': 0.1 },
"VC": { 'H': 0.0, 'L': 0.1, 'N': 0.2 },
"VI": { 'H': 0.0, 'L': 0.1, 'N': 0.2 },
"VA": { 'H': 0.0, 'L': 0.1, 'N': 0.2 },
"SC": { 'H': 0.1, 'L': 0.2, 'N': 0.3 },
"SI": { 'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3 },
"SA": { 'S': 0.0, 'H': 0.1, 'L': 0.2, 'N': 0.3 },
"CR": { 'H': 0.0, 'M': 0.1, 'L': 0.2 },
"IR": { 'H': 0.0, 'M': 0.1, 'L': 0.2 },
"AR": { 'H': 0.0, 'M': 0.1, 'L': 0.2 },
"E": { 'U': 0.2, 'P': 0.1, 'A': 0 }
};

static MAX_COMPOSED = {
Expand Down Expand Up @@ -880,7 +886,7 @@ class CVSS40 {
// If the input is a string, create a new Vector object from the string
this.vector = new Vector(input);
} else {
throw new Error("Invalid input type for CVSS40 constructor. Expected a string or a Vector object.");
throw new Error(`Invalid input type for CVSSv4.0 constructor. Expected a string or a Vector object in ${vector}`);
}

// Calculate the score
Expand Down Expand Up @@ -1208,4 +1214,3 @@ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
window.CVSS40 = CVSS40;
window.Vector = Vector;
}

29 changes: 29 additions & 0 deletions cvss40.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { CVSS40 } = require('./cvss40');
const fs = require('fs');
const path = require('path');

const testDataPaths = fs.readdirSync('./data').map(fileName => ({
path: path.join('./data', fileName),
name: fileName,
}));

describe('CVSS 4.0', () => {
const testData = testDataPaths.reduce((data, file) => {
const fileData = fs.readFileSync(file.path, 'utf8');
const lineEntries = fileData.split('\n');
const scoredVectors = lineEntries.map(vectorScore => {
const vectorScorePair = vectorScore.trim().split(' - ');
return (vectorScorePair.length !== 2) ? null : { vector: vectorScorePair[0], score: parseFloat(vectorScorePair[1]) };
}).filter(Boolean);
data[file.name] = scoredVectors;
return data;
}, {});

Object.entries(testData).forEach(([fileName, vectorScores]) => {
it(`should calculate scores in ${fileName} correctly`, () => {
vectorScores.forEach(({ vector, score }) => {
expect(new CVSS40(vector).score).toBe(score);
});
});
});
});
Loading

0 comments on commit f7c34e7

Please sign in to comment.