diff --git a/.gitignore b/.gitignore
index 7f6b81740c..48aea601a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -129,3 +129,4 @@ dmypy.json
.pyre/
.DS_Store
+yarn.lock
\ No newline at end of file
diff --git a/community_images/common/templates/image_readme.j2 b/community_images/common/templates/image_readme.j2
index c1ba323c54..e42176ea68 100644
--- a/community_images/common/templates/image_readme.j2
+++ b/community_images/common/templates/image_readme.j2
@@ -152,8 +152,8 @@ Learn more about RapidFort's pioneering Software Attack Surface Management platf
[slack-link]: https://join.slack.com/t/rapidfortcommunity/shared_invite/zt-1g3wy28lv-DaeGexTQ5IjfpbmYW7Rm_Q
[rf-h-badge]: https://img.shields.io/static/v1?label=RapidFort&labelColor=333F48&message=hardened&color=50B4C4&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACcAAAAkCAYAAAAKNyObAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAHvSURBVHgB7ZjvTcMwEMUvEgNkhNuAjOAR2IAyQbsB2YAyQbsBYoKwQdjA3aAjHA514Xq1Hf9r6QeeFKVJ3tkv+cWOVYCAiKg124b82gZqe0+NNlsHJbLBxthg1o+RASetIEdTJxnBRvtUMCHgM6TIBtMZwY7SiQFfrhUsN+Ao/TJYR3WC5QY88/Nge6oXLBRwO+P/GcnNMZzZteBR0zQfogM0O4Q47Uz9TtSrUIHs71+paugw16Dn+qt5xJ/TD4viEcrE25tepaXPaHxP350GXtD10WwHQWjQxKhl7YUGRg/MuPaY9vxuzPFA+RpEW9rj0yCMbcCsmG9B+Xpk7YRo4RnjQEEttBiBtAefyI23BtoYpBrmRO6ZX0EZWo60c1yfaGBMOKRzdKVocYZO/NpuMss7E9cHitcc0gFS5Qig2LUUtCGkmmJwOsJJvLlokdWtfMFzAvLGctCOooYPtg2USoRQ7HwM2hXzIzuvKQenIxzHm4oWmZ9TKF1AnAR8sI2moB093nKcjoBvtnHFzoXQ8qeMDGcLtUW/i4NYtJ3jJhRcSnRYHMSg1Q5PD5cWHT4/ih0vIpDOf9QrhZtQLsWxlILT8AjXEol/iQRaiVTBX4pO57D6U0WJBFoFtyaLtuqLfwf19G62e7hFWbQKKuoLYovGDo9dW28AAAAASUVORK5CYII=
-[metrics-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/metrics.webp
-[cve-reduction-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/cve_reduction.webp
+[metrics-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/metrics.svg
+[cve-reduction-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/cve_reduction.svg
[source-image-repo-link]: {{ source_image_repo_link }}
[rf-dh-image-link]: https://hub.docker.com/r/{{- rf_docker_link }}
diff --git a/report_shots/cvss-parser/cvss.js b/report_shots/cvss-parser/cvss.js
new file mode 100644
index 0000000000..b130831738
--- /dev/null
+++ b/report_shots/cvss-parser/cvss.js
@@ -0,0 +1,33 @@
+const cvssParserV2 = require('./v2');
+const cvssParserV3 = require('./v3');
+function memoize(fn) {
+ const cache = new Map();
+ return function(...args) {
+ const key = JSON.stringify(args);
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
+ const result = fn.apply(this, args);
+ cache.set(key, result);
+ return result;
+ };
+}
+
+const parse = (vector, version) => {
+ let parser;
+ switch (version){
+ case 'V2':
+ parser = cvssParserV2;
+ break;
+ case 'V3':
+ parser = cvssParserV3;
+ break;
+ default:
+ parser = cvssParserV3;
+ break;
+ }
+ return parser.parse(vector)
+};
+module.exports = {
+ parse:memoize(parse)
+}
\ No newline at end of file
diff --git a/report_shots/cvss-parser/helpers.js b/report_shots/cvss-parser/helpers.js
new file mode 100644
index 0000000000..6ed6ee92c7
--- /dev/null
+++ b/report_shots/cvss-parser/helpers.js
@@ -0,0 +1,22 @@
+function roundUpApprox(num, precision) {
+ precision = Math.pow(10, precision);
+ return Math.ceil(num * precision) / precision;
+}
+function roundUpExact(num) {
+ const int_input = Math.round(num * 100000);
+ if (int_input % 10000 === 0) {
+ return int_input / 100000;
+ } else {
+ return (Math.floor(int_input / 10000) + 1) / 10;
+ }
+}
+function roundUp (value) {
+ let rounded = Math.round(value * 100000);
+ return rounded % 10000 === 0 ? rounded / 100000.0 : (Math.floor(rounded / 10000) + 1) / 10.0;
+}
+
+module.exports = {
+ roundUpApprox,
+ roundUpExact,
+ roundUp
+};
\ No newline at end of file
diff --git a/report_shots/cvss-parser/v2.js b/report_shots/cvss-parser/v2.js
new file mode 100644
index 0000000000..9cdbb58609
--- /dev/null
+++ b/report_shots/cvss-parser/v2.js
@@ -0,0 +1,240 @@
+const metricWeights = {
+ // Base metrics
+ AV: {
+ title: 'Access Vector',
+ values: {
+ N: { weight: 1.0, title: "Network", default:true},
+ A: { weight: 0.646, title: "Adjacent Network" },
+ L: { weight: 0.395, title: "Local" }
+ }
+ },
+ AC: {
+ title: 'Access Complexity',
+ values: {
+ H: { weight: 0.35, title: "High", default:true },
+ M: { weight: 0.61, title: "Medium" },
+ L: { weight: 0.71, title: "Low" }
+ }
+ },
+ Au: {
+ title: 'Authentication',
+ values: {
+ M: { weight: 0.45, title: "Multiple", default:true },
+ S: { weight: 0.56, title: "Single" },
+ N: { weight: 0.704, title: "None" }
+ }
+ },
+ C: {
+ title: 'Confidentiality Impact',
+ values: {
+ N: { weight: 0.0, title: "None" , default:true},
+ P: { weight: 0.275, title: "Partial" },
+ C: { weight: 0.660, title: "Complete" }
+ }
+ },
+ I: {
+ title: 'Integrity Impact',
+ values: {
+ N: { weight: 0.0, title: "None", default:true },
+ P: { weight: 0.275, title: "Partial" },
+ C: { weight: 0.660, title: "Complete" }
+ }
+ },
+ A: {
+ title: 'Availability Impact',
+ values: {
+ N: { weight: 0.0, title: "None", default:true },
+ P: { weight: 0.275, title: "Partial" },
+ C: { weight: 0.660, title: "Complete" }
+ }
+ },
+ // Temporal metrics
+ E: {
+ title: 'Exploitability',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined" , default:true},
+ U: { weight: 0.85, title: "Unproven" },
+ POC: { weight: 0.9, title: "Proof-of-Concept" },
+ F: { weight: 0.95, title: "Functional" },
+ H: { weight: 1.0, title: "High" }
+ }
+ },
+ RL: {
+ title: 'Remediation Level',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined", default:true },
+ OF: { weight: 0.87, title: "Official Fix" },
+ TF: { weight: 0.9, title: "Temporary Fix" },
+ W: { weight: 0.95, title: "Workaround" },
+ U: { weight: 1.0, title: "Unavailable" }
+ }
+ },
+ RC: {
+ title: 'Report Confidence',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined", default:true },
+ UC: { weight: 0.9, title: "Uncorroborated" },
+ UR: { weight: 0.95, title: "Uncorroborated" },
+ C: { weight: 1.0, title: "Confirmed" }
+ }
+ },
+ // Environmental metrics
+ CDP: {
+ title: 'Collateral Damage Potential',
+ values: {
+ ND: { weight: 0.0, title: "Not Defined", default:true },
+ N: { weight: 0.0, title: "None" },
+ L: { weight: 0.1, title: "Low" },
+ LM: { weight: 0.3, title: "Low-Medium" },
+ MH: { weight: 0.4, title: "Medium-High" },
+ H: { weight: 0.5, title: "High" }
+ }
+ },
+ TD: {
+ title: 'Target Distribution',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined", default:true },
+ N: { weight: 0.0, title: "None" },
+ L: { weight: 0.25, title: "Low" },
+ M: { weight: 0.75, title: "Medium" },
+ H: { weight: 1.0, title: "High" }
+ }
+ },
+ CR: {
+ title: 'Confidentiality Requirement',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined", default:true },
+ L: { weight: 0.5, title: "Low" },
+ M: { weight: 1.0, title: "Medium" },
+ H: { weight: 1.51, title: "High" }
+ }
+ },
+ IR: {
+ title: 'Integrity Requirement',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined", default:true },
+ L: { weight: 0.5, title: "Low" },
+ M: { weight: 1.0, title: "Medium" },
+ H: { weight: 1.51, title: "High" }
+ }
+ },
+ AR: {
+ title: 'Availability Requirement',
+ values: {
+ ND: { weight: 1.0, title: "Not Defined", default:true },
+ L: { weight: 0.5, title: "Low" },
+ M: { weight: 1.0, title: "Medium" },
+ H: { weight: 1.51, title: "High" }
+ }
+ }
+};
+const parse = (vector) => {
+ if (!vector) {
+ return false;
+ }
+ const metricsComponents = vector.split("/");
+ const startIndex = metricsComponents[0].includes("CVSS2") ? 1 : 0;
+ const parsedMetrics = {};
+
+ for (let i = startIndex; i < metricsComponents.length; i++) {
+ const [metric, value] = metricsComponents[i].split(":");
+ if (metricWeights[metric] && metricWeights[metric]?.values?.[value]) {
+ parsedMetrics[metric] = metricWeights[metric]?.values?.[value];
+ parsedMetrics[metric].key = value
+ } else {
+ console.warn(`Unknown metric or value encountered for CVSSv2: ${metric}:${value}`);
+ }
+ }
+
+ const cvss = {
+ metrics: parsedMetrics,
+ };
+
+ Object.keys(metricWeights).forEach((key) => {
+ cvss.metrics[key] = cvss.metrics[key] ? cvss.metrics[key] : { weight: 0, title: undefined, key:'X' };
+ });
+
+ // Define functions to calculate various scores using cvss.metrics for CVSS v2
+ function getImpactScore() {
+ return 10.41 * (1 - (1 - cvss.metrics.C.weight) * (1 - cvss.metrics.I.weight) * (1 - cvss.metrics.A.weight));
+ }
+
+ function getExploitabilityScore() {
+ return 20 * cvss.metrics.AV.weight * cvss.metrics.AC.weight * cvss.metrics.Au.weight;
+ }
+
+ function getBaseScore() {
+ const impact = getImpactScore();
+ const exploitability = getExploitabilityScore();
+ const fImpactValue = impact === 0 ? 0 : 1.176;
+ const baseScore = ((0.6 * impact) + (0.4 * exploitability) - 1.5) * fImpactValue;
+ return roundUp(baseScore); // or use a different rounding function if needed
+ // return impact === 0 ? 0 : roundUp(Math.min((0.6 * impact) + (0.4 * exploitability) - 1.5, 10.0));
+ }
+
+ function getTemporalScore() {
+ const tempMetricKeys = ['E', 'RL', 'RC']
+ const temporalScoreAvailable = tempMetricKeys.reduce((prev, cur)=> {
+ return cvss.metrics[cur].key !== 'X' || prev
+ }, false)
+
+ if (!temporalScoreAvailable) {
+ return 0;
+ }
+ const E = (cvss.metrics.E.key !== 'X' ? cvss.metrics.E.weight : 1) ;
+ const RL = (cvss.metrics.RL.key !== 'X' ? cvss.metrics.RL.weight : 1);
+ const RC = (cvss.metrics.RC.key !== 'X' ? cvss.metrics.RC.weight : 1);
+ return roundUp(getBaseScore() * E * RL * RC);
+ }
+
+ function getEnvironmentalScore() {
+ const environmentalMetrics = ['CR', 'IR', 'AR', 'CDP', 'TD'];
+ const allNotDefined = environmentalMetrics.every(metric => cvss.metrics[metric].key === 'ND');
+
+ if (allNotDefined) {
+ return null; // or however you wish to represent an undefined score
+ } else {
+ const temporalScoreAdjusted = getTemporalScore(true); // Assuming this function exists and can handle adjusted impact
+ const CDP = (cvss.metrics.CDP.key !== 'ND' ? cvss.metrics.CDP.weight : 1);
+ const TD = (cvss.metrics.TD.key !== 'ND' ? cvss.metrics.TD.weight : 1);
+ let rawEnvironmentalScore = ((temporalScoreAdjusted + (10 - temporalScoreAdjusted) * CDP) * TD);
+ // Round to one decimal place
+ rawEnvironmentalScore = roundUp(rawEnvironmentalScore);
+ // Ensure the score is not less than 0.0
+ return Math.max(0.0, rawEnvironmentalScore);
+ }
+}
+ const baseScore = roundUp(getBaseScore());
+ const temporalScore = roundUp(getTemporalScore());
+ const environmentalScore = roundUp(getEnvironmentalScore())
+ let overallScore = baseScore; // Default to base score
+
+ // If environmental score is not "NA" and not zero, use it
+ if (environmentalScore !== "NA" && environmentalScore !== 0) {
+ overallScore = environmentalScore;
+ } else if (temporalScore !== "NA" && temporalScore !== 0) {
+ overallScore = temporalScore;
+ }
+ return {
+ version: 'v2.0',
+ vector: vector,
+ metrics: cvss.metrics,
+ metricWeights:metricWeights,
+ impactScore: roundUp(getImpactScore()),
+ exploitabilityScore: roundUp(getExploitabilityScore()),
+ baseScore: baseScore,
+ temporalScore: temporalScore,
+ environmentalScore: environmentalScore,
+ overallScore:overallScore,
+ }
+};
+
+// Helper function to round up to one decimal place
+function roundUp(number) {
+ return Math.round(number * 10) / 10;
+}
+
+module.exports = {
+ metricWeights:metricWeights,
+ parse:parse,
+};
\ No newline at end of file
diff --git a/report_shots/cvss-parser/v3.js b/report_shots/cvss-parser/v3.js
new file mode 100644
index 0000000000..fd3b99b50f
--- /dev/null
+++ b/report_shots/cvss-parser/v3.js
@@ -0,0 +1,385 @@
+const { roundUp } = require('./helpers');
+const metricWeights = {
+ // Base Metric Group
+ AV: {
+ title: 'Attack Vector',
+ default:0.85,
+ values: {
+ N: { weight: 0.85, title: "Network" },
+ A: { weight: 0.62, title: "Adjacent Network" },
+ L: { weight: 0.55, title: "Local" },
+ P: { weight: 0.2, title: "Physical" }
+ }
+ },
+ AC: {
+ title: 'Attack Complexity',
+ values: {
+ L: { weight: 0.77, title: "Low" },
+ H: { weight: 0.44, title: "High" }
+ }
+ },
+ PR: {
+ title: 'Privileges Required',
+ values: {
+ N: { weight: 0.85, title: "None" },
+ L: { weight: 0.62, title: "Low" },
+ H: { weight: 0.27, title: "High" }
+ },
+ valuesChanged: {
+ N: { weight: 0.85, title: "None" },
+ L: { weight: 0.68, title: "Low" },
+ H: { weight: 0.50, title: "High" },
+ X: { weight: 0, title: "High" }
+ }
+ },
+ UI: {
+ title: 'User Interaction',
+ values: {
+ N: { weight: 0.85, title: "None" },
+ R: { weight: 0.62, title: "Required" }
+ }
+ },
+ S: {
+ title: 'Scope',
+ values: {
+ U: { weight: 0, title: "Unchanged" },
+ C: { weight: 0, title: "Changed" }
+ }
+ },
+ C: {
+ title: 'Confidentiality Impact',
+ values: {
+ N: { weight: 0, title: "None" },
+ L: { weight: 0.22, title: "Low" },
+ H: { weight: 0.56, title: "High" }
+ }
+ },
+ I: {
+ title: 'Integrity Impact',
+ default:0,
+ values: {
+ N: { weight: 0, title: "None" },
+ L: { weight: 0.22, title: "Low" },
+ H: { weight: 0.56, title: "High" }
+ }
+ },
+ A: {
+ title: 'Availability Impact',
+ values: {
+ N: { weight: 0, title: "None" },
+ L: { weight: 0.22, title: "Low" },
+ H: { weight: 0.56, title: "High" }
+ }
+ },
+ // Temporal Metric Group
+ E: {
+ title: 'Exploit Code Maturity',
+ values: {
+ X: { weight: 1, title: "Not Defined" },
+ H: { weight: 1, title: "High" },
+ F: { weight: 0.97, title: "Functional" },
+ P: { weight: 0.94, title: "Proof-of-Concept" },
+ U: { weight: 0.91, title: "Unproven" }
+ }
+ },
+ RL: {
+ title: 'Remediation Level',
+ default:1,
+ values: {
+ X: { weight: 1, title: "Not Defined" },
+ O: { weight: 0.95, title: "Official Fix" },
+ T: { weight: 0.96, title: "Temporary Fix" },
+ W: { weight: 0.97, title: "Workaround" },
+ U: { weight: 1, title: "Unavailable" }
+ }
+ },
+ RC: {
+ title: 'Report Confidence',
+ default:1,
+ values: {
+ X: { weight: 1, title: "Not Defined" },
+ C: { weight: 1, title: "Confirmed" },
+ R: { weight: 0.96, title: "Reasonable" },
+ U: { weight: 0.92, title: "Unknown" }
+ }
+ },
+ // Environmental Metric Group
+ CR: {
+ title: 'Confidentiality Requirement',
+ default:1,
+ values: {
+ X: { weight: 1, title: "Not Defined" },
+ L: { weight: 0.5, title: "Low" },
+ M: { weight: 1, title: "Medium" },
+ H: { weight: 1.5, title: "High" }
+ }
+ },
+ IR: {
+ title: 'Integrity Requirement',
+ default:1,
+ values: {
+ X: { weight: 1, title: "Not Defined" },
+ L: { weight: 0.5, title: "Low" },
+ M: { weight: 1, title: "Medium" },
+ H: { weight: 1.5, title: "High" }
+ }
+ },
+ AR: {
+ title: 'Availability Requirement',
+ default:1,
+ values: {
+ X: { weight: 1, title: "Not Defined" },
+ L: { weight: 0.5, title: "Low" },
+ M: { weight: 1, title: "Medium" },
+ H: { weight: 1.5, title: "High" }
+ }
+ },
+ // Modified Base Metrics
+ MAV: {
+ title: 'Modified Attack Vector',
+ default:0.85,
+ values: {
+ X: { weight: 0.85, title: "Not Defined" },
+ N: { weight: 0.85, title: "Network" },
+ A: { weight: 0.62, title: "Adjacent" },
+ L: { weight: 0.55, title: "Local" },
+ P: { weight: 0.2, title: "Physical" }
+ }
+ },
+ MAC: {
+ title: 'Modified Attack Complexity',
+ default:0.77,
+ values: {
+ X: { weight: 0.77, title: "Not Defined"},
+ L: { weight: 0.77, title: "Low" },
+ H: { weight: 0.44, title: "High" }
+ }
+ },
+ MPR: {
+ title: 'Modified Privileges Required',
+ default:0.85,
+ values: {
+ X: { weight: 0.85, title: "Not Defined" },
+ N: { weight: 0.85, title: "None" },
+ L: { weight: 0.62, title: "Low" },
+ H: { weight: 0.27, title: "High" }
+ }
+ },
+ MUI: {
+ title: 'Modified User Interaction',
+ default:0.85,
+ values: {
+ X: { weight: 0.85, title: "Not Defined" },
+ N: { weight: 0.85, title: "None" },
+ R: { weight: 0.62, title: "Required" }
+ }
+ },
+ MS: {
+ title: 'Modified Scope',
+ default:6.42,
+ values: {
+ X: { weight: 6.42, title: "Not Defined" },
+ U: { weight: 6.42, title: "Unchanged" },
+ C: { weight: 7.52, title: "Changed" }
+ }
+ },
+ MC: {
+ title: 'Modified Confidentiality',
+ default:0,
+ values: {
+ X: { weight: 0, title: "Not Defined" },
+ N: { weight: 0, title: "None" },
+ L: { weight: 0.22, title: "Low" },
+ H: { weight: 0.56, title: "High" }
+ }
+ },
+ MI: {
+ title: 'Modified Integrity',
+ default:0,
+ values: {
+ X: { weight: 0, title: "Not Defined" },
+ N: { weight: 0, title: "None" },
+ L: { weight: 0.22, title: "Low" },
+ H: { weight: 0.56, title: "High" }
+ }
+ },
+ MA: {
+ title: 'Modified Availability',
+ default:0,
+ values: {
+ X: { weight: 0, title: "Not Defined" },
+ N: { weight: 0, title: "None" },
+ L: { weight: 0.22, title: "Low" },
+ H: { weight: 0.56, title: "High" }
+ }
+ }
+};
+
+const parse = (vector)=> {
+ if (!vector) {
+ return false;
+ }
+
+ const metricsComponents = vector.split("/");
+ const version = metricsComponents[0].includes("CVSS") ? metricsComponents[0].split(":")[1] : "Unknown";
+ const startIndex = metricsComponents[0].includes("CVSS") ? 1 : 0;
+
+ const parsedMetrics = {};
+ for (let i = startIndex; i < metricsComponents.length; i++) {
+ const [metric, value] = metricsComponents[i].split(":");
+
+ if (metricWeights[metric] && metricWeights[metric]?.values?.[value]) {
+ parsedMetrics[metric] = {...metricWeights[metric].values?.[value]};
+ parsedMetrics[metric].key = value
+ }
+ }
+ const cvss = {
+ metrics: parsedMetrics,
+ };
+ // Adjust the PR weight if necessary
+ ['PR', 'MRP'].forEach((l)=> {
+ if (cvss.metrics?.[l] && cvss.metrics.S && cvss.metrics.S.key === 'C') {
+ // Adjust PR weight for 'Changed' scope
+ const prAdjustments = { 'N': 0.85, 'L': 0.68, 'H': 0.50 };
+ const prValue = cvss.metrics[l].key;
+ if (prAdjustments[prValue]) {
+ cvss.metrics[l].weight = prAdjustments[prValue];
+ }
+ }
+ })
+
+ // Ensure all required metrics are present
+ Object.keys(metricWeights).forEach((key) => {
+ cvss.metrics[key] = cvss.metrics[key] ? cvss.metrics[key] : { weight: 0, title: undefined, key:'X' };
+ });
+
+ function getImpactScore() {
+ const ISCbase = 1 - (1 - cvss.metrics.C.weight) * (1 - cvss.metrics.I.weight) * (1 - cvss.metrics.A.weight);
+ return cvss.metrics.S.title === 'Changed' ? 7.52 * (ISCbase - 0.029) - 3.25 * Math.pow(ISCbase - 0.02, 15) : 6.42 * ISCbase;
+ }
+
+ function getExploitabilityScore() {
+ return (
+ 8.22 *
+ cvss.metrics.AV.weight *
+ cvss.metrics.AC.weight *
+ cvss.metrics.PR.weight *
+ cvss.metrics.UI.weight
+ );
+ }
+
+
+ function getBaseScore() {
+ const ISC = getImpactScore();
+ const ESC = getExploitabilityScore();
+
+ let baseScore = 0;
+ if (ISC > 0) {
+ if (cvss.metrics.S.title === 'Changed')
+ baseScore = Math.min(1.08 * (ISC + ESC), 10);
+ else
+ baseScore = Math.min(ISC + ESC, 10);
+ }
+
+ return roundUp(baseScore);
+ }
+
+ function getTemporalScore() {
+ const envMetricKeys = ['E', 'RL', 'RC']
+ const temporalScoreAvailable = envMetricKeys.reduce((prev, cur)=> {
+ return cvss.metrics[cur].key !== 'X' || prev
+ }, false)
+
+ if (!temporalScoreAvailable) {
+ return 0;
+ }
+ const E = (cvss.metrics.E.key !== 'X' ? cvss.metrics.E.weight : 1) ;
+ const RL = (cvss.metrics.RL.key !== 'X' ? cvss.metrics.RL.weight : 1);
+ const RC = (cvss.metrics.RC.key !== 'X' ? cvss.metrics.RC.weight : 1);
+ return roundUp(getBaseScore() * E * RL * RC);
+ }
+
+ function getEnvironmentalScore() {
+ // First, handle 'X' values by setting them to the base score values
+
+ const envMetricKeys = ['MC', 'MI', 'MA', 'MAV', 'MAC', 'MPR', 'MUI', 'CR', 'IR', 'AR']
+ const environmentalScoreAvailable = envMetricKeys.reduce((prev, cur)=> {
+ return cvss.metrics[cur].key !== 'X' || prev
+ }, false)
+
+ if (!environmentalScoreAvailable) {
+ return 0;
+ }
+ const MC = cvss.metrics.MC.key !== 'X' ? cvss.metrics.MC.weight : cvss.metrics.C.weight;
+ const MI = cvss.metrics.MI.key !== 'X' ? cvss.metrics.MI.weight : cvss.metrics.I.weight;
+ const MA = cvss.metrics.MA.key !== 'X' ? cvss.metrics.MA.weight : cvss.metrics.A.weight;
+
+ const MAV = cvss.metrics.MAV.key !== 'X' ? cvss.metrics.MAV.weight : cvss.metrics.AV.weight;
+ const MAC = cvss.metrics.MAC.key !== 'X' ? cvss.metrics.MAC.weight : cvss.metrics.AC.weight;
+ const MPR = cvss.metrics.MPR.key !== 'X' ? cvss.metrics.MPR.weight : cvss.metrics.PR.weight;
+ const MUI = cvss.metrics.MUI.key !== 'X' ? cvss.metrics.MUI.weight : cvss.metrics.UI.weight;
+
+ const CR = cvss.metrics.CR.key !== 'X' ? cvss.metrics.CR.weight : 1;
+ const IR = cvss.metrics.IR.key !== 'X' ? cvss.metrics.IR.weight : 1;
+ const AR = cvss.metrics.AR.key !== 'X' ? cvss.metrics.AR.weight : 1;
+
+ // Now calculate the ISC Modified using the base or modified values as appropriate
+ const ISCmodified = Math.min(1 - (1 - MC * CR) * (1 - MI * IR) * (1 - MA * AR), 0.915);
+
+ let mISC;
+ if (cvss.metrics.MS && cvss.metrics.MS.title === 'Changed') {
+ // Use the original formula for CVSS v3.0
+ mISC = 7.52 * (ISCmodified - 0.029) - 3.25 * Math.pow(ISCmodified * 0.9731 - 0.02, 13);
+ } else {
+ mISC = 6.42 * ISCmodified;
+ }
+
+ // Calculate Modified Exploitability Subscore
+ const mESC = 8.22 * MAV * MAC * MPR * MUI;
+
+ let environmentalScore = 0;
+ if (mISC > 0) {
+ if (cvss.metrics.MS && cvss.metrics.MS.title === 'Changed') {
+ environmentalScore = roundUp(Math.min(1.08 * (mISC + mESC), 10)) *
+ (cvss.metrics.E.key !== 'X' ? cvss.metrics.E.weight : 1) *
+ (cvss.metrics.RL.key !== 'X' ? cvss.metrics.RL.weight : 1) *
+ (cvss.metrics.RC.key !== 'X' ? cvss.metrics.RC.weight : 1);
+ } else {
+ environmentalScore = roundUp(Math.min(mISC + mESC, 10)) *
+ (cvss.metrics.E.key !== 'X' ? cvss.metrics.E.weight : 1) *
+ (cvss.metrics.RL.key !== 'X' ? cvss.metrics.RL.weight : 1) *
+ (cvss.metrics.RC.key !== 'X' ? cvss.metrics.RC.weight : 1);
+ }
+ }
+ return roundUp(environmentalScore);
+ }
+
+ const baseScore = roundUp(getBaseScore());
+ const temporalScore = roundUp(getTemporalScore());
+ const environmentalScore = roundUp(getEnvironmentalScore())
+ let overallScore = baseScore; // Default to base score
+
+ // If environmental score is not "NA" and not zero, use it
+ if (environmentalScore !== "NA" && environmentalScore !== 0) {
+ overallScore = environmentalScore;
+ } else if (temporalScore !== "NA" && temporalScore !== 0) {
+ overallScore = temporalScore;
+ }
+ return {
+ version: version,
+ vector: vector,
+ metrics: cvss.metrics,
+ impactScore: getImpactScore().toFixed(1),
+ metricWeights:metricWeights,
+ exploitabilityScore: getExploitabilityScore().toFixed(1),
+ baseScore: baseScore,
+ temporalScore: temporalScore,
+ environmentalScore: environmentalScore,
+ overallScore:overallScore,
+ };
+}
+
+module.exports = {
+ metricWeights:metricWeights,
+ parse:parse,
+};
\ No newline at end of file
diff --git a/report_shots/eslint.config.mjs b/report_shots/eslint.config.mjs
new file mode 100644
index 0000000000..2eed6313a8
--- /dev/null
+++ b/report_shots/eslint.config.mjs
@@ -0,0 +1,27 @@
+import globals from "globals";
+import pluginJs from "@eslint/js";
+
+
+
+export default [
+ // Main configuration for JavaScript files
+ {
+ files: ["**/*.js"],
+ languageOptions: {
+ sourceType: "script",
+ globals: globals.node,
+ },
+ rules: {
+ "no-unused-vars": "warn", // Explicitly set as warning
+ },
+ },
+
+ // Ensure this part also has `no-unused-vars` set to "warn"
+ {
+ ...pluginJs.configs.recommended,
+ rules: {
+ ...pluginJs.configs.recommended.rules,
+ "no-unused-vars": "warn", // Override here as well
+ },
+ },
+];
\ No newline at end of file
diff --git a/report_shots/package.json b/report_shots/package.json
index 0cce428000..f71f8a572b 100644
--- a/report_shots/package.json
+++ b/report_shots/package.json
@@ -1,6 +1,14 @@
{
"dependencies": {
+ "@svgdotjs/svg.js": "^3.2.4",
"js-yaml": "^4.1.0",
- "puppeteer": "^14.1.1"
+ "sharp": "^0.33.5",
+ "svgdom": "^0.1.19",
+ "svgson": "^5.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.13.0",
+ "eslint": "^9.13.0",
+ "globals": "^15.11.0"
}
}
diff --git a/report_shots/shots.js b/report_shots/shots.js
index 5db4c70bab..5e455c5b23 100644
--- a/report_shots/shots.js
+++ b/report_shots/shots.js
@@ -1,46 +1,468 @@
-const puppeteer = require('puppeteer');
-const process = require('process');
const util = require('util');
const fsPromise = require('fs/promises');
const yaml = require('js-yaml')
const fs = require('fs');
+const { parseJSON, parseCSVFormatV2, formatBytes } = require('./utils');
+const { convertVulnsData, vulnsColorScheme } = require('./vulnsParser');
+const sharp = require('sharp');
+const svgson = require('svgson');
+// Function to save SVG content to file
+function saveSVGToFile(svgContent, imageSavePath) {
+ fs.writeFile(imageSavePath, svgContent, 'utf8', (err) => {
+ if (err) {
+ console.error('Error saving SVG file:', err);
+ } else {
+ console.log('SVG file successfully saved at:', imageSavePath);
+ }
+ });
+}
+
+// generate rect path with rounded top left and right corners
+function createRoundedRectPath(x, y, width, height, radius) {
+ if (height < radius) {
+ radius = height;
+ }
+ return `
+ M${x + radius},${y}
+ H${x + width - radius}
+ C${x + width},${y} ${x + width},${y} ${x + width},${y + radius}
+ V${y + height}
+ H${x}
+ V${y + radius}
+ C${x},${y} ${x},${y} ${x + radius},${y}
+ Z
+ `;
+}
+
+const generateCharts = async (imageName, platform, imageSavePath) => {
+ const fetchDataRequest = async (path)=> {
+ let baseAPIUrl = ''
+ switch (platform) {
+ case 'pre-prod':
+ baseAPIUrl = 'https://frontrow-dev.rapidfort.io'
+ break;
+ case 'staging':
+ baseAPIUrl = 'https://frontrow.rapidfort.io'
+ break;
+ default:
+ baseAPIUrl = 'https://us01.rapidfort.com'
+ }
+ const result = await fetch(`${baseAPIUrl}${path}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ return await parseJSON(result);
+ }
+
+ try {
+ // api/v1/community/imageinfo/?name=docker.io/library/redis
+ const jsonInfo = await fetchDataRequest(`/api/v1/community/imageinfo/?name=${imageName}`);
+ const imageInfoRaw = await fetchDataRequest(`/api/v1/community/images/?image_name=${imageName}`);
+ const imageInfo = parseCSVFormatV2({fields:imageInfoRaw?.fields, data:[imageInfoRaw?.image]})?.[0]
+ const vulns = await fetchDataRequest(jsonInfo?.vulns);
+ const vulnsHardened = await fetchDataRequest(jsonInfo?.vulns_hardened);
+ const {vulnsSeverityCount: vulnsHardenedSummary, hardenedVulnsFlags, } = convertVulnsData(vulnsHardened, true, true);
+ const {vulnsSeverityCount: vulnsOriginalSummary} = convertVulnsData(vulns, true, false, hardenedVulnsFlags);
+
+ // generate SVGs
+ const vulnsSavingsChartSVG = await generateSavingsChart('Vulnerabilities', imageInfo.noVulns, imageInfo.noVulnsHardened, false);
+ const packagesSavingsChartSVG = await generateSavingsChart('Packages', imageInfo.noPkgs, imageInfo.noPkgsHardened, false);
+ const sizeSavingsChartSVG = await generateSavingsChart('Attack surface', imageInfo.origImageSize, imageInfo.hardenedImageSize, true);
+ const contextualSeverityChart = await generateContextualSeverityChart(vulnsOriginalSummary)
+ const vulnsBySeverityChart = await generateVulnsBySeverityChart(vulnsOriginalSummary.default, vulnsHardenedSummary.default);
+
+ // save individual charts as svg
+ // saveSVGToFile(vulnsSavingsChartSVG, util.format('%s/savings_chart_vulns.svg', imageSavePath));
+ // saveSVGToFile(packagesSavingsChartSVG, util.format('%s/savings_chart_pkgs.svg', imageSavePath));
+ // saveSVGToFile(sizeSavingsChartSVG, util.format('%s/savings_chart_size.svg', imageSavePath));
+ // saveSVGToFile(contextualSeverityChart, util.format('%s/contextual_severity_chart.svg', imageSavePath));
+ // saveSVGToFile(vulnsBySeverityChart, util.format('%s/vulns_by_severity_histogram.svg', imageSavePath));
+ generateReportViews(vulnsSavingsChartSVG, packagesSavingsChartSVG, sizeSavingsChartSVG, contextualSeverityChart, vulnsBySeverityChart, imageSavePath);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Recursive function to find width and height in nested SVG tags
+const findSVGDimensions = (node) => {
+ if (node.name === 'svg' && node.attributes.width && node.attributes.height) {
+ return {
+ width: parseFloat(node.attributes.width),
+ height: parseFloat(node.attributes.height),
+ };
+ }
+
+ for (const child of node.children || []) {
+ const dimensions = findSVGDimensions(child);
+ if (dimensions) return dimensions;
+ }
+
+ return null;
+};
+
+const parseSVGDimensions = async (svgContent) => {
+ const svgJSON = await svgson.parse(svgContent);
+ const dimensions = findSVGDimensions(svgJSON);
+ return dimensions || { width: 0, height: 0 };
+};
+
+const generateReportViews = async (
+ vulnsSavingsChartSVG,
+ packagesSavingsChartSVG,
+ sizeSavingsChartSVG,
+ contextualSeverityChartSVG,
+ vulnsBySeverityChartSVG,
+ imageSavePath
+) => {
+ const gap = 16;
+ let padding = 16;
+
+ const svgFiles = [
+ vulnsBySeverityChartSVG,
+ contextualSeverityChartSVG,
+ vulnsSavingsChartSVG,
+ packagesSavingsChartSVG,
+ sizeSavingsChartSVG,
+ ];
+ // Extract and deduplicate style content
+ // Extract and deduplicate @font-face and other style content
+ const uniqueFontFaces = new Set();
+ const otherStyles = new Set();
-async function takeShots(browser, imageSavePath, imageUrl, firstShot) {
- const page = await browser.newPage();
+ svgFiles.forEach((svgContent) => {
+ const styleMatch = svgContent.match(/
+ `;
+
+ // Remove individual
+
+
+
+
+20
+40
+0
+
+Low
+
+
+
+
+Medium
+
+
+
+
+High
+
+
+
+
+Critical
+
+
+
+Contextual severity
+
+
diff --git a/report_shots/template_savings_chart.svg b/report_shots/template_savings_chart.svg
new file mode 100644
index 0000000000..1411fca12d
--- /dev/null
+++ b/report_shots/template_savings_chart.svg
@@ -0,0 +1,51 @@
+
diff --git a/report_shots/template_vulns_by_severity.svg b/report_shots/template_vulns_by_severity.svg
new file mode 100644
index 0000000000..a72897b005
--- /dev/null
+++ b/report_shots/template_vulns_by_severity.svg
@@ -0,0 +1,76 @@
+
diff --git a/report_shots/utils.js b/report_shots/utils.js
new file mode 100644
index 0000000000..4aa2226e2f
--- /dev/null
+++ b/report_shots/utils.js
@@ -0,0 +1,75 @@
+const parseJSON = (result) => {
+ return result.text().then(function(text) {
+ try {
+ return JSON.parse(text);
+ } catch {
+ const arr = text.split('\n');
+ for (let i = 0; i < arr.length; i++) {
+ const line = arr[i]
+ try {
+ const lineJSON = JSON.parse(line);
+ if(typeof lineJSON === 'object') {
+ return lineJSON
+ }
+ } catch (error) {
+ console.log('error', error)
+ }
+ }
+ }
+ return text
+ })
+}
+
+function formatBytes (bytes, decimals = 2, forceUnit = null) {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1000;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+
+ let i;
+ if (forceUnit === null) {
+ i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
+ } else {
+ i = sizes.indexOf(forceUnit);
+ }
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+}
+
+
+function toCamelCase(str) {
+ return str
+ // First, lowercases the string to handle cases like 'Hello-World'
+ .toLowerCase()
+ // Then, replaces any non-alphanumeric character (or set of characters) followed by an alphanumeric character
+ .replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
+}
+
+const parseCSVFormatV2 = ({fields, data}, topLevel) => {
+ const reducer = (prev, cur, index) => {
+ if (Array.isArray(fields[index])) {
+ const [key, subheaders] = fields[index]
+ prev[toCamelCase(key)] = parseCSVFormatV2({fields:subheaders, data:cur})
+ } else {
+ prev[toCamelCase(fields[index])] = cur
+ }
+ return prev
+ }
+ const mapper = (l) => {
+ return l?.reduce?.(reducer, {}) ?? {}
+ }
+ let result
+ if ((data?.length > 0 && Array.isArray(data[0]))) {
+ result = data?.map?.(mapper)
+ } else {
+ result = data?.reduce?.(reducer, {}) ?? topLevel ? [] : {}
+ }
+ return result
+}
+
+module.exports = {
+ parseJSON,
+ parseCSVFormatV2,
+ formatBytes
+};
diff --git a/report_shots/vulnsParser.js b/report_shots/vulnsParser.js
new file mode 100644
index 0000000000..cee7519127
--- /dev/null
+++ b/report_shots/vulnsParser.js
@@ -0,0 +1,265 @@
+const { parse : parseCVSS } = require('./cvss-parser/cvss');
+
+
+
+const vulnsColorScheme = {
+ exploited:'#C62A2F',
+ critical:'#DF1C41',
+ high:'#6E3FF3',
+ medium:'#F2AE40',
+ low:'#35B9E9',
+ unknown:'#8b8d98',
+ poc:'#C62A2F',
+ na:'#32D583',
+}
+const SEVERITY = {
+ CRITICAL: 'critical',
+ HIGH: 'high',
+ MEDIUM: 'medium',
+ LOW: 'low',
+ UNKNOWN: 'unknown'
+}
+
+
+const SEVERITY_DETAIL = {
+ [SEVERITY.CRITICAL]: {
+ id: SEVERITY.CRITICAL,
+ order: 0,
+ label: 'Critical',
+ color: vulnsColorScheme.critical
+ },
+ [SEVERITY.HIGH]: {
+ id: SEVERITY.HIGH,
+ order: 1,
+ label: 'High',
+ color: vulnsColorScheme.high
+ },
+ [SEVERITY.MEDIUM]: {
+ id: SEVERITY.MEDIUM,
+ order: 2,
+ label: 'Medium',
+ color: vulnsColorScheme.medium
+ },
+ [SEVERITY.LOW]: {
+ id: SEVERITY.LOW,
+ order: 3,
+ label: 'Low',
+ color:vulnsColorScheme.low
+ },
+ [SEVERITY.UNKNOWN]: {
+ id: SEVERITY.UNKNOWN,
+ order: 4,
+ label: 'Unknown',
+ color: vulnsColorScheme.unknown
+ },
+}
+
+const applyVectorModifiers = (vector, version, pocAvailable, execPath) => {
+ // Parse the vector string into an object
+ const vectorParams = vector.split('/').reduce((acc, param) => {
+ const [key, value] = param.split(':');
+ acc[key] = value;
+ return acc;
+ }, {});
+
+ // Update the Exploit Code Maturity if pocAvailable is true
+ if (!pocAvailable) {
+ vectorParams.E = 'U';
+ vectorParams.RL = version === 'V2' ? 'ND' : 'X';
+ vectorParams.RC = version === 'V2' ? 'ND' : 'X';
+ }
+
+ // Update the Attack Complexity and User Interaction if execPath is true
+ if (!execPath && version === 'V3') {
+ vectorParams.MAC = 'H';
+ vectorParams.MUI = 'R';
+ }
+
+ // Construct the updated vector string
+ const updatedVector = Object.entries(vectorParams)
+ .map(([key, value]) => `${key}:${value}`)
+ .join('/');
+
+ return updatedVector;
+};
+
+
+function getSeverity(version, score) {
+ const ratings = {
+ 'V2': [
+ { threshold: 0.0, label: 'UNKNOWN' },
+ { threshold: 3.9, label: 'LOW' },
+ { threshold: 6.9, label: 'MEDIUM' },
+ { threshold: 10.0, label: 'HIGH' }
+ ],
+ 'V3': [
+ { threshold: 0.0, label: 'UNKNOWN' },
+ { threshold: 3.9, label: 'LOW' },
+ { threshold: 6.9, label: 'MEDIUM' },
+ { threshold: 8.9, label: 'HIGH' },
+ { threshold: 10.0, label: 'CRITICAL' }
+ ]
+ };
+
+ const rating = ratings[version];
+ if (!rating) return 'UNKNOWN';
+
+ for (let i = 0; i < rating.length; i++) {
+ if (score <= rating[i].threshold) {
+ return rating[i].label;
+ }
+ }
+}
+
+const applyContextualCVSS = (item, type, imageHardened)=> {
+ const version = item[type]?.Version;
+ let vector = item[type].SeverityVector;
+ let score = item[type].SeverityScore;
+ let severity = item[type].Severity;
+ if (vector !== '-') {
+ vector = applyVectorModifiers(vector, version, item.RRS === 1, !imageHardened || item.hardened)
+ let parsedData = parseCVSS(vector, version);
+ score = parsedData;
+ const severity = getSeverity(version, parsedData.overallScore) ?? 'Unknown'
+ return {
+ Severity : (SEVERITY_DETAIL[severity.toLowerCase()]?.order ?? 0) > (SEVERITY_DETAIL[item[type]?.Severity.toLowerCase()]?.order) ? severity : item[type].Severity,
+ SeverityScore: parsedData.overallScore ?? score,
+ SeverityVector: vector,
+ Source: item[type].SeveritySource,
+ Version: version,
+ parsedData:parsedData,
+ }
+ } else {
+ return {
+ Severity: severity,
+ SeverityScore : score,
+ SeverityVector : vector,
+ Source: item[type].SeveritySource,
+ Version : version,
+ }
+ }
+}
+
+const vulnsCountInfoObjTemplate = {
+ critical:0,
+ medium: 0,
+ high: 0,
+ low: 0,
+ unknown:0,
+ total : 0,
+ poc: 0,
+ na: 0
+}
+
+const updateSeverityInfo = (item, cvss, severity, severitySource, severityRef, imageHardened) => {
+ const nvdScale = cvss?.nvd?.V3Score ? 'V3' : cvss?.nvd?.V2Score ? 'V2' : severityRef?.nvd?.V3Severity ? 'V3' : severityRef?.nvd?.V2Severity ? 'V2' : '-';
+ const defaultScale = cvss?.[severitySource]?.V3Score ? 'V3' : cvss?.[severitySource]?.V2Score ? 'V2' : severityRef?.[severitySource]?.V3Severity ? 'V3' : severityRef?.[severitySource]?.V2Severity ? 'V2' : '-';;
+ item.nvd = {
+ Severity : severityRef?.nvd?.[`${nvdScale}Severity`] ?? 'UNKNOWN',
+ SeverityScore : (parseFloat(cvss?.nvd?.[`${nvdScale}Score`]) > 0 ? cvss?.nvd?.[`${nvdScale}Score`] : '-') ?? '-',
+ SeverityVector : cvss?.nvd?.[`${nvdScale}Vector`] ?? '-',
+ Source:'NVD',
+ Version : nvdScale,
+ }
+
+ item.default = {
+ Severity : severity ?? '',
+ }
+ item.default = {
+ Severity : severity,
+ Source:severitySource,
+ SeverityScore : (parseFloat(cvss?.[severitySource]?.[`${defaultScale}Score`]) > 0 ? cvss?.[severitySource]?.[`${defaultScale}Score`] : '-') ?? '-',
+ SeverityVector : cvss?.[severitySource]?.[`${defaultScale}Vector`] ?? '-',
+ Version : defaultScale,
+ }
+
+ item.rfcvss = {}
+
+ item.rfcvss_nvd = applyContextualCVSS(item, 'nvd', imageHardened)
+ if (severitySource.toLowerCase() === 'nvd') {
+ item.rfcvss_default = item.rfcvss_nvd
+ } else {
+ item.rfcvss_default = applyContextualCVSS(item, 'default', imageHardened)
+ }
+
+ if (!cvss) {
+ if (cvss?.[severitySource]?.V3Score) {
+ item.default.Version = 'V3'
+ item.default.SeverityScore = cvss?.[severitySource]?.V3Score
+ } else if (cvss?.[severitySource]?.V2Score) {
+ item.default.Version = 'V2'
+ item.default.SeverityScore = cvss?.[severitySource]?.V2Score
+ } else {
+ item.default.Version = '-'
+ item.default.SeverityScore = '-'
+ }
+ }
+}
+
+const convertVulnsData = (data, imageHardened, isHardened, flags) => {
+ let vulnsSeverityCount = {default: {...vulnsCountInfoObjTemplate}, nvd:{...vulnsCountInfoObjTemplate}, rfcvss_nvd:{...vulnsCountInfoObjTemplate}, rfcvss_default:{...vulnsCountInfoObjTemplate}}
+ const seenVulns = new Map();
+ const seenNotApplicableVulns = new Map();
+ const hardenedVulnsFlags = {}
+ let appKeyIndex = 0;
+ data?.forEach?.((cur, index) => {
+ cur?.Vulnerabilities?.forEach?.((item)=> {
+ let appKey = appKeyIndex++;
+ if ((cur.Class && cur.Class === 'os-pkgs') || (!cur.Class && index === 0)) {
+ appKey = cur.Type;
+ }
+ if (isHardened) {
+ item.hardened = true;
+ hardenedVulnsFlags[`i:${item.VulnerabilityID}|v${item.InstalledVersion}|p${item.PkgName}`] = true
+ } else {
+ item.hardened = flags[`i:${item.VulnerabilityID}|v${item.InstalledVersion}|p${item.PkgName}`]
+ }
+ item.applicable = item.RFJustification?.status === 'na' ? false : true;
+ item.Severity = (item.Severity) ? item.Severity : 'UNKNOWN'
+ updateSeverityInfo(item, item.CVSS, item.Severity, item.SeveritySource, item.SeverityRef, imageHardened)
+ const key = `${item.VulnerabilityID}_${item?.SourcePkgName ?? item?.PkgName}_${item.PkgSource}_${item.SourceVersion}_${appKey}`;
+ let isFirstOccurency = false;
+ if (item.applicable) {
+ if (!seenVulns.has(key)) {
+ isFirstOccurency = true
+ seenVulns.set(key, item);
+ }
+ } else {
+ if (!seenNotApplicableVulns.has(key)) {
+ isFirstOccurency = true
+ item.Packages = [item.PkgName]
+ seenNotApplicableVulns.set(key, item);
+ }
+ }
+ const advisories = ['default', 'rfcvss_default'];
+ // const advisories = ['default', 'rfcvss_default', 'nvd', 'rfcvss_nvd'];
+ advisories.forEach(advisory => {
+ const severitykey = item[advisory]?.Severity.toLowerCase() ?? 'unknown'
+ if (item.applicable) {
+ if (isFirstOccurency) {
+ vulnsSeverityCount[advisory][severitykey]++;
+ if (item.RRS >= 1) {
+ vulnsSeverityCount[advisory].poc++;
+ }
+ vulnsSeverityCount[advisory].total++
+ }
+ } else {
+ if (isFirstOccurency) {
+ vulnsSeverityCount[advisory].na++;
+ }
+ }
+ })
+ })
+ })
+ return {
+ hardenedVulnsFlags,
+ vulnsSeverityCount
+ }
+}
+
+
+module.exports = {
+ applyContextualCVSS,
+ convertVulnsData,
+ vulnsColorScheme,
+};
\ No newline at end of file
diff --git a/scripts/generate_screenshots.sh b/scripts/generate_screenshots.sh
index b61b48605e..c7ab1dfd43 100755
--- a/scripts/generate_screenshots.sh
+++ b/scripts/generate_screenshots.sh
@@ -7,7 +7,7 @@ SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit ; pwd -P )"
sudo apt-get install -y curl
-curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
+curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs