Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vulnerabilities): add option to add summary to dashboard #21766

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/usage/configuration-options.md
Expand Up @@ -702,6 +702,16 @@ It is pointless to edit the labels, as Renovate bot restores the labels on each

Configure this option if you prefer a different title for the Dependency Dashboard.

## dependencyDashboardVulnerabilitySummary
secustor marked this conversation as resolved.
Show resolved Hide resolved

Set this function to control if a summary of the CVEs affected should be added to the dependency dashboard.

Available options are:

- `none` ( default ) no section will be added.
- `unresolved` only vulnerabilities will be added if there are no fixes available.
- `all` list all vulnerabilities which affect this repository.
secustor marked this conversation as resolved.
Show resolved Hide resolved

## description

The description field can be used inside any configuration object to add a human-readable description of the object's config purpose.
Expand Down
9 changes: 9 additions & 0 deletions lib/config/options/index.ts
Expand Up @@ -520,6 +520,15 @@ const options: RenovateOptions[] = [
subType: 'string',
default: null,
},
{
name: 'dependencyDashboardVulnerabilitySummary',
description:
'These labels will always be applied on the Dependency Dashboard issue, even when they have been removed manually.',
secustor marked this conversation as resolved.
Show resolved Hide resolved
type: 'string',
allowedValues: ['none', 'all', 'unresolved'],
default: 'none',
experimental: true,
},
{
name: 'configWarningReuseIssue',
description:
Expand Down
1 change: 1 addition & 0 deletions lib/config/types.ts
Expand Up @@ -231,6 +231,7 @@ export interface RenovateConfig
dependencyDashboardHeader?: string;
dependencyDashboardFooter?: string;
dependencyDashboardLabels?: string[];
dependencyDashboardVulnerabilitySummary?: 'none' | 'all' | 'unresolved';
packageFile?: string;
packageRules?: PackageRule[];
postUpdateOptions?: string[];
Expand Down
69 changes: 69 additions & 0 deletions lib/workers/repository/dependency-dashboard.spec.ts
secustor marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -21,7 +21,18 @@ import {
import { regEx } from '../../util/regex';
import type { BranchConfig, BranchUpgradeConfig } from '../types';
import * as dependencyDashboard from './dependency-dashboard';
import { getDashboardMarkdownVulnerabilities } from './dependency-dashboard';
import { PackageFiles } from './package-files';
// import { Vulnerabilities } from './process/vulnerabilities';
//
// jest.mock('./process/vulnerabilities', () => {
// return {
// Vulnerabilities: {
// create: jest.fn(),
// fetchVulnerabilities: jest.fn(),
// },
// };
// });

type PrUpgrade = BranchUpgradeConfig;

Expand Down Expand Up @@ -994,4 +1005,62 @@ describe('workers/repository/dependency-dashboard', () => {
});
});
});

describe('getDashboardMarkdownVulnerabilities()', () => {
const packageFiles = Fixtures.getJson<Record<string, PackageFile[]>>(
'./package-files.json'
);

it('return empty string if summary is empty', async () => {
const result = await getDashboardMarkdownVulnerabilities(
config,
packageFiles
);
expect(result).toBeEmpty();
});

it('return empty string if summary is set to none', async () => {
const result = await getDashboardMarkdownVulnerabilities(
{
...config,
dependencyDashboardVulnerabilitySummary: 'none',
},
packageFiles
);
expect(result).toBeEmpty();
});

it('return no data section if summary is set to all and no vulnerabilities', async () => {
const result = await getDashboardMarkdownVulnerabilities(
{
...config,
dependencyDashboardVulnerabilitySummary: 'all',
},
{}
);
expect(result).toBe(`## Vulnerabilities\n\nNone detected.\n\n`);
});

it('return all vulnerabilities if set to all', async () => {
const result = await getDashboardMarkdownVulnerabilities(
{
...config,
dependencyDashboardVulnerabilitySummary: 'all',
},
packageFiles
);
expect(result).toBe(`## Vulnerabilities\n\n TODO add list`);
});

it('return unresolved vulnerabilities if set to "unresolved"', async () => {
const result = await getDashboardMarkdownVulnerabilities(
{
...config,
dependencyDashboardVulnerabilitySummary: 'unresolved',
},
packageFiles
);
expect(result).toBe(`## Vulnerabilities\n\n TODO add list`);
});
});
});
96 changes: 96 additions & 0 deletions lib/workers/repository/dependency-dashboard.ts
Expand Up @@ -11,6 +11,8 @@
import type { BranchConfig, SelectAllConfig } from '../types';
import { getDepWarningsDashboard } from './errors-warnings';
import { PackageFiles } from './package-files';
import type { Vulnerability } from './process/types';
import { Vulnerabilities } from './process/vulnerabilities';

interface DependencyDashboard {
dependencyDashboardChecks: Record<string, string>;
Expand Down Expand Up @@ -396,6 +398,9 @@
'This repository currently has no open or pending branches.\n\n';
}

// add CVE section
issueBody += await getDashboardMarkdownVulnerabilities(config, packageFiles);

