Show vulnerabities and license information on the job summary.

Users can see the results that were found directly on the job summary

All the results are grouped by manifest.

It shows a table with vulnerable packages, together with package version,
the vulnerabily info and it's severity.

Shows info about package licenses, which packages have a non allowed license,
and the list of packages with unknown licenses.
This commit is contained in:
Tiago Pascoal
2022-08-01 21:07:02 +01:00
parent 90edb6f286
commit aef949f026
5 changed files with 364 additions and 27 deletions
+14 -7
View File
@@ -4,8 +4,13 @@ This action scans your pull requests for dependency changes and will raise an er
The action is available for all public repositories, as well as private repositories that have GitHub Advanced Security licensed.
You can see the results on the job logs
<img width="854" alt="Screen Shot 2022-03-31 at 1 10 51 PM" src="https://user-images.githubusercontent.com/2161/161042286-b22d7dd3-13cb-458d-8744-ce70ed9bf562.png">
or on the job summary
<img src="https://user-images.githubusercontent.com/7847935/182871416-50332bbb-b279-4621-a136-ca72a4314301.png">
## Installation
@@ -31,6 +36,7 @@ jobs:
Please keep in mind that you need a GitHub Advanced Security license if you're running this action on private repos.
## Configuration
You can pass additional options to the Dependency Review
Action using your workflow file. Here's an example workflow with
all the possible configurations:
@@ -107,13 +113,13 @@ to filter. A couple of examples:
**Important**
* The action will only accept one of the two parameters; an error will
be raised if you provide both.
* By default both parameters are empty (no license checking is
performed).
* We don't have license information for all of your dependents. If we
can't detect the license for a dependency **we will inform you, but the
action won't fail**.
- The action will only accept one of the two parameters; an error will
be raised if you provide both.
- By default both parameters are empty (no license checking is
performed).
- We don't have license information for all of your dependents. If we
can't detect the license for a dependency **we will inform you, but the
action won't fail**.
## Blocking pull requests
@@ -131,4 +137,5 @@ We are grateful for any contributions made to this project.
Please read [CONTRIBUTING.MD](https://github.com/actions/dependency-review-action/blob/main/CONTRIBUTING.md) to get started.
## License
This project is released under the [MIT License](https://github.com/actions/dependency-review-action/blob/main/LICENSE).
Generated Vendored
+166 -9
View File
@@ -161,6 +161,7 @@ const schemas_1 = __nccwpck_require__(8774);
const config_1 = __nccwpck_require__(6373);
const filter_1 = __nccwpck_require__(8752);
const licenses_1 = __nccwpck_require__(3247);
const summary = __importStar(__nccwpck_require__(8608));
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
@@ -181,21 +182,24 @@ function run() {
allow: config.allow_licenses,
deny: config.deny_licenses
};
const filteredChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, changes);
for (const change of filteredChanges) {
if (change.change_type === 'added' &&
change.vulnerabilities !== undefined &&
change.vulnerabilities.length > 0) {
printChangeVulnerabilities(change);
failed = true;
}
}
const addedChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, changes).filter(change => change.change_type === 'added' &&
change.vulnerabilities !== undefined &&
change.vulnerabilities.length > 0);
const [licenseErrors, unknownLicenses] = (0, licenses_1.getDeniedLicenseChanges)(changes, licenses);
summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses);
if (addedChanges.length > 0) {
for (const change of addedChanges) {
printChangeVulnerabilities(change);
}
failed = true;
}
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity || '');
if (licenseErrors.length > 0) {
printLicensesError(licenseErrors);
core.setFailed('Dependency review detected incompatible licenses.');
}
printNullLicenses(unknownLicenses);
summary.addLicensesToSummary(licenseErrors, unknownLicenses, config);
if (failed) {
core.setFailed('Dependency review detected vulnerable packages.');
}
@@ -219,6 +223,9 @@ function run() {
}
}
}
finally {
yield core.summary.write();
}
});
}
function printChangeVulnerabilities(change) {
@@ -326,6 +333,156 @@ exports.ConfigurationOptionsSchema = z
exports.ChangesSchema = z.array(exports.ChangeSchema);
/***/ }),
/***/ 8608:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.addLicensesToSummary = exports.addChangeVulnerabilitiesToSummary = exports.addSummaryToSummary = void 0;
const core = __importStar(__nccwpck_require__(2186));
function addSummaryToSummary(addedPackages, licenseErrors, unknownLicenses) {
core.summary
.addHeading('Dependency Review')
.addRaw(`We found ${addedPackages.length} vulnerable package(s), ${licenseErrors.length} package()s with incompatible licenses, and ${unknownLicenses.length} package(s) with unknown licenses.`);
}
exports.addSummaryToSummary = addSummaryToSummary;
function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
const rows = [];
const manifests = getManifests(addedPackages);
core.summary
.addHeading('Vulnerabilities')
.addQuote(`Vulnerabilites were filtered by mininum severity <strong>${severity}</strong>.`);
if (addedPackages.length === 0) {
core.summary.addQuote('No vulnerabilities found in added packages.');
return;
}
for (const manifest of manifests) {
for (const change of addedPackages.filter(pkg => pkg.manifest === manifest)) {
let previous_package = '';
let previous_version = '';
for (const vuln of change.vulnerabilities) {
const sameAsPrevious = previous_package === change.name &&
previous_version === change.version;
if (!sameAsPrevious) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version,
renderUrl(vuln.advisory_url, vuln.advisory_summary),
vuln.severity
]);
}
else {
rows.push([
{ data: '', colspan: '2' },
renderUrl(vuln.advisory_url, vuln.advisory_summary),
vuln.severity
]);
}
previous_package = change.name;
previous_version = change.version;
}
}
core.summary.addHeading(`<em>${manifest}</em>`, 3).addTable([
[
{ data: 'Name', header: true },
{ data: 'Version', header: true },
{ data: 'Vulnerability', header: true },
{ data: 'Severity', header: true }
],
...rows
]);
}
}
exports.addChangeVulnerabilitiesToSummary = addChangeVulnerabilitiesToSummary;
function addLicensesToSummary(licenseErrors, unknownLicenses, config) {
core.summary.addHeading('Licenses');
if (config.allow_licenses && config.allow_licenses.length > 0) {
core.summary.addQuote(`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`);
}
if (config.deny_licenses && config.deny_licenses.length > 0) {
core.summary.addQuote(`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`);
}
if (licenseErrors.length === 0 && unknownLicenses.length === 0) {
core.summary.addQuote('No license violations detected.');
return;
}
if (licenseErrors.length > 0) {
const rows = [];
const manifests = getManifests(licenseErrors);
core.summary.addHeading('Incompatible Licenses', 3).addSeparator();
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4);
for (const change of licenseErrors.filter(pkg => pkg.manifest === manifest)) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version,
change.license || ''
]);
}
core.summary.addTable([['Package', 'Version', 'License'], ...rows]);
}
}
else {
core.summary.addQuote('No license violations detected.');
}
core.debug(`found ${unknownLicenses.length} unknown licenses`);
if (unknownLicenses.length > 0) {
const rows = [];
const manifests = getManifests(unknownLicenses);
core.debug(`found ${manifests.entries.length} manifests for unknown licenses`);
core.summary.addHeading('Unknown Licenses', 3).addSeparator();
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4);
for (const change of unknownLicenses.filter(pkg => pkg.manifest === manifest)) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version
]);
}
core.summary.addTable([['Package', 'Version'], ...rows]);
}
}
}
exports.addLicensesToSummary = addLicensesToSummary;
function getManifests(changes) {
return new Set(changes.flatMap(c => c.manifest));
}
function renderUrl(url, text) {
if (url) {
return `<a href="${url}">${text}</a>`;
}
else {
return text;
}
}
/***/ }),
/***/ 7351:
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+20 -10
View File
@@ -7,6 +7,7 @@ import {Change, PullRequestSchema, Severity} from './schemas'
import {readConfig} from '../src/config'
import {filterChangesBySeverity} from '../src/filter'
import {getDeniedLicenseChanges} from './licenses'
import * as summary from './summary'
async function run(): Promise<void> {
try {
@@ -36,27 +37,32 @@ async function run(): Promise<void> {
deny: config.deny_licenses
}
const filteredChanges = filterChangesBySeverity(
const addedChanges = filterChangesBySeverity(
minSeverity as Severity,
changes
)
for (const change of filteredChanges) {
if (
).filter(
change =>
change.change_type === 'added' &&
change.vulnerabilities !== undefined &&
change.vulnerabilities.length > 0
) {
printChangeVulnerabilities(change)
failed = true
}
}
)
const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges(
changes,
licenses
)
summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses)
if (addedChanges.length > 0) {
for (const change of addedChanges) {
printChangeVulnerabilities(change)
}
failed = true
}
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity || '')
if (licenseErrors.length > 0) {
printLicensesError(licenseErrors)
core.setFailed('Dependency review detected incompatible licenses.')
@@ -64,6 +70,8 @@ async function run(): Promise<void> {
printNullLicenses(unknownLicenses)
summary.addLicensesToSummary(licenseErrors, unknownLicenses, config)
if (failed) {
core.setFailed('Dependency review detected vulnerable packages.')
} else {
@@ -87,6 +95,8 @@ async function run(): Promise<void> {
core.setFailed('Unexpected fatal error')
}
}
} finally {
await core.summary.write()
}
}
+163
View File
@@ -0,0 +1,163 @@
import * as core from '@actions/core'
import {ConfigurationOptions, Change, Changes} from './schemas'
import {SummaryTableRow} from '@actions/core/lib/summary'
export function addSummaryToSummary(
addedPackages: Changes,
licenseErrors: Change[],
unknownLicenses: Change[]
): void {
core.summary
.addHeading('Dependency Review')
.addRaw(
`We found ${addedPackages.length} vulnerable package(s), ${licenseErrors.length} package()s with incompatible licenses, and ${unknownLicenses.length} package(s) with unknown licenses.`
)
}
export function addChangeVulnerabilitiesToSummary(
addedPackages: Changes,
severity: string
): void {
const rows: SummaryTableRow[] = []
const manifests = getManifests(addedPackages)
core.summary
.addHeading('Vulnerabilities')
.addQuote(
`Vulnerabilites were filtered by mininum severity <strong>${severity}</strong>.`
)
if (addedPackages.length === 0) {
core.summary.addQuote('No vulnerabilities found in added packages.')
return
}
for (const manifest of manifests) {
for (const change of addedPackages.filter(
pkg => pkg.manifest === manifest
)) {
let previous_package = ''
let previous_version = ''
for (const vuln of change.vulnerabilities) {
const sameAsPrevious =
previous_package === change.name &&
previous_version === change.version
if (!sameAsPrevious) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version,
renderUrl(vuln.advisory_url, vuln.advisory_summary),
vuln.severity
])
} else {
rows.push([
{data: '', colspan: '2'},
renderUrl(vuln.advisory_url, vuln.advisory_summary),
vuln.severity
])
}
previous_package = change.name
previous_version = change.version
}
}
core.summary.addHeading(`<em>${manifest}</em>`, 3).addTable([
[
{data: 'Name', header: true},
{data: 'Version', header: true},
{data: 'Vulnerability', header: true},
{data: 'Severity', header: true}
],
...rows
])
}
}
export function addLicensesToSummary(
licenseErrors: Change[],
unknownLicenses: Change[],
config: ConfigurationOptions
): void {
core.summary.addHeading('Licenses')
if (config.allow_licenses && config.allow_licenses.length > 0) {
core.summary.addQuote(
`<strong>Allowed Licenses</strong>: ${config.allow_licenses.join(', ')}`
)
}
if (config.deny_licenses && config.deny_licenses.length > 0) {
core.summary.addQuote(
`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`
)
}
if (licenseErrors.length === 0 && unknownLicenses.length === 0) {
core.summary.addQuote('No license violations detected.')
return
}
if (licenseErrors.length > 0) {
const rows: SummaryTableRow[] = []
const manifests = getManifests(licenseErrors)
core.summary.addHeading('Incompatible Licenses', 3).addSeparator()
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4)
for (const change of licenseErrors.filter(
pkg => pkg.manifest === manifest
)) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version,
change.license || ''
])
}
core.summary.addTable([['Package', 'Version', 'License'], ...rows])
}
} else {
core.summary.addQuote('No license violations detected.')
}
core.debug(`found ${unknownLicenses.length} unknown licenses`)
if (unknownLicenses.length > 0) {
const rows: SummaryTableRow[] = []
const manifests = getManifests(unknownLicenses)
core.debug(
`found ${manifests.entries.length} manifests for unknown licenses`
)
core.summary.addHeading('Unknown Licenses', 3).addSeparator()
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4)
for (const change of unknownLicenses.filter(
pkg => pkg.manifest === manifest
)) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version
])
}
core.summary.addTable([['Package', 'Version'], ...rows])
}
}
}
function getManifests(changes: Changes): Set<string> {
return new Set(changes.flatMap(c => c.manifest))
}
function renderUrl(url: string | null, text: string): string {
if (url) {
return `<a href="${url}">${text}</a>`
} else {
return text
}
}