Compare commits

...

5 Commits

Author SHA1 Message Date
Federico Builes 20f8e76960 Merge branch 'main' into add-summary
# Conflicts:
#	README.md
#	dist/index.js
#	dist/index.js.map
#	src/main.ts
2022-08-18 15:48:03 +02:00
Tiago Pascoal 47f663b6ee update dist after fixing typo 2022-08-07 11:39:10 +00:00
Tiago Pascoal dfcdb87cb3 Fix typo
Co-authored-by: Eric Cornelissen <ericornelissen@gmail.com>
2022-08-07 12:36:42 +01:00
Tiago Pascoal 79f5aede88 Merge branch 'main' into add-summary 2022-08-04 16:59:57 +01:00
Tiago Pascoal aef949f026 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.
2022-08-04 15:35:07 +00:00
5 changed files with 356 additions and 31 deletions
+6 -11
View File
@@ -5,8 +5,14 @@ raise an error if any vulnerabilities or invalid licenses are being introduced.
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
**Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.**
@@ -144,7 +150,6 @@ to filter. A couple of examples:
**Important**
<<<<<<< HEAD
- 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
@@ -152,16 +157,6 @@ to filter. A couple of examples:
- 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**.
=======
* Checking for licenses is not supported on GHES.
* 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**.
>>>>>>> main
## Blocking pull requests
Generated Vendored
+166 -9
View File
@@ -201,6 +201,7 @@ const request_error_1 = __nccwpck_require__(537);
const config_1 = __nccwpck_require__(6373);
const filter_1 = __nccwpck_require__(8752);
const licenses_1 = __nccwpck_require__(3247);
const summary = __importStar(__nccwpck_require__(8608));
const git_refs_1 = __nccwpck_require__(1086);
function run() {
return __awaiter(this, void 0, void 0, function* () {
@@ -219,21 +220,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.');
}
@@ -257,6 +261,9 @@ function run() {
}
}
}
finally {
yield core.summary.write();
}
});
}
function printChangeVulnerabilities(change) {
@@ -366,6 +373,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, Severity} from './schemas'
import {readConfig} from '../src/config'
import {filterChangesBySeverity} from '../src/filter'
import {getDeniedLicenseChanges} from './licenses'
import * as summary from './summary'
import {getRefs} from './git-refs'
async function run(): Promise<void> {
@@ -29,27 +30,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.')
@@ -57,6 +63,8 @@ async function run(): Promise<void> {
printNullLicenses(unknownLicenses)
summary.addLicensesToSummary(licenseErrors, unknownLicenses, config)
if (failed) {
core.setFailed('Dependency review detected vulnerable packages.')
} else {
@@ -80,6 +88,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
}
}