UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

116 lines (115 loc) 5.93 kB
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