Compare commits

..
Author SHA1 Message Date
Federico Builes 77a34f96cc Fixing typo in config.test.ts. 2022-10-31 07:46:08 +01:00
Eric Cornelissen f095b5a541 Build and package 2022-10-28 22:25:06 +02:00
Eric Cornelissen f54a1f3b74 Document the license-check & vulnerability-check config options
Include the license-check and vulnerability-check options in the config
documentation in the README.

Also fix a typo in the README ("configuraton" -> "configuration").
2022-10-28 22:23:33 +02:00
Eric Cornelissen 84921e5e4a Simplify Summary summary based on license-check and vulnerability-check
Omit details related to the license check of vulnerability check from
the GitHub Actions Summary's summary if the respective check is disabled
from the configuration.
2022-10-28 22:15:44 +02:00
Eric Cornelissen c5af7ff272 Prevent disabling all checks
Prevent users from disabling both the license and vulnerability check by
checking if both are set to `false` and throwing if that's the case.
2022-10-28 22:08:55 +02:00
Eric Cornelissen 31279d265a Add license-check and vulnerability-check inputs
Add support for two new inputs, named `license-check` and
`vulnerability-check`, to disable the license checks or vulnerability
checks performed by this action. By default, both are enabled.
2022-10-28 22:06:05 +02:00
Federico Builes 2532504548 Merge pull request #310 from actions/cn/node-18
Upgrade to Node 18
2022-10-28 13:46:26 +02:00
cnagadya cc6d251652 Update contributing guide 2022-10-28 10:13:58 +00:00
cnagadya 516e8497ac Add codespace defaults 2022-10-28 10:13:58 +00:00
cnagadya 43c5083e6c Node 18 2022-10-28 10:13:58 +00:00
Federico Builes fa62a0febc Merge pull request #294 from actions/cn/spdx-licenses
Add support for SPDX expressions
2022-10-28 11:27:18 +02:00
cnagadya e897e8ebdd Add dist folder 2022-10-28 09:25:16 +00:00
cnagadyaandFederico Builes 216fafaed5 PR feedback
Co-authored-by: Federico Builes <[email protected]>
2022-10-28 11:23:05 +02:00
cnagadya 0144419c8e Format violations area 2022-10-27 16:43:45 +00:00
cnagadya 7b16bd0b54 Add unvalidated changes to summary 2022-10-27 16:24:30 +00:00
cnagadya 4525a8c091 Format summary findings 2022-10-27 15:41:19 +00:00
cnagadya 72273c9a36 Update dist folder 2022-10-27 15:22:00 +00:00
cnagadya 562a2f3c0a Improve summary formatting 2022-10-27 15:19:32 +00:00
cnagadya c82c183029 Resolve package-lock conflicts 2022-10-27 14:37:08 +00:00
cnagadya 26be1f407e Merge pull request #309 from actions/codespace-actions-dependency-review-action-p79j7j9pxqrh669p
Add unresolved licenses section
2022-10-27 15:43:28 +02:00
Federico Builes d6e28cdfae Merge pull request #307 from actions/dependabot/npm_and_yarn/types/node-16.18.2
Bump @types/node from 16.18.0 to 16.18.2
2022-10-27 07:34:11 +02:00
dependabot[bot] da3d8af3e3 Bump @types/node from 16.18.0 to 16.18.2
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.18.0 to 16.18.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-10-27 01:26:35 +00:00
15 changed files with 249 additions and 123 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"name": "Dependency Review Action",
"image": "mcr.microsoft.com/devcontainers/typescript-node:18",
"postCreateCommand": "npm install",
"remoteUser": "node",
"features": {
"ghcr.io/devcontainers/features/ruby:1": {}
}
}
+2 -2
View File
@@ -23,10 +23,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set Node.js 16.x
- name: Set Node.js 18.x
uses: actions/setup-node@v3
with:
node-version: 16.x
node-version: 18.x
- name: Install dependencies
run: npm ci
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: npm
- name: Install dependencies
run: npm ci --ignore-scripts
@@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: npm
- name: Install dependencies
run: npm ci --ignore-scripts
+3 -2
View File
@@ -38,6 +38,7 @@ _Note_: We don't have any useful tests yet, contributions are welcome!
## Local Development
It is recommended to have atleast [Node 18](https://nodejs.org/en/) installed.
We have a script to scan a given PR for vulnerabilities, this will
help you test your local changes. Make sure to [grab a Personal Access Token (PAT)](https://github.com/settings/tokens) before proceeding (you'll need `repo` permissions for private repos):
@@ -56,11 +57,11 @@ $ GITHUB_TOKEN=my-secret-token ./scripts/scan_pr https://github.com/actions/depe
```
[Configuration options](README.md#configuration-options) can be set by
passing an external YAML [configuration file](README.md#configuration-file) to the
passing an external YAML [configuration file](README.md#configuration-file) to the
`scan_pr` script with the `-c`/`--config-file` option:
```sh
$ GITHUB_TOKEN=<token> ./scripts/scan_pr --config-file my_custom_config.yml <pr_url>
$ GITHUB_TOKEN=<token> ./scripts/scan_pr --config-file my_custom_config.yml <pr_url>
```
## Submitting a pull request
+21 -5
View File
@@ -71,7 +71,7 @@ or by inlining these options in your workflow file.
### config-file
A string representing the path to an external configuraton file. By
A string representing the path to an external configuration file. By
default external configuration files are not used.
**Possible values**: A string representing the absolute path to the
@@ -108,7 +108,7 @@ fail-on-scopes:
Only allow the licenses that comply with the expressions in this list. See "[Licenses](https://github.com/actions/dependency-review-action#licenses)".
**Possible values**: Any valid [spdx license expression](https://spdx.dev/spdx-specification-21-web-version/#h.jxpfx0ykyb60).
**Possible values**: A list of of [SPDX-compliant license identifiers](https://spdx.org/licenses/).
**Inline example**: `allow-licenses: BSD-3-Clause, LGPL-2.1 OR MIT OR BSD-3-Clause`
@@ -117,7 +117,9 @@ Only allow the licenses that comply with the expressions in this list. See "[Lic
```yaml
allow-licenses:
- BSD-3-Clause
- LGPL-2.1 OR MIT OR BSD-3-Clause
- LGPL-2.1
- MIT
- BSD-3-Clause
```
### deny-licenses
@@ -125,7 +127,7 @@ allow-licenses:
Add a custom list of licenses you want to block. See
"[Licenses](https://github.com/actions/dependency-review-action#licenses)".
**Possible values**: Any valid [sodx license expression](https://spdx.dev/spdx-specification-21-web-version/#h.jxpfx0ykyb60).
**Possible values**: Any valid set of [SPDX licenses](https://spdx.org/licenses/).
**Inline example**: `deny-licenses: LGPL-2.0, GPL-2.0+ WITH Bison-exception-2.2`
@@ -153,6 +155,20 @@ allow-ghsas:
- GHSA-efgh-1234-5679
```
### license-check/vulnerability-check
Disable the license checks or vulnerability checks performed by this Action.
You can't disable both checks.
**Possible values**: `true` or `false`
**Example**:
```yaml
license-check: true
vulnerability-check: false
```
### base-ref/head-ref
Provide custom git references for the git base/head when performing
@@ -257,7 +273,7 @@ forbid a subset of licenses. These options are not supported on Enterprise Serve
You can use the [Licenses
API](https://docs.github.com/en/rest/licenses) to see the full list of
supported licenses. Use [spdx license expressions](https://spdx.dev/spdx-specification-21-web-version/#h.jxpfx0ykyb60)
supported licenses. Use [SPDX licenses](https://spdx.org/licenses/)
to filter the licenses. A couple of examples:
```yaml
+42
View File
@@ -18,6 +18,8 @@ function clearInputs() {
'ALLOW-LICENSES',
'DENY-LICENSES',
'ALLOW-GHSAS',
'LICENSE-CHECK',
'VULNERABILITY-CHECK',
'CONFIG-FILE',
'BASE-REF',
'HEAD-REF'
@@ -181,6 +183,46 @@ test('it successfully parses GHSA allowlist', async () => {
])
})
test('it defaults to checking licenses', async () => {
const options = readConfig()
expect(options.license_check).toBe(true)
})
test('it parses the license-check input', async () => {
setInput('license-check', 'false')
let options = readConfig()
expect(options.license_check).toEqual(false)
clearInputs()
setInput('license-check', 'true')
options = readConfig()
expect(options.license_check).toEqual(true)
})
test('it defaults to checking vulnerabilities', async () => {
const options = readConfig()
expect(options.vulnerability_check).toBe(true)
})
test('it parses the vulnerability-check input', async () => {
setInput('vulnerability-check', 'false')
let options = readConfig()
expect(options.vulnerability_check).toEqual(false)
clearInputs()
setInput('vulnerability-check', 'true')
options = readConfig()
expect(options.vulnerability_check).toEqual(true)
})
test('it is not possible to disable both checks', async () => {
setInput('license-check', 'false')
setInput('vulnerability-check', 'false')
expect(() => {
readConfig()
}).toThrow("Can't disable both license-check and vulnerability-check")
})
describe('licenses that are not valid SPDX licenses', () => {
beforeAll(() => {
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(false)
Generated Vendored
+71 -41
View File
@@ -368,12 +368,16 @@ function run() {
allow: config.allow_licenses,
deny: config.deny_licenses
});
summary.addSummaryToSummary(addedChanges, invalidLicenseChanges);
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity);
summary.addLicensesToSummary(invalidLicenseChanges, config);
summary.addSummaryToSummary(config.vulnerability_check ? addedChanges : null, config.license_check ? invalidLicenseChanges : null);
if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity);
printVulnerabilitiesBlock(addedChanges, minSeverity);
}
if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config);
printLicensesBlock(invalidLicenseChanges);
}
summary.addScannedDependencies(changes);
printVulnerabilitiesBlock(addedChanges, minSeverity);
printLicensesBlock(invalidLicenseChanges);
printScannedDependencies(changes);
}
catch (error) {
@@ -423,12 +427,12 @@ function printChangeVulnerabilities(change) {
function printLicensesBlock(invalidLicenseChanges) {
core.group('Licenses', () => __awaiter(this, void 0, void 0, function* () {
if (invalidLicenseChanges.forbidden.length > 0) {
core.info('\nThe following dependencies have incompatible licenses:\n');
core.info('\nThe following dependencies have incompatible licenses:');
printLicensesError(invalidLicenseChanges.forbidden);
core.setFailed('Dependency review detected incompatible licenses.');
}
if (invalidLicenseChanges.unresolved.length > 0) {
core.warning('\nThe validity of the licenses of the dependecies below could not be determine. Ensure that they are valid spdx licenses:\n');
core.warning('\nThe validity of the licenses of the dependencies below could not be determined. Ensure that they are valid SPDX licenses:');
printLicensesError(invalidLicenseChanges.unresolved);
core.setFailed('Dependency review could not detect the validity of all licenses.');
}
@@ -444,7 +448,7 @@ function printNullLicenses(changes) {
if (changes.length === 0) {
return;
}
core.info('\nWe could not detect a license for the following dependencies:\n');
core.info('\nWe could not detect a license for the following dependencies:');
for (const change of changes) {
core.info(`${ansi_styles_1.default.bold.open}${change.manifest} » ${change.name}@${change.version}${ansi_styles_1.default.bold.close}`);
}
@@ -556,6 +560,8 @@ exports.ConfigurationOptionsSchema = z
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]),
allow_ghsas: z.array(z.string()).default([]),
license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true),
config_file: z.string().optional().default('false'),
base_ref: z.string(),
head_ref: z.string()
@@ -600,11 +606,21 @@ exports.addScannedDependencies = exports.addLicensesToSummary = exports.addChang
const core = __importStar(__nccwpck_require__(2186));
const utils_1 = __nccwpck_require__(918);
function addSummaryToSummary(addedPackages, invalidLicenseChanges) {
core.summary.addHeading('Dependency Review').addRaw(`We found:
- ${addedPackages.length} vulnerable package(s),
- ${invalidLicenseChanges.unresolved.length} package(s) with unprocessable licenses,
- ${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses, and
- ${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`);
core.summary
.addHeading('Dependency Review')
.addRaw('We found:')
.addList([
...(addedPackages
? [`${addedPackages.length} vulnerable package(s)`]
: []),
...(invalidLicenseChanges
? [
`${invalidLicenseChanges.unresolved.length} package(s) with invalid SPDX license definitions`,
`${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses`,
`${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`
]
: [])
]);
}
exports.addSummaryToSummary = addSummaryToSummary;
function addChangeVulnerabilitiesToSummary(addedPackages, severity) {
@@ -663,60 +679,55 @@ function addLicensesToSummary(invalidLicenseChanges, config) {
if (config.deny_licenses && config.deny_licenses.length > 0) {
core.summary.addQuote(`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`);
}
if (invalidLicenseChanges.forbidden.length === 0 &&
invalidLicenseChanges.unlicensed.length === 0) {
if (Object.values(invalidLicenseChanges).every(item => item.length === 0)) {
core.summary.addQuote('No license violations detected.');
return;
}
if (invalidLicenseChanges.forbidden.length > 0) {
core.debug(`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`);
core.debug(`${invalidLicenseChanges.unresolved.length} licenses could not be validated`);
printLicenseViolation('Incompatible Licenses', invalidLicenseChanges.forbidden);
printLicenseViolation('Unknown Licenses', invalidLicenseChanges.unlicensed);
printLicenseViolation('Invalid SPDX License Definitions', invalidLicenseChanges.unresolved);
}
exports.addLicensesToSummary = addLicensesToSummary;
function printLicenseViolation(heading, changes) {
core.summary.addHeading(heading, 5).addSeparator();
if (changes.length > 0) {
const rows = [];
const manifests = (0, utils_1.getManifestsSet)(invalidLicenseChanges.forbidden);
core.summary.addHeading('Incompatible Licenses', 3).addSeparator();
const manifests = (0, utils_1.getManifestsSet)(changes);
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4);
for (const change of invalidLicenseChanges.forbidden.filter(pkg => pkg.manifest === manifest)) {
for (const change of changes.filter(pkg => pkg.manifest === manifest)) {
rows.push([
(0, utils_1.renderUrl)(change.source_repository_url, change.name),
change.version,
change.license || ''
formatLicense(change.license)
]);
}
core.summary.addTable([['Package', 'Version', 'License'], ...rows]);
}
}
else {
core.summary.addQuote('No license violations detected.');
}
core.debug(`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`);
if (invalidLicenseChanges.unlicensed.length > 0) {
const rows = [];
const manifests = (0, utils_1.getManifestsSet)(invalidLicenseChanges.unlicensed);
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 invalidLicenseChanges.unlicensed.filter(pkg => pkg.manifest === manifest)) {
rows.push([
(0, utils_1.renderUrl)(change.source_repository_url, change.name),
change.version
]);
}
core.summary.addTable([['Package', 'Version'], ...rows]);
}
core.summary.addQuote(`No ${heading.toLowerCase()} detected.`);
}
}
exports.addLicensesToSummary = addLicensesToSummary;
function formatLicense(license) {
if (license === null || license === 'NOASSERTION') {
return 'Null';
}
return license;
}
function addScannedDependencies(changes) {
const dependencies = (0, utils_1.groupDependenciesByManifest)(changes);
const manifests = dependencies.keys();
const summary = core.summary
.addHeading('Scanned Dependencies')
.addRaw(`We scanned ${dependencies.size} manifest files:`);
.addHeading(`We scanned ${dependencies.size} manifest files:`, 5);
for (const manifest of manifests) {
const deps = dependencies.get(manifest);
if (deps) {
const dependencyNames = deps.map(dependency => `<li>${dependency.name}@${dependency.version}</li>`);
summary.addRaw(`<h3>${manifest}</h3><ul>${dependencyNames.join('')}</ul>`);
summary.addDetails(manifest, `<ul>${dependencyNames.join('')}</ul>`);
}
}
}
@@ -27398,6 +27409,10 @@ const core = __importStar(__nccwpck_require__(2186));
const z = __importStar(__nccwpck_require__(3301));
const schemas_1 = __nccwpck_require__(1129);
const utils_1 = __nccwpck_require__(1314);
function getOptionalBoolean(name) {
const value = core.getInput(name);
return value.length > 0 ? core.getBooleanInput(name) : undefined;
}
function getOptionalInput(name) {
const value = core.getInput(name);
return value.length > 0 ? value : undefined;
@@ -27449,6 +27464,17 @@ function readInlineConfig() {
validateLicenses('allow-licenses', allow_licenses);
validateLicenses('deny-licenses', deny_licenses);
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'));
const license_check = z
.boolean()
.default(true)
.parse(getOptionalBoolean('license-check'));
const vulnerability_check = z
.boolean()
.default(true)
.parse(getOptionalBoolean('vulnerability-check'));
if (license_check === false && vulnerability_check === false) {
throw new Error("Can't disable both license-check and vulnerability-check");
}
const base_ref = getOptionalInput('base-ref');
const head_ref = getOptionalInput('head-ref');
return {
@@ -27457,6 +27483,8 @@ function readInlineConfig() {
allow_licenses,
deny_licenses,
allow_ghsas,
license_check,
vulnerability_check,
base_ref,
head_ref
};
@@ -27633,6 +27661,8 @@ exports.ConfigurationOptionsSchema = z
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]),
allow_ghsas: z.array(z.string()).default([]),
license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true),
config_file: z.string().optional().default('false'),
base_ref: z.string(),
head_ref: z.string()
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+7 -7
View File
@@ -24,9 +24,9 @@
},
"devDependencies": {
"@types/jest": "^27.5.2",
"@types/node": "^16.18.0",
"@types/spdx-expression-parse": "^3.0.2",
"@types/spdx-satisfies": "^0.1.0",
"@types/node": "^16.18.2",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"@vercel/ncc": "^0.34.0",
@@ -1802,9 +1802,9 @@
"integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="
},
"node_modules/@types/node": {
"version": "16.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.0.tgz",
"integrity": "sha512-LqYqYzYvnbCaQfLAwRt0zboqnsViwhZm+vjaMSqcfN36vulAg7Pt0T83q4WZO2YOBw3XdyHi8cQ88H22zmULOA=="
"version": "16.18.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.2.tgz",
"integrity": "sha512-KIGQJyya+opDCFvDSZMNNS899ov5jlNdtN7PypgHWeb8e+5vWISdwTRo/ClsNVlmDihzOGqFyNBDamUs7TQQCA=="
},
"node_modules/@types/prettier": {
"version": "2.7.1",
@@ -9592,9 +9592,9 @@
"integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="
},
"@types/node": {
"version": "16.18.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.0.tgz",
"integrity": "sha512-LqYqYzYvnbCaQfLAwRt0zboqnsViwhZm+vjaMSqcfN36vulAg7Pt0T83q4WZO2YOBw3XdyHi8cQ88H22zmULOA=="
"version": "16.18.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.2.tgz",
"integrity": "sha512-KIGQJyya+opDCFvDSZMNNS899ov5jlNdtN7PypgHWeb8e+5vWISdwTRo/ClsNVlmDihzOGqFyNBDamUs7TQQCA=="
},
"@types/prettier": {
"version": "2.7.1",
+1 -1
View File
@@ -40,7 +40,7 @@
},
"devDependencies": {
"@types/jest": "^27.5.2",
"@types/node": "^16.18.0",
"@types/node": "^16.18.2",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"@types/spdx-expression-parse": "^3.0.2",
+19
View File
@@ -13,6 +13,11 @@ import {isSPDXValid} from './utils'
type licenseKey = 'allow-licenses' | 'deny-licenses'
function getOptionalBoolean(name: string): boolean | undefined {
const value = core.getInput(name)
return value.length > 0 ? core.getBooleanInput(name) : undefined
}
function getOptionalInput(name: string): string | undefined {
const value = core.getInput(name)
return value.length > 0 ? value : undefined
@@ -77,6 +82,18 @@ export function readInlineConfig(): ConfigurationOptions {
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
const license_check = z
.boolean()
.default(true)
.parse(getOptionalBoolean('license-check'))
const vulnerability_check = z
.boolean()
.default(true)
.parse(getOptionalBoolean('vulnerability-check'))
if (license_check === false && vulnerability_check === false) {
throw new Error("Can't disable both license-check and vulnerability-check")
}
const base_ref = getOptionalInput('base-ref')
const head_ref = getOptionalInput('head-ref')
@@ -86,6 +103,8 @@ export function readInlineConfig(): ConfigurationOptions {
allow_licenses,
deny_licenses,
allow_ghsas,
license_check,
vulnerability_check,
base_ref,
head_ref
}
-1
View File
@@ -1,7 +1,6 @@
import * as core from '@actions/core'
import spdxSatisfies from 'spdx-satisfies'
import {Octokit} from 'octokit'
import {Change, Changes} from './schemas'
import {isSPDXValid} from './utils'
+17 -9
View File
@@ -53,13 +53,21 @@ async function run(): Promise<void> {
}
)
summary.addSummaryToSummary(addedChanges, invalidLicenseChanges)
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity)
summary.addLicensesToSummary(invalidLicenseChanges, config)
summary.addScannedDependencies(changes)
summary.addSummaryToSummary(
config.vulnerability_check ? addedChanges : null,
config.license_check ? invalidLicenseChanges : null
)
printVulnerabilitiesBlock(addedChanges, minSeverity)
printLicensesBlock(invalidLicenseChanges)
if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity)
printVulnerabilitiesBlock(addedChanges, minSeverity)
}
if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config)
printLicensesBlock(invalidLicenseChanges)
}
summary.addScannedDependencies(changes)
printScannedDependencies(changes)
} catch (error) {
if (error instanceof RequestError && error.status === 404) {
@@ -123,13 +131,13 @@ function printLicensesBlock(
): void {
core.group('Licenses', async () => {
if (invalidLicenseChanges.forbidden.length > 0) {
core.info('\nThe following dependencies have incompatible licenses:\n')
core.info('\nThe following dependencies have incompatible licenses:')
printLicensesError(invalidLicenseChanges.forbidden)
core.setFailed('Dependency review detected incompatible licenses.')
}
if (invalidLicenseChanges.unresolved.length > 0) {
core.warning(
'\nThe validity of the licenses of the dependecies below could not be determine. Ensure that they are valid spdx licenses:\n'
'\nThe validity of the licenses of the dependencies below could not be determined. Ensure that they are valid SPDX licenses:'
)
printLicensesError(invalidLicenseChanges.unresolved)
core.setFailed(
@@ -153,7 +161,7 @@ function printNullLicenses(changes: Changes): void {
return
}
core.info('\nWe could not detect a license for the following dependencies:\n')
core.info('\nWe could not detect a license for the following dependencies:')
for (const change of changes) {
core.info(
`${styles.bold.open}${change.manifest} » ${change.name}@${change.version}${styles.bold.close}`
+2
View File
@@ -41,6 +41,8 @@ export const ConfigurationOptionsSchema = z
allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]),
allow_ghsas: z.array(z.string()).default([]),
license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true),
config_file: z.string().optional().default('false'),
base_ref: z.string(),
head_ref: z.string()
+52 -52
View File
@@ -4,16 +4,24 @@ import {SummaryTableRow} from '@actions/core/lib/summary'
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
export function addSummaryToSummary(
addedPackages: Changes,
invalidLicenseChanges: Record<string, Changes>
addedPackages: Changes | null,
invalidLicenseChanges: Record<string, Changes> | null
): void {
core.summary.addHeading('Dependency Review').addRaw(
`We found:
- ${addedPackages.length} vulnerable package(s),
- ${invalidLicenseChanges.unresolved.length} package(s) with unprocessable licenses,
- ${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses, and
- ${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`
)
core.summary
.addHeading('Dependency Review')
.addRaw('We found:')
.addList([
...(addedPackages
? [`${addedPackages.length} vulnerable package(s)`]
: []),
...(invalidLicenseChanges
? [
`${invalidLicenseChanges.unresolved.length} package(s) with invalid SPDX license definitions`,
`${invalidLicenseChanges.forbidden.length} package(s) with incompatible licenses`,
`${invalidLicenseChanges.unlicensed.length} package(s) with unknown licenses.`
]
: [])
])
}
export function addChangeVulnerabilitiesToSummary(
@@ -93,76 +101,68 @@ export function addLicensesToSummary(
)
}
if (
invalidLicenseChanges.forbidden.length === 0 &&
invalidLicenseChanges.unlicensed.length === 0
) {
if (Object.values(invalidLicenseChanges).every(item => item.length === 0)) {
core.summary.addQuote('No license violations detected.')
return
}
if (invalidLicenseChanges.forbidden.length > 0) {
const rows: SummaryTableRow[] = []
const manifests = getManifestsSet(invalidLicenseChanges.forbidden)
core.summary.addHeading('Incompatible Licenses', 3).addSeparator()
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4)
for (const change of invalidLicenseChanges.forbidden.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 ${invalidLicenseChanges.unlicensed.length} unknown licenses`
)
if (invalidLicenseChanges.unlicensed.length > 0) {
core.debug(
`${invalidLicenseChanges.unresolved.length} licenses could not be validated`
)
printLicenseViolation(
'Incompatible Licenses',
invalidLicenseChanges.forbidden
)
printLicenseViolation('Unknown Licenses', invalidLicenseChanges.unlicensed)
printLicenseViolation(
'Invalid SPDX License Definitions',
invalidLicenseChanges.unresolved
)
}
function printLicenseViolation(heading: string, changes: Changes): void {
core.summary.addHeading(heading, 5).addSeparator()
if (changes.length > 0) {
const rows: SummaryTableRow[] = []
const manifests = getManifestsSet(invalidLicenseChanges.unlicensed)
core.debug(
`found ${manifests.entries.length} manifests for unknown licenses`
)
core.summary.addHeading('Unknown Licenses', 3).addSeparator()
const manifests = getManifestsSet(changes)
for (const manifest of manifests) {
core.summary.addHeading(`<em>${manifest}</em>`, 4)
for (const change of invalidLicenseChanges.unlicensed.filter(
pkg => pkg.manifest === manifest
)) {
for (const change of changes.filter(pkg => pkg.manifest === manifest)) {
rows.push([
renderUrl(change.source_repository_url, change.name),
change.version
change.version,
formatLicense(change.license)
])
}
core.summary.addTable([['Package', 'Version'], ...rows])
core.summary.addTable([['Package', 'Version', 'License'], ...rows])
}
} else {
core.summary.addQuote(`No ${heading.toLowerCase()} detected.`)
}
}
function formatLicense(license: string | null): string {
if (license === null || license === 'NOASSERTION') {
return 'Null'
}
return license
}
export function addScannedDependencies(changes: Changes): void {
const dependencies = groupDependenciesByManifest(changes)
const manifests = dependencies.keys()
const summary = core.summary
.addHeading('Scanned Dependencies')
.addRaw(`We scanned ${dependencies.size} manifest files:`)
.addHeading(`We scanned ${dependencies.size} manifest files:`, 5)
for (const manifest of manifests) {
const deps = dependencies.get(manifest)
@@ -170,7 +170,7 @@ export function addScannedDependencies(changes: Changes): void {
const dependencyNames = deps.map(
dependency => `<li>${dependency.name}@${dependency.version}</li>`
)
summary.addRaw(`<h3>${manifest}</h3><ul>${dependencyNames.join('')}</ul>`)
summary.addDetails(manifest, `<ul>${dependencyNames.join('')}</ul>`)
}
}
}