Skip to content

Commit

Permalink
Test
Browse files Browse the repository at this point in the history
  • Loading branch information
BradyMitch committed Jul 10, 2024
1 parent 4973550 commit c20c373
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 70 deletions.
175 changes: 109 additions & 66 deletions .github/helpers/npm-audit/create-report.cjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const path = require('path');
const vulnerabilities = require(path.resolve(__dirname, `../../../vulnerabilities.json`));
const path = require("path");
const vulnerabilities = require(path.resolve(
__dirname,
`../../../vulnerabilities.json`
));

const LOCAL_TEST = false;
const TEST_DIR_PATHS = ['.'];
const TEST_DIR_PATHS = ["."];

/**
* THIS FILE DOES NOT REQUIRE ANY EDITING.
Expand All @@ -17,23 +20,25 @@ const TEST_DIR_PATHS = ['.'];
*/

// Get directory paths from env.
const directoryPaths = LOCAL_TEST ? TEST_DIR_PATHS : JSON.parse(process.env.directoryPaths);
const directoryPaths = LOCAL_TEST
? TEST_DIR_PATHS
: JSON.parse(process.env.directoryPaths);

// Save results to json.
let results = {};

// Emojis.
const check = '✔️';
const attention = '⚠️';
const check = "✔️";
const attention = "⚠️";

// Badge color codes (checked for WCAG standards).
const red = '701807'; // Red background, White text.
const orange = '9e3302'; // Orange background, White text.
const yellow = 'f5c60c'; // Yellow background, Black text.
const blue = '0859A1'; // Blue background, White text.
const red = "701807"; // Red background, White text.
const orange = "9e3302"; // Orange background, White text.
const yellow = "f5c60c"; // Yellow background, Black text.
const blue = "0859A1"; // Blue background, White text.

// GitHub Markdown Formatting.
const heading = (text, size) => `${'#'.repeat(size)} ${text}\n`;
const heading = (text, size) => `${"#".repeat(size)} ${text}\n`;
const lineBreak = () => `\n<br />\n\n`;
const line = (text) => `${text}\n`;
const link = (text, url) => `[${text}](${url}) \n`;
Expand All @@ -47,17 +52,17 @@ const getFormattedDate = () => {

// Determine the ordinal suffix.
const ordinal = (day) => {
const s = ['th', 'st', 'nd', 'rd'];
const s = ["th", "st", "nd", "rd"];
const v = day % 100;
return day + (s[(v - 20) % 10] || s[v] || s[0]);
};

// Formatter for the rest of the date.
const formatter = new Intl.DateTimeFormat('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});

// Format the date and replace the day number with ordinal.
Expand All @@ -66,7 +71,8 @@ const getFormattedDate = () => {

// Messages.
const title = `NPM Vulnerability Report - ${getFormattedDate()}`;
const subTitle = 'NPM packages have been checked for vulnerabilities using npm audit.';
const subTitle =
"NPM packages have been checked for vulnerabilities using npm audit.";
const noVulnerabilities = `${check} - No vulnerabilities detected.`;
const noFixAvailable = `This dependency does \`NOT\` have a \`fix available.\``;
const fixAvailableIndirect = (vuln) =>
Expand All @@ -88,45 +94,67 @@ const outputVulnerabilities = (vulnerabilitiesArray, dirPath) => {
} = vuln;

const badgeColor =
severity === 'critical'
severity === "critical"
? red
: severity === 'high'
? orange
: severity === 'moderate'
? yellow
: blue;
: severity === "high"
? orange
: severity === "moderate"
? yellow
: blue;

// Output vulnerable dep.
results[dirPath] += `${lineBreak()}\n`;
results[dirPath] += `\n---\n`;
results[dirPath] += `${line(`![${name}_header]`)}\n\n`;

// Output details.
results[dirPath] += `\n${line(`**Severity**: \`${severity}\``)}`;
results[dirPath] += `${line(`**Vulnerable Range**: \`${range}\``)}`;

if (via.length > 0) results[dirPath] += `${line(`**Via**:`)}`;
if (via.length > 0) {
results[dirPath] += `\n${line(`**Via**:`)}`;

//Output via details
via.forEach((v, index) => {
results[dirPath] += `\n${line(`${index + 1}: ${v.title}.`)}`;
// Output start of spoiler.
results[dirPath] += `${line(`<details>`)}\n`;
results[dirPath] += `${line(`<summary>`)}`;
results[dirPath] += `${line(
`Expand to see vulnerability details. <br /><br />\n`
)}`;
results[dirPath] += `${line(`</summary>\n`)}`;

//Output via details
via.forEach((v, index) => {
if (typeof v === "string") {
results[dirPath] += `\n${line(`Via \`${v}\``)}`;
} else {
results[dirPath] += `\n${line(`\`${index + 1}\`: ${v.title}.`)}`;

results[dirPath] += `\n${line(`**Severity**: \`${v.severity}\``)}`;
results[dirPath] += `${line(`**Vulnerable Range**: \`${v.range}\``)}`;
results[dirPath] += `${line(`**CVSS Score**: \`${v.cvss} / 10\``)}`;
results[dirPath] += `${line(`**Weaknesses**: \`${v.cwe}\``)}`;
results[dirPath] += `\n${line(`**Severity**: \`${v.severity}\``)}`;
results[dirPath] += `${line(`**Vulnerable Range**: \`${v.range}\``)}`;
results[dirPath] += `${line(`**CVSS Score**: \`${v.cvss} / 10\``)}`;
results[dirPath] += `${line(`**Weaknesses**: \`${v.cwe}\``)}`;

results[dirPath] += `\n${link("GitHub Advisory", v.url)}`;
}
});

results[dirPath] += `\n${link('GitHub Advisory', v.url)}`;
});
// Output end of spoiler.
results[dirPath] += `${line(`</details>\n`)}`;
}

// Output latest version.
results[dirPath] += `\n${line(`**Latest Available Version**: \`${latestVersion}\``)}`;
results[dirPath] += `\n${line(
`**Latest Available Version**: \`${latestVersion}\``
)}`;

// Output fix message.
if (!fixAvailable) results[dirPath] += `\n${line(noFixAvailable)}`;

if (fixAvailable && isDirect) {
results[dirPath] += `\n${line(fixAvailableDirect)}`;
results[dirPath] += `\n${line(`Update \`${name}\` to \`${latestVersion}\`.`)}`;
results[dirPath] += `\n${line(
`Update \`${name}\` to \`${latestVersion}\`.`
)}`;
}

if (fixAvailable && !isDirect) {
Expand All @@ -135,26 +163,29 @@ const outputVulnerabilities = (vulnerabilitiesArray, dirPath) => {
// Output start of spoiler.
results[dirPath] += `${line(`<details>`)}\n`;
results[dirPath] += `${line(`<summary>`)}`;
results[dirPath] +=
`${line(`Expand to see direct dependencies affacted by this vulnerability. <br /><br />\n`)}`;

// Output end of spoiler summary.
results[dirPath] += `${line(
`Expand to see direct dependencies affacted by this vulnerability. <br /><br />\n`
)}`;
results[dirPath] += `${line(`</summary>\n`)}`;

// Output possible fixes:
parentDependencies.forEach((parent) => {
parent.directDependencies.forEach((directDep) => {
const { name, possibleFixAvailable, latestVersion, currentVersion } = directDep;
const { name, possibleFixAvailable, latestVersion, currentVersion } =
directDep;

if (!possibleFixAvailable)
results[dirPath] +=
`${line(`- Direct dependency \`${name}\` does NOT have a fix available.\n\n`)}`;
results[dirPath] += `${line(
`- Direct dependency \`${name}\` does NOT have a fix available.\n\n`
)}`;

if (possibleFixAvailable) {
results[dirPath] +=
`${line(`- Direct dependency \`${name}\` may have a fix available because one of it's nested child dependencies fixes the vulnerability and there is a new version of \`${name}\` available.`)}`;
results[dirPath] +=
`${line(`Update from version \`${currentVersion}\` to \`${latestVersion}\`.\n\n`)}`;
results[dirPath] += `${line(
`- Direct dependency \`${name}\` may have a fix available because one of it's nested child dependencies fixes the vulnerability and there is a new version of \`${name}\` available.`
)}`;
results[dirPath] += `${line(
`Update from version \`${currentVersion}\` to \`${latestVersion}\`.\n\n`
)}`;
}
});
});
Expand All @@ -165,24 +196,30 @@ const outputVulnerabilities = (vulnerabilitiesArray, dirPath) => {

// Add Header text
results[dirPath] += `\n${line(
`[${name}_header]: https://img.shields.io/badge/${name}-${badgeColor}?style=for-the-badge \n`,
`[${name}_header]: https://img.shields.io/badge/${name.replace(
/-/g,
"_"
)}-${badgeColor}?style=for-the-badge \n`
)}`;
});
};

// Escape special characters for GitHub Actions.
const escapeForGitHubActions = (str) =>
str.replace(/%/g, '%25').replace(/\n/g, '%0A').replace(/\r/g, '%0D');
str.replace(/%/g, "%25").replace(/\n/g, "%0A").replace(/\r/g, "%0D");

(async () => {
// Create an array of promises for each dirPath.
const promises = directoryPaths.map(async (dirPath) => {
results[dirPath] = '';
results[dirPath] = "";

// Read the vulnerabilities file.
const vulnerabilitiesArray = vulnerabilities[dirPath].vulnerabilities ?? [];
const metadata = vulnerabilities[dirPath].metadata ?? { vulnerabilities: 0 };
const { info, low, moderate, high, critical, total } = metadata.vulnerabilities;
const metadata = vulnerabilities[dirPath].metadata ?? {
vulnerabilities: 0,
};
const { info, low, moderate, high, critical, total } =
metadata.vulnerabilities;

// Output title.
results[dirPath] += `${heading(title, 2)}`;
Expand All @@ -192,38 +229,44 @@ const escapeForGitHubActions = (str) =>
const highestSeverity = metadata.highestSeverity;

let highestSeverityColor = blue; // Low or Info
if (highestSeverity === 'critical')
highestSeverityColor = red; // Critical
else if (highestSeverity === 'high')
highestSeverityColor = orange; // High
else if (highestSeverity === 'moderate') highestSeverityColor = yellow; // Moderate
if (highestSeverity === "critical") highestSeverityColor = red; // Critical
else if (highestSeverity === "high") highestSeverityColor = orange; // High
else if (highestSeverity === "moderate") highestSeverityColor = yellow; // Moderate

// Output summary.
if ((total && total === 0) || metadata.vulnerabilities === 0) {
results[dirPath] += `${line(noVulnerabilities)}`;
} else {
// Output highest severity.
results[dirPath] += `${line('![HIGHEST_SEVERITY]\n')}`;
results[dirPath] += `${line("![HIGHEST_SEVERITY]\n")}`;
results[dirPath] += `${line(
`\n[HIGHEST_SEVERITY]: https://img.shields.io/badge/highest_severity-${highestSeverity}-${highestSeverityColor}?style=for-the-badge \n\n`,
`\n[HIGHEST_SEVERITY]: https://img.shields.io/badge/highest_severity-${highestSeverity}-${highestSeverityColor}?style=for-the-badge \n\n`
)}`;

if (info !== 0)
results[dirPath] += `${line(`${attention} - ${info} Info severity vulnerabilities`)}`;
results[dirPath] += `${line(
`${attention} - ${info} \`INFO\` severity vulnerabilities.`
)}`;

if (low !== 0)
results[dirPath] += `${line(`${attention} - ${low} Low severity vulnerabilities`)}`;
results[dirPath] += `${line(
`${attention} - ${low} \`LOW\` severity vulnerabilities.`
)}`;

if (moderate !== 0)
results[dirPath] +=
`${line(`${attention} - ${moderate} Moderate severity vulnerabilities`)}`;
results[dirPath] += `${line(
`${attention} - ${moderate} \`MODERATE\` severity vulnerabilities.`
)}`;

if (high !== 0)
results[dirPath] += `${line(`${attention} - ${high} High severity vulnerabilities`)}`;
results[dirPath] += `${line(
`${attention} - ${high} \`HIGH\` severity vulnerabilities.`
)}`;

if (critical !== 0)
results[dirPath] +=
`${line(`${attention} - ${critical} Critical severity vulnerabilities`)}`;
results[dirPath] += `${line(
`${attention} - ${critical} \`CRITICAL\` severity vulnerabilities.`
)}`;

// Output vulnerabilities and possible fixes.
outputVulnerabilities(vulnerabilitiesArray, dirPath);
Expand Down
6 changes: 3 additions & 3 deletions .github/helpers/npm-audit/is-fix-available.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const semver = require('semver');
const semver = require("semver");

/**
* Determines if a fix is available in a parent package for a vulnerable child dependency.
Expand All @@ -15,8 +15,8 @@ const isFixAvailable = (latestVersion, vulnerableRange, childDepVersion) => {

// Check if the child dependency version is outside the vulnerable range
const resolvedVersion = semver.maxSatisfying(
[latestVersion, childDepVersion.replace(/^[^\d]*/, '')],
childDepVersion,
[latestVersion, childDepVersion.replace(/^[^\d]*/, "")],
childDepVersion
);

return !semver.satisfies(resolvedVersion, vulnerableRange);
Expand Down
2 changes: 1 addition & 1 deletion .github/helpers/npm-audit/parse-npm-vulnerabilities.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fs = require("fs");
// Requires semver dependency to run.

const LOCAL_TEST = false;
const TEST_DIR_PATHS = ["."];
const TEST_DIR_PATHS = ["src/backend"];

/**
* THIS FILE DOES NOT REQUIRE ANY EDITING.
Expand Down
1 change: 1 addition & 0 deletions .github/helpers/npm-audit/run-npm-audit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const parseDetails = (auditData) => {
severity: vuln.severity,
isDirect: vuln.isDirect,
via: vuln.via.map((v) => {
if (typeof v === "string") return v;
return {
title: v?.title,
severity: v?.severity,
Expand Down

0 comments on commit c20c373

Please sign in to comment.