2 * Copyright 2023 highstreet technologies and others
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 const express = require('express');
18 const router = express.Router();
19 const fs = require('fs');
20 const path = require('path');
22 const directoryPath = 'public/files';
23 const fileExtensions = ['.sbom.spdx.json', '.vulnerabilities.vex.json'];
26 { name: 'critical', color: 'red' },
27 { name: 'high', color: 'orange' },
28 { name: 'medium', color: 'yellow' },
29 { name: 'low', color: 'lightblue' },
30 { name: 'unknown', color: 'lightgrey' },
31 { name: 'none', color: 'lightgrey' }
33 const getScaleFromTotal = (total) => {
34 let scale = Math.round(total / 120);
35 if (scale < 4) scale = 4;
36 if (scale > 10) scale = 10;
41 router.get('/', function (req, res, next) {
43 fs.readdir(directoryPath, function (err, files) {
45 console.error('Error reading directory:', err);
46 return res.status(500).send('Error reading directory (' + directoryPath + ')');
49 const fileList = files.filter(file => file.charAt(0) !== '.').map(file => ({
51 type: file.endsWith(fileExtensions[0]) ? 'sbom' : 'vex',
52 url: path.join('files', file)
55 const vulnerabilities = (list) => {
57 const data = severities.map((severity, index) => {
58 const count = list.filter((vul) => {
59 return vul.ratings[0].severity === severity.name;
61 result[severity.name] = count;
62 return { ...severity, count }
64 const total = data.reduce((sum, d) => sum + d.count, 0);
65 result.scale = getScaleFromTotal(total);
68 const slices = data.map((d, i) => {
69 const startAngle = i === 0 ? 0 : angle
70 angle = startAngle + 2 * Math.PI * (d.count / total)
71 const endAngle = angle;
72 return { ...d, startAngle, endAngle, arc: endAngle - startAngle > Math.PI ? 1 : 0 }
74 result.slices = slices;
75 result.count = list.length;
79 const analyser = (type, file) => {
85 const sbomRawData = fs.readFileSync('./public/' + file);
86 const sbom = JSON.parse(sbomRawData);
87 result.created = new Date(sbom.creationInfo.created).toISOString().replace('.000Z', 'Z');
88 result.packages = sbom.packages.length;
89 result.files = sbom.files.length;
92 const vexRawData = fs.readFileSync('./public/' + file);
93 const vex = JSON.parse(vexRawData);
94 result.created = new Date(vex.metadata.timestamp).toISOString().replace('.000Z', 'Z');
95 result.components = vex.components.length;
96 result.vulnerabilities = vulnerabilities(vex.vulnerabilities);
104 fileList.forEach((file) => {
105 const imageInfo = file.name.split(':');
106 const imageVersion = file.type === 'sbom' ? imageInfo[1].substring(0, imageInfo[1].indexOf(fileExtensions[0])) : imageInfo[1].substring(0, imageInfo[1].indexOf(fileExtensions[1]));
107 const exists = imageList.filter((image) => {
108 return image.name === imageInfo[0];
110 if (exists.length === 0) {
113 version: imageVersion
115 imageObj[file.type] = analyser(file.type, file.url);
116 imageList.push(imageObj);
118 exists[0][file.type] = analyser(file.type, file.url);
122 res.render('index', {
123 title: 'Container Analysis',
124 date: new Date().toISOString(),
130 module.exports = router;