// fit the detected dependencies section
const footer = getFooter(config);
issueBody += PackageFiles.getDashboardMarkdown(
Expand Down Expand Up @@ -453,3 +458,94 @@

return footer;
}

export async function getDashboardMarkdownVulnerabilities(
secustor marked this conversation as resolved.
Show resolved Hide resolved
config: RenovateConfig,
packageFiles: Record<string, PackageFile[]>
): Promise<string> {
let result = '';

if (
is.nullOrUndefined(config.dependencyDashboardVulnerabilitySummary) ||
config.dependencyDashboardVulnerabilitySummary === 'none'
) {
return result;
}

result += '## Vulnerabilities\n\n';
secustor marked this conversation as resolved.
Show resolved Hide resolved

const vulnerabilityFetcher = await Vulnerabilities.create();
const vulnerabilities = await vulnerabilityFetcher.fetchVulnerabilities(

Check warning on line 478 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L478

Added line #L478 was not covered by tests
config,
packageFiles
);
secustor marked this conversation as resolved.
Show resolved Hide resolved

if (vulnerabilities.length === 0) {
secustor marked this conversation as resolved.
Show resolved Hide resolved
result += 'None detected.\n\n';
secustor marked this conversation as resolved.
Show resolved Hide resolved
return result;

Check warning on line 485 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L483-L485

Added lines #L483 - L485 were not covered by tests
}

const unresolvedVulnerabilities = vulnerabilities.filter((value) =>
is.nullOrUndefined(value.fixedVersion)

Check warning on line 489 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L488-L489

Added lines #L488 - L489 were not covered by tests
);

result += `${unresolvedVulnerabilities.length} of a total of ${vulnerabilities.length} CVEs have no fixes in this repository.\n`;

Check warning on line 492 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L492

Added line #L492 was not covered by tests
secustor marked this conversation as resolved.
Show resolved Hide resolved

let renderedVulnerabilities: Vulnerability[];
switch (config.dependencyDashboardVulnerabilitySummary) {

Check warning on line 495 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L495

Added line #L495 was not covered by tests
// filter vulnerabilities to display based on configuration
case 'unresolved':
renderedVulnerabilities = unresolvedVulnerabilities;
break;
default:
renderedVulnerabilities = vulnerabilities;

Check warning on line 501 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L497-L501

Added lines #L497 - L501 were not covered by tests
}

const managerRecords: Record<
string,
Record<string, Record<string, Vulnerability[]>>
> = {};
for (const vulnerability of renderedVulnerabilities) {
const { manager, packageFile } = vulnerability.packageFileConfig;
if (is.nullOrUndefined(managerRecords[manager!])) {
managerRecords[manager!] = {};

Check warning on line 511 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L507-L511

Added lines #L507 - L511 were not covered by tests
}
if (is.nullOrUndefined(managerRecords[manager!][packageFile])) {
managerRecords[manager!][packageFile] = {};

Check warning on line 514 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L513-L514

Added lines #L513 - L514 were not covered by tests
}
if (

Check warning on line 516 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L516

Added line #L516 was not covered by tests
is.nullOrUndefined(
managerRecords[manager!][packageFile][vulnerability.packageName]
)
) {
managerRecords[manager!][packageFile][vulnerability.packageName] = [];

Check warning on line 521 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L521

Added line #L521 was not covered by tests
}
managerRecords[manager!][packageFile][vulnerability.packageName].push(

Check warning on line 523 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L523

Added line #L523 was not covered by tests
vulnerability
);
}

for (const [manager, packageFileRecords] of Object.entries(managerRecords)) {
result += `<details><summary>${manager}</summary>\n<blockquote>\n\n`;
for (const [packageFile, packageNameRecords] of Object.entries(

Check warning on line 530 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L528-L530

Added lines #L528 - L530 were not covered by tests
packageFileRecords
)) {
result += `<details><summary>${packageFile}</summary>\n<blockquote>\n\n`;
for (const [packageName, cves] of Object.entries(packageNameRecords)) {
result += `<details><summary>${packageName}</summary>\n<blockquote>\n\n`;
for (const vul of cves) {
const id = vul.vulnerability.id;
const suffix = is.nonEmptyString(vul.fixedVersion)
? `(fixed in ${vul.fixedVersion})`
: '';
result += `- [${id}](https://osv.dev/vulnerability/${id}) ${suffix}\n`;

Check warning on line 541 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L533-L541

Added lines #L533 - L541 were not covered by tests
}
result += `</blockquote>\n</details>\n\n`;

Check warning on line 543 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L543

Added line #L543 was not covered by tests
}
result += `</blockquote>\n</details>\n\n`;

Check warning on line 545 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L545

Added line #L545 was not covered by tests
}
result += `</blockquote>\n</details>\n\n`;

Check warning on line 547 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L547

Added line #L547 was not covered by tests
}

return result;

Check warning on line 550 in lib/workers/repository/dependency-dashboard.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/dependency-dashboard.ts#L550

Added line #L550 was not covered by tests
}