Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77a34f96cc | ||
|
|
f095b5a541 | ||
|
|
f54a1f3b74 | ||
|
|
84921e5e4a | ||
|
|
c5af7ff272 | ||
|
|
31279d265a | ||
|
|
2532504548 | ||
|
|
cc6d251652 | ||
|
|
516e8497ac | ||
|
|
43c5083e6c | ||
|
|
fa62a0febc | ||
|
|
e897e8ebdd | ||
|
|
216fafaed5 | ||
|
|
0144419c8e | ||
|
|
7b16bd0b54 | ||
|
|
4525a8c091 | ||
|
|
72273c9a36 | ||
|
|
562a2f3c0a | ||
|
|
c82c183029 | ||
|
|
26be1f407e | ||
|
|
d6e28cdfae | ||
|
|
da3d8af3e3 |
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+71
-41
@@ -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()
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Generated
+7
-7
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,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
@@ -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}`
|
||||
|
||||
@@ -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
@@ -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>`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user