renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
116 lines (115 loc) • 5.93 kB
JavaScript
import { NO_VULNERABILITY_ALERTS } from "../../../constants/error-messages.js";
import { escapeRegExp, regEx } from "../../../util/regex.js";
import { titleCase } from "../../../util/string.js";
import { logger } from "../../../logger/index.js";
import { getHighestVulnerabilitySeverity } from "../../../util/vulnerability/utils.js";
import { get } from "../../../modules/versioning/index.js";
import { MavenDatasource } from "../../../modules/datasource/maven/index.js";
import { GithubTagsDatasource } from "../../../modules/datasource/github-tags/index.js";
import { NugetDatasource } from "../../../modules/datasource/nuget/index.js";
import { getDefaultVersioning } from "../../../modules/datasource/common.js";
import { platform } from "../../../modules/platform/index.js";
import { sanitizeMarkdown } from "../../../util/markdown.js";
import { githubEcosystemToDatasource } from "../../../util/vulnerability/ecosystem.js";
import is from "@sindresorhus/is";
//#region lib/workers/repository/init/vulnerability.ts
async function detectVulnerabilityAlerts(input) {
if (!input?.vulnerabilityAlerts) return input;
if (input.vulnerabilityAlerts.enabled === false) {
logger.debug("Vulnerability alerts are disabled");
return input;
}
const alerts = await platform.getVulnerabilityAlerts?.();
if (!alerts?.length) {
logger.debug("No vulnerability alerts found");
if (input.vulnerabilityAlertsOnly) throw new Error(NO_VULNERABILITY_ALERTS);
return input;
}
const config = { ...input };
const combinedAlerts = {};
for (const alert of alerts) try {
if (alert.dismissed_reason) continue;
if (!alert.security_vulnerability?.first_patched_version) {
logger.debug({ alert }, "Vulnerability alert has no firstPatchedVersion - skipping");
continue;
}
const ecosystem = alert.security_vulnerability.package.ecosystem;
const datasources = githubEcosystemToDatasource[ecosystem];
const depName = alert.security_vulnerability.package.name;
const firstPatchedVersion = alert.security_vulnerability.first_patched_version.identifier;
const advisory = alert.security_advisory;
combinedAlerts[ecosystem] ??= {};
combinedAlerts[ecosystem][depName] ??= { advisories: [] };
const alertDetails = combinedAlerts[ecosystem][depName];
alertDetails.advisories.push(advisory);
alertDetails.severity = getHighestVulnerabilitySeverity({ vulnerabilitySeverity: alertDetails.severity }, { vulnerabilitySeverity: alert.security_vulnerability.severity });
const versioningApi = get(getDefaultVersioning(datasources[0]));
if (versioningApi.isVersion(firstPatchedVersion)) {
if (!alertDetails.firstPatchedVersion || versioningApi.isGreaterThan(firstPatchedVersion, alertDetails.firstPatchedVersion)) alertDetails.firstPatchedVersion = firstPatchedVersion;
} else logger.debug(`Invalid firstPatchedVersion: ${firstPatchedVersion}`);
} catch (err) {
logger.warn({ err }, "Error parsing vulnerability alert");
}
const alertPackageRules = [];
config.remediations = {};
for (const [ecosystem, dependencies] of Object.entries(combinedAlerts)) {
const matchDatasources = githubEcosystemToDatasource[ecosystem];
const primaryDatasource = matchDatasources[0];
for (const [depName, val] of Object.entries(dependencies)) {
if (!val.firstPatchedVersion) continue;
let prBodyNotes = [];
try {
prBodyNotes = val.advisories.flatMap((advisory) => generatePrBodyNotes(advisory));
} catch (err) /* v8 ignore next */ {
logger.warn({ err }, "Error generating vulnerability PR notes");
}
let matchRule = {
matchDatasources,
matchPackageNames: [depName]
};
let matchCurrentVersion = `< ${val.firstPatchedVersion}`;
if (primaryDatasource === MavenDatasource.id || primaryDatasource === NugetDatasource.id) matchCurrentVersion = `(,${val.firstPatchedVersion})`;
else if (primaryDatasource === GithubTagsDatasource.id) matchCurrentVersion = `!/^${escapeRegExp(val.firstPatchedVersion)}$/`;
matchRule = {
...matchRule,
matchCurrentVersion,
vulnerabilityFixVersion: val.firstPatchedVersion,
vulnerabilitySeverity: val.severity,
prBodyNotes,
isVulnerabilityAlert: true,
force: { ...config.vulnerabilityAlerts }
};
alertPackageRules.push(matchRule);
}
}
logger.debug({ alertPackageRules }, "alert package rules");
config.packageRules = (config.packageRules ?? []).concat(alertPackageRules);
return config;
}
function generatePrBodyNotes(advisory) {
const aliases = advisory.identifiers.map((id) => id.value).sort().map((id) => {
if (id.startsWith("CVE-")) return `[${id}](https://nvd.nist.gov/vuln/detail/${id})`;
if (id.startsWith("GHSA-")) return `[${id}](https://github.com/advisories/${id})`;
return id;
});
let content = "\n\n---\n\n### ";
content += `${advisory.summary}\n`;
content += `${aliases.join(" / ")}\n`;
content += `\n<details>\n<summary>More information</summary>\n`;
const details = advisory.description.replace(regEx(/^#{1,4} /gm), "##### ");
content += `#### Details\n${details}\n`;
content += "#### Severity\n";
const { cvss_v4, cvss_v3 } = advisory.cvss_severities ?? {};
const cvss = cvss_v4?.vector_string ? cvss_v4 : cvss_v3;
if (is.number(cvss?.score) && cvss?.vector_string) {
content += `- CVSS Score: ${cvss.score.toFixed(1)} / 10 (${titleCase(advisory.severity)})\n`;
content += `- Vector String: \`${cvss.vector_string}\`\n`;
} else content += `${titleCase(advisory.severity)}\n`;
content += `\n#### References\n${advisory.references?.map((ref) => `- [${ref.url}](${ref.url})`).join("\n") ?? "No references."}`;
content += `\n\nThis data is provided by the [GitHub Advisory Database](https://github.com/advisories/${advisory.ghsa_id}) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).\n`;
content += `</details>`;
return [sanitizeMarkdown(content)];
}
//#endregion
export { detectVulnerabilityAlerts };
//# sourceMappingURL=vulnerability.js.map