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 | ||
|
|
022ea02fbb | ||
|
|
d6e28cdfae | ||
|
|
da3d8af3e3 | ||
|
|
52fa73c086 | ||
|
|
3baea959cf | ||
|
|
782c57b17e | ||
|
|
ac5ed8754d | ||
|
|
024a5a6342 | ||
|
|
b2fc686406 | ||
|
|
4ec1d46392 | ||
|
|
cfef8bfe29 | ||
|
|
bd43b8d1e2 | ||
|
|
fced408b87 | ||
|
|
65f9f50468 | ||
|
|
a393c83ce5 | ||
|
|
56163c5659 | ||
|
|
5dc2e6e4bb | ||
|
|
0efb1d1d84 | ||
|
|
d4f6425aa4 | ||
|
|
49a61bd9bd | ||
|
|
06c01e11e8 | ||
|
|
4538b29c27 | ||
|
|
4153ec555a | ||
|
|
7c8d0843f9 | ||
|
|
fc00198e43 | ||
|
|
80e573b784 | ||
|
|
b5c3d1e723 | ||
|
|
7fd272118a | ||
|
|
3c9a31f5a0 | ||
|
|
d8fba3fdc1 | ||
|
|
e805dd89e8 | ||
|
|
32276cb73d | ||
|
|
fe226ac019 | ||
|
|
b759175bdb | ||
|
|
6af054f363 | ||
|
|
6f32cb0afd | ||
|
|
2791afab72 | ||
|
|
a8b5c8c24e | ||
|
|
12a250de95 | ||
|
|
917e5af203 | ||
|
|
ba6dba6225 | ||
|
|
63154658bc | ||
|
|
f84c5813e5 | ||
|
|
228a6404a2 | ||
|
|
c84947f64b | ||
|
|
71dbf10e60 | ||
|
|
f9deefc2e9 | ||
|
|
0e5d083be1 | ||
|
|
2f428eec67 | ||
|
|
dff2fdff0f | ||
|
|
12a171cf96 | ||
|
|
3156cf8998 |
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
fail-on-severity: low
|
||||
@@ -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
|
||||
|
||||
+25
-16
@@ -1,4 +1,5 @@
|
||||
# Contributing
|
||||
|
||||
[fork]: https://github.com/actions/dependency-review-action/fork
|
||||
[pr]: https://github.com/actions/dependency-review-action/compare
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
@@ -9,7 +10,6 @@ Contributions to this project are
|
||||
[released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license)
|
||||
to the public under the [project's open source license](LICENSE).
|
||||
|
||||
|
||||
Please note that this project is released with a [Contributor Code of
|
||||
Conduct][code-of-conduct]. By participating in this project you agree
|
||||
to abide by its terms.
|
||||
@@ -20,7 +20,6 @@ This Action makes an authenticated query to the Dependency Graph Diff
|
||||
API endpoint (`GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}`)
|
||||
to find out the set of added and removed dependencies for each manifest.
|
||||
|
||||
|
||||
### Bootstrapping the project
|
||||
|
||||
```
|
||||
@@ -35,10 +34,11 @@ npm install
|
||||
npm run test
|
||||
```
|
||||
|
||||
*Note*: We don't have any useful tests yet, contributions are welcome!
|
||||
_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,16 +56,24 @@ Like this:
|
||||
$ GITHUB_TOKEN=my-secret-token ./scripts/scan_pr https://github.com/actions/dependency-review-action/pull/3
|
||||
```
|
||||
|
||||
[Configuration options](README.md#configuration-options) can be set by
|
||||
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>
|
||||
```
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
0. [Fork][fork] and clone the repository
|
||||
0. Configure and install the dependencies: `npm install`
|
||||
0. Make sure the tests pass on your machine: `npm run test`
|
||||
0. Create a new branch: `git checkout -b my-branch-name`
|
||||
0. Make your change, add tests, and make sure the tests still pass
|
||||
0. Make sure to build and package before pushing: `npm run build && npm run package`
|
||||
0. Push to your fork and [submit a pull request][pr]
|
||||
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
1. Configure and install the dependencies: `npm install`
|
||||
2. Make sure the tests pass on your machine: `npm run test`
|
||||
3. Create a new branch: `git checkout -b my-branch-name`
|
||||
4. Make your change, add tests, and make sure the tests still pass
|
||||
5. Make sure to build and package before pushing: `npm run build && npm run package`
|
||||
6. Push to your fork and [submit a pull request][pr]
|
||||
7. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
@@ -77,21 +85,21 @@ Here are a few things you can do that will increase the likelihood of your pull
|
||||
|
||||
1. Update the version number in [package.json](https://github.com/actions/dependency-review-action/blob/main/package.json).
|
||||
1. Go to [Draft a new
|
||||
release](https://github.com/actions/dependency-review-action/releases/new)
|
||||
in the Releases page.
|
||||
2. Make sure that the `Publish this Action to the GitHub Marketplace`
|
||||
checkbox is enabled
|
||||
release](https://github.com/actions/dependency-review-action/releases/new)
|
||||
in the Releases page.
|
||||
1. Make sure that the `Publish this Action to the GitHub Marketplace`
|
||||
checkbox is enabled
|
||||
|
||||
<img width="481" alt="Screenshot 2022-06-15 at 12 08 19" src="https://user-images.githubusercontent.com/2161/173822484-4b60d8b4-c674-4bff-b5ff-b0c4a3650ab7.png">
|
||||
|
||||
3. Click "Choose a tag" and then "Create new tag", where the tag name
|
||||
will be your version prefixed by a `v` (e.g. `v1.2.3`).
|
||||
will be your version prefixed by a `v` (e.g. `v1.2.3`).
|
||||
4. Use a version number for the release title (e.g. "1.2.3").
|
||||
|
||||
<img width="700" alt="Screenshot 2022-06-15 at 12 08 36" src="https://user-images.githubusercontent.com/2161/173822548-33ab3432-d679-4dc1-adf8-b50fdaf47de3.png">
|
||||
|
||||
5. Add your release notes. If this is a major version make sure to
|
||||
include a small description of the biggest changes in the new version.
|
||||
include a small description of the biggest changes in the new version.
|
||||
6. Click "Publish Release".
|
||||
|
||||
You now have a tag and release using the semver version you used
|
||||
@@ -102,6 +110,7 @@ automatically getting all the
|
||||
minor/patch updates.
|
||||
|
||||
To do this just checkout `main`, force-create a new annotated tag, and push it:
|
||||
|
||||
```
|
||||
git tag -fa v2 -m "Updating v2 to 2.3.4"
|
||||
git push origin v2 --force
|
||||
|
||||
@@ -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
|
||||
@@ -106,19 +106,20 @@ fail-on-scopes:
|
||||
|
||||
### allow-licenses
|
||||
|
||||
Only allow the licenses in this list. See "[Licenses](https://github.com/actions/dependency-review-action#licenses)".
|
||||
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 `spdx_id` value(s) from
|
||||
https://docs.github.com/en/rest/licenses.
|
||||
**Possible values**: A list of of [SPDX-compliant license identifiers](https://spdx.org/licenses/).
|
||||
|
||||
**Inline example**: `allow-licenses: BSD-3-Clause, MIT`
|
||||
**Inline example**: `allow-licenses: BSD-3-Clause, LGPL-2.1 OR MIT OR BSD-3-Clause`
|
||||
|
||||
**YAML example**:
|
||||
|
||||
```yaml
|
||||
allow-licenses:
|
||||
- BSD-3-Clause
|
||||
- LGPL-2.1
|
||||
- MIT
|
||||
- BSD-3-Clause
|
||||
```
|
||||
|
||||
### deny-licenses
|
||||
@@ -126,17 +127,16 @@ 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 `spdx_id` value(s) from
|
||||
https://docs.github.com/en/rest/licenses.
|
||||
**Possible values**: Any valid set of [SPDX licenses](https://spdx.org/licenses/).
|
||||
|
||||
**Inline example**: `deny-licenses: LGPL-2.0, BSD-2-Clause`
|
||||
**Inline example**: `deny-licenses: LGPL-2.0, GPL-2.0+ WITH Bison-exception-2.2`
|
||||
|
||||
**YAML example**:
|
||||
|
||||
```yaml
|
||||
deny-licenses:
|
||||
- LGPL-2.0
|
||||
- BSD-2-Clause
|
||||
- GPL-2.0+ WITH Bison-exception-2.2
|
||||
```
|
||||
|
||||
### allow-ghsas
|
||||
@@ -155,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
|
||||
@@ -259,8 +273,8 @@ 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 the `spdx_id` field for every license you want
|
||||
to filter. A couple of examples:
|
||||
supported licenses. Use [SPDX licenses](https://spdx.org/licenses/)
|
||||
to filter the licenses. A couple of examples:
|
||||
|
||||
```yaml
|
||||
# only allow MIT-licensed dependents
|
||||
@@ -275,7 +289,7 @@ to filter. A couple of examples:
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v2
|
||||
with:
|
||||
deny-licenses: Apache-1.1, Apache-2.0
|
||||
deny-licenses: Apache-1.1+
|
||||
```
|
||||
|
||||
### Considerations
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {expect, test, beforeEach} from '@jest/globals'
|
||||
import {readConfig, readConfigFile} from '../src/config'
|
||||
import {getRefs} from '../src/git-refs'
|
||||
import * as Utils from '../src/utils'
|
||||
|
||||
// GitHub Action inputs come in the form of environment variables
|
||||
// with an INPUT prefix (e.g. INPUT_FAIL-ON-SEVERITY)
|
||||
@@ -17,6 +18,8 @@ function clearInputs() {
|
||||
'ALLOW-LICENSES',
|
||||
'DENY-LICENSES',
|
||||
'ALLOW-GHSAS',
|
||||
'LICENSE-CHECK',
|
||||
'VULNERABILITY-CHECK',
|
||||
'CONFIG-FILE',
|
||||
'BASE-REF',
|
||||
'HEAD-REF'
|
||||
@@ -27,6 +30,10 @@ function clearInputs() {
|
||||
})
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(Utils, 'isSPDXValid').mockReturnValue(true)
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
clearInputs()
|
||||
})
|
||||
@@ -175,3 +182,63 @@ test('it successfully parses GHSA allowlist', async () => {
|
||||
'GHSA-efgh-1234-5679'
|
||||
])
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
test('it raises an error for invalid licenses in allow-licenses', async () => {
|
||||
setInput('allow-licenses', ' BSD, GPL 2')
|
||||
expect(() => {
|
||||
readConfig()
|
||||
}).toThrow('Invalid license(s) in allow-licenses: BSD, GPL 2')
|
||||
})
|
||||
|
||||
test('it raises an error for invalid licenses in deny-licenses', async () => {
|
||||
setInput('deny-licenses', ' BSD, GPL 2')
|
||||
expect(() => {
|
||||
readConfig()
|
||||
}).toThrow('Invalid license(s) in deny-licenses: BSD, GPL 2')
|
||||
})
|
||||
})
|
||||
|
||||
+57
-27
@@ -1,6 +1,7 @@
|
||||
import {expect, jest, test} from '@jest/globals'
|
||||
import {Change, Changes} from '../src/schemas'
|
||||
import {getDeniedLicenseChanges} from '../src/licenses'
|
||||
|
||||
let getInvalidLicenseChanges: Function
|
||||
|
||||
let npmChange: Change = {
|
||||
manifest: 'package.json',
|
||||
@@ -70,65 +71,94 @@ jest.mock('octokit', () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('it fails if a license outside the allow list is found', async () => {
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
|
||||
allow: ['BSD']
|
||||
beforeEach(async () => {
|
||||
jest.resetModules()
|
||||
jest.doMock('spdx-satisfies', () => {
|
||||
// mock spdx-satisfies return value
|
||||
// true for BSD, false for all others
|
||||
return jest.fn((license: string, _: string): boolean => license === 'BSD')
|
||||
})
|
||||
expect(invalidChanges[0]).toBe(npmChange)
|
||||
;({getInvalidLicenseChanges} = require('../src/licenses'))
|
||||
})
|
||||
|
||||
test('it fails if a license inside the deny list is found', async () => {
|
||||
test('it adds license outside the allow list to forbidden changes', async () => {
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
const [invalidChanges] = await getDeniedLicenseChanges(changes, {
|
||||
const {forbidden} = await getInvalidLicenseChanges(changes, {
|
||||
allow: ['BSD']
|
||||
})
|
||||
expect(forbidden[0]).toBe(npmChange)
|
||||
expect(forbidden.length).toEqual(1)
|
||||
})
|
||||
|
||||
test('it adds license inside the deny list to forbidden changes', async () => {
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
const {forbidden} = await getInvalidLicenseChanges(changes, {
|
||||
deny: ['BSD']
|
||||
})
|
||||
expect(invalidChanges[0]).toBe(rubyChange)
|
||||
expect(forbidden[0]).toBe(rubyChange)
|
||||
expect(forbidden.length).toEqual(1)
|
||||
})
|
||||
|
||||
// This is more of a "here's a behavior that might be surprising" than an actual
|
||||
// thing we want in the system. Please remove this test after refactoring.
|
||||
test('it fails all license checks when allow is provided an empty array', async () => {
|
||||
test('it adds all licenses to forbidden changes when allow is provided an empty array', async () => {
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
let [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
|
||||
let {forbidden} = await getInvalidLicenseChanges(changes, {
|
||||
allow: [],
|
||||
deny: ['BSD']
|
||||
})
|
||||
expect(invalidChanges.length).toBe(2)
|
||||
expect(forbidden.length).toBe(2)
|
||||
})
|
||||
|
||||
test('it does not fail if a license outside the allow list is found in removed changes', async () => {
|
||||
test('it does not add license outside the allow list to forbidden changes if it is in removed changes', async () => {
|
||||
const changes: Changes = [
|
||||
{...npmChange, change_type: 'removed'},
|
||||
{...rubyChange, change_type: 'removed'}
|
||||
]
|
||||
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
|
||||
const {forbidden} = await getInvalidLicenseChanges(changes, {
|
||||
allow: ['BSD']
|
||||
})
|
||||
expect(invalidChanges).toStrictEqual([])
|
||||
expect(forbidden).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('it does not fail if a license inside the deny list is found in removed changes', async () => {
|
||||
test('it does not add license inside the deny list to forbidden changes if it is in removed changes', async () => {
|
||||
const changes: Changes = [
|
||||
{...npmChange, change_type: 'removed'},
|
||||
{...rubyChange, change_type: 'removed'}
|
||||
]
|
||||
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
|
||||
const {forbidden} = await getInvalidLicenseChanges(changes, {
|
||||
deny: ['BSD']
|
||||
})
|
||||
expect(invalidChanges).toStrictEqual([])
|
||||
expect(forbidden).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('it fails if a license outside the allow list is found in both of added and removed changes', async () => {
|
||||
test('it adds license outside the allow list to forbidden changes if it is in both added and removed changes', async () => {
|
||||
const changes: Changes = [
|
||||
{...npmChange, change_type: 'removed'},
|
||||
npmChange,
|
||||
{...rubyChange, change_type: 'removed'}
|
||||
]
|
||||
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
|
||||
const {forbidden} = await getInvalidLicenseChanges(changes, {
|
||||
allow: ['BSD']
|
||||
})
|
||||
expect(invalidChanges).toStrictEqual([npmChange])
|
||||
expect(forbidden).toStrictEqual([npmChange])
|
||||
})
|
||||
|
||||
test('it adds all licenses to unresolved if it is unable to determine the validity', async () => {
|
||||
jest.resetModules() // reset module set in before
|
||||
jest.doMock('spdx-satisfies', () => {
|
||||
return jest.fn((_first: string, _second: string) => {
|
||||
throw new Error('Some Error')
|
||||
})
|
||||
})
|
||||
;({getInvalidLicenseChanges} = require('../src/licenses'))
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
const invalidLicenses = await getInvalidLicenseChanges(changes, {
|
||||
allow: ['BSD']
|
||||
})
|
||||
expect(invalidLicenses.forbidden.length).toEqual(0)
|
||||
expect(invalidLicenses.unlicensed.length).toEqual(0)
|
||||
expect(invalidLicenses.unresolved.length).toEqual(2)
|
||||
})
|
||||
|
||||
describe('GH License API fallback', () => {
|
||||
@@ -138,7 +168,7 @@ describe('GH License API fallback', () => {
|
||||
license: null,
|
||||
source_repository_url: 'http://github.com/some-owner/some-repo'
|
||||
}
|
||||
const [_, unknownChanges] = await getDeniedLicenseChanges(
|
||||
const {unlicensed} = await getInvalidLicenseChanges(
|
||||
[nullLicenseChange, rubyChange],
|
||||
{}
|
||||
)
|
||||
@@ -147,25 +177,25 @@ describe('GH License API fallback', () => {
|
||||
owner: 'some-owner',
|
||||
repo: 'some-repo'
|
||||
})
|
||||
expect(unknownChanges.length).toEqual(0)
|
||||
expect(unlicensed.length).toEqual(0)
|
||||
})
|
||||
|
||||
test('it does not call licenses API endpoint for change with null license and invalid source_repository_url ', async () => {
|
||||
const [_, unknownChanges] = await getDeniedLicenseChanges(
|
||||
const {unlicensed} = await getInvalidLicenseChanges(
|
||||
[{...npmChange, license: null}],
|
||||
{}
|
||||
)
|
||||
expect(mockOctokit.rest.licenses.getForRepo).not.toHaveBeenCalled()
|
||||
expect(unknownChanges.length).toEqual(1)
|
||||
expect(unlicensed.length).toEqual(1)
|
||||
})
|
||||
|
||||
test('it does not call licenses API endpoint if licenses for all changes are present', async () => {
|
||||
const [_, unknownChanges] = await getDeniedLicenseChanges(
|
||||
const {unlicensed} = await getInvalidLicenseChanges(
|
||||
[npmChange, rubyChange],
|
||||
{}
|
||||
)
|
||||
|
||||
expect(mockOctokit.rest.licenses.getForRepo).not.toHaveBeenCalled()
|
||||
expect(unknownChanges.length).toEqual(0)
|
||||
expect(unlicensed.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
+2422
-6451
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+108
@@ -517,6 +517,31 @@ The above copyright notice and this permission notice shall be included in all c
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
array-find-index
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Sindre Sorhus <[email protected]> (sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
before-after-hook
|
||||
Apache-2.0
|
||||
Apache License
|
||||
@@ -1590,6 +1615,89 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
spdx-compare
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015 Kyle E. Mitchell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
spdx-exceptions
|
||||
CC-BY-3.0
|
||||
|
||||
spdx-expression-parse
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015 Kyle E. Mitchell & other authors listed in AUTHORS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
spdx-license-ids
|
||||
CC0-1.0
|
||||
|
||||
spdx-ranges
|
||||
(MIT AND CC-BY-3.0)
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015 Kyle E. Mitchell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
spdx-satisfies
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) spdx-satisfies.js contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
tr46
|
||||
MIT
|
||||
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
moduleFileExtensions: ['js', 'json', 'ts'],
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+3031
-3103
File diff suppressed because it is too large
Load Diff
+13
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dependency-review-action",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"private": true,
|
||||
"description": "A GitHub Action for Dependency Review",
|
||||
"main": "lib/main.js",
|
||||
@@ -27,25 +27,29 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/plugin-retry": "^3.0.9",
|
||||
"@octokit/request-error": "^3.0.1",
|
||||
"@octokit/plugin-retry": "^4.0.3",
|
||||
"@octokit/request-error": "^3.0.2",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"got": "^12.5.2",
|
||||
"nodemon": "^2.0.20",
|
||||
"octokit": "^2.0.7",
|
||||
"octokit": "^2.0.10",
|
||||
"spdx-expression-parse": "^3.0.1",
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"yaml": "^2.1.3",
|
||||
"zod": "^3.19.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.65",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
"@typescript-eslint/parser": "^5.40.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",
|
||||
"@types/spdx-satisfies": "^0.1.0",
|
||||
"@vercel/ncc": "^0.34.0",
|
||||
"esbuild-register": "^3.3.3",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-plugin-github": "^4.4.0",
|
||||
"eslint-plugin-jest": "^27.1.1",
|
||||
"eslint-plugin-jest": "^27.1.3",
|
||||
"jest": "^27.5.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
|
||||
+34
-3
@@ -3,22 +3,52 @@ require 'json'
|
||||
require 'tempfile'
|
||||
require 'open3'
|
||||
require 'bundler/inline'
|
||||
require 'optparse'
|
||||
|
||||
gemfile do
|
||||
source 'https://rubygems.org'
|
||||
gem 'octokit'
|
||||
end
|
||||
|
||||
config_file = nil
|
||||
github_token = ENV["GITHUB_TOKEN"]
|
||||
|
||||
if !github_token || github_token.empty?
|
||||
puts "Please set the GITHUB_TOKEN environment variable"
|
||||
exit -1
|
||||
end
|
||||
|
||||
arg = /(?<repo_nwo>[\w\-]+\/[\w\-]+)\/pull\/(?<pr_number>\d+)/.match(ARGV[0])
|
||||
op = OptionParser.new do |opts|
|
||||
usage = <<EOF
|
||||
Run Dependency Review on a repository.
|
||||
|
||||
\e[1mUsage:\e[22m
|
||||
scripts/scan_pr [options] <pr_url>
|
||||
|
||||
\e[1mExample:\e[22m
|
||||
scripts/scan_pr https://github.com/actions/dependency-review-action/pull/294
|
||||
|
||||
EOF
|
||||
|
||||
opts.banner = usage
|
||||
|
||||
opts.on('-c', '--config-file <FILE>', 'Use an external configuration file') do |cf|
|
||||
config_file = cf
|
||||
end
|
||||
|
||||
opts.on("-h", "--help", "Prints this help") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
op.parse!
|
||||
|
||||
# make sure we have a NWO somewhere in the parameters
|
||||
arg = /(?<repo_nwo>[\w\-]+\/[\w\-]+)\/pull\/(?<pr_number>\d+)/.match(ARGV.join(" "))
|
||||
|
||||
if arg.nil?
|
||||
puts "Usage: script/scan_pr <pr_url>"
|
||||
puts op
|
||||
exit -1
|
||||
end
|
||||
|
||||
@@ -33,7 +63,8 @@ event_file.write("{ \"pull_request\": #{pr.to_h.to_json}}")
|
||||
event_file.close
|
||||
|
||||
action_inputs = {
|
||||
"repo-token" => github_token
|
||||
"repo-token": github_token,
|
||||
"config-file": config_file
|
||||
}
|
||||
|
||||
dev_cmd_env = {
|
||||
|
||||
+44
-1
@@ -9,6 +9,14 @@ import {
|
||||
SeveritySchema,
|
||||
SCOPES
|
||||
} from './schemas'
|
||||
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)
|
||||
@@ -23,6 +31,22 @@ function parseList(list: string | undefined): string[] | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
function validateLicenses(
|
||||
key: licenseKey,
|
||||
licenses: string[] | undefined
|
||||
): void {
|
||||
if (licenses === undefined) {
|
||||
return
|
||||
}
|
||||
const invalid_licenses = licenses.filter(license => !isSPDXValid(license))
|
||||
|
||||
if (invalid_licenses.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid license(s) in ${key}: ${invalid_licenses.join(', ')}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function readConfig(): ConfigurationOptions {
|
||||
const externalConfig = getOptionalInput('config-file')
|
||||
if (externalConfig !== undefined) {
|
||||
@@ -53,9 +77,23 @@ export function readInlineConfig(): ConfigurationOptions {
|
||||
if (allow_licenses !== undefined && deny_licenses !== undefined) {
|
||||
throw new Error("Can't specify both allow_licenses and deny_licenses")
|
||||
}
|
||||
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')
|
||||
|
||||
@@ -65,6 +103,8 @@ export function readInlineConfig(): ConfigurationOptions {
|
||||
allow_licenses,
|
||||
deny_licenses,
|
||||
allow_ghsas,
|
||||
license_check,
|
||||
vulnerability_check,
|
||||
base_ref,
|
||||
head_ref
|
||||
}
|
||||
@@ -80,8 +120,11 @@ export function readConfigFile(filePath: string): ConfigurationOptions {
|
||||
}
|
||||
data = YAML.parse(data)
|
||||
|
||||
// get rid of the ugly dashes from the actions conventions
|
||||
for (const key of Object.keys(data)) {
|
||||
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
||||
validateLicenses(key, data[key])
|
||||
}
|
||||
// get rid of the ugly dashes from the actions conventions
|
||||
if (key.includes('-')) {
|
||||
data[key.replace(/-/g, '_')] = data[key]
|
||||
delete data[key]
|
||||
|
||||
+91
-24
@@ -1,6 +1,8 @@
|
||||
import * as core from '@actions/core'
|
||||
import spdxSatisfies from 'spdx-satisfies'
|
||||
import {Octokit} from 'octokit'
|
||||
import {Change} from './schemas'
|
||||
import {Change, Changes} from './schemas'
|
||||
import {isSPDXValid} from './utils'
|
||||
|
||||
/**
|
||||
* Loops through a list of changes, filtering and returning the
|
||||
@@ -12,48 +14,62 @@ import {Change} from './schemas'
|
||||
* we will ignore the deny list.
|
||||
* @param {Change[]} changes The list of changes to filter.
|
||||
* @param { { allow?: string[], deny?: string[]}} licenses An object with `allow`/`deny` keys, each containing a list of licenses.
|
||||
* @returns {Promise<[Array.<Change>, Array.<Change>]>} A promise to a 2 element tuple. The first element is the list of denied changes and the second one is the list of changes with unknown licenses
|
||||
* @returns {Promise<{Object.<string, Array.<Change>>}} A promise to a Record Object. The keys are strings, unlicensed, unresolved and forbidden. The values are a list of changes
|
||||
*/
|
||||
export async function getDeniedLicenseChanges(
|
||||
export async function getInvalidLicenseChanges(
|
||||
changes: Change[],
|
||||
licenses: {
|
||||
allow?: string[]
|
||||
deny?: string[]
|
||||
}
|
||||
): Promise<[Change[], Change[]]> {
|
||||
): Promise<Record<string, Changes>> {
|
||||
const {allow, deny} = licenses
|
||||
|
||||
const disallowed: Change[] = []
|
||||
const unknown: Change[] = []
|
||||
const groupedChanges = await groupChanges(changes)
|
||||
const licensedChanges: Changes = groupedChanges.licensed
|
||||
|
||||
const consolidatedChanges = changes.some(
|
||||
({source_repository_url, license}) => !license && source_repository_url
|
||||
)
|
||||
? await setGHLicenses(changes)
|
||||
: changes
|
||||
const invalidLicenseChanges: Record<string, Changes> = {
|
||||
unlicensed: groupedChanges.unlicensed,
|
||||
unresolved: [],
|
||||
forbidden: []
|
||||
}
|
||||
|
||||
for (const change of consolidatedChanges) {
|
||||
if (change.change_type === 'removed') {
|
||||
continue
|
||||
}
|
||||
const validityCache = new Map<string, boolean>()
|
||||
|
||||
for (const change of licensedChanges) {
|
||||
const license = change.license
|
||||
|
||||
// should never happen since licensedChanges always have licenses but license is nullable in changes schema
|
||||
if (license === null) {
|
||||
unknown.push(change)
|
||||
continue
|
||||
}
|
||||
if (allow !== undefined) {
|
||||
if (!allow.includes(license)) {
|
||||
disallowed.push(change)
|
||||
}
|
||||
} else if (deny !== undefined) {
|
||||
if (deny.includes(license)) {
|
||||
disallowed.push(change)
|
||||
|
||||
if (license === 'NOASSERTION') {
|
||||
invalidLicenseChanges.unlicensed.push(change)
|
||||
} else if (validityCache.get(license) === undefined) {
|
||||
try {
|
||||
if (allow !== undefined) {
|
||||
const found = allow.find(spdxExpression =>
|
||||
spdxSatisfies(license, spdxExpression)
|
||||
)
|
||||
validityCache.set(license, found !== undefined)
|
||||
} else if (deny !== undefined) {
|
||||
const found = deny.find(spdxExpression =>
|
||||
spdxSatisfies(license, spdxExpression)
|
||||
)
|
||||
validityCache.set(license, found === undefined)
|
||||
}
|
||||
} catch (err) {
|
||||
invalidLicenseChanges.unresolved.push(change)
|
||||
}
|
||||
}
|
||||
|
||||
if (validityCache.get(license) === false) {
|
||||
invalidLicenseChanges.forbidden.push(change)
|
||||
}
|
||||
}
|
||||
|
||||
return [disallowed, unknown]
|
||||
return invalidLicenseChanges
|
||||
}
|
||||
|
||||
const fetchGHLicense = async (
|
||||
@@ -108,3 +124,54 @@ const setGHLicenses = async (changes: Change[]): Promise<Change[]> => {
|
||||
|
||||
return Promise.all(updatedChanges)
|
||||
}
|
||||
// Currently Dependency Graph licenses are truncated to 255 characters
|
||||
// This possibly makes them invalid spdx ids
|
||||
const truncatedDGLicense = (license: string): boolean =>
|
||||
license.length === 255 && !isSPDXValid(license)
|
||||
|
||||
async function groupChanges(
|
||||
changes: Changes
|
||||
): Promise<Record<string, Changes>> {
|
||||
const result: Record<string, Changes> = {
|
||||
licensed: [],
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
const ghChanges = []
|
||||
|
||||
for (const change of changes) {
|
||||
if (change.change_type === 'removed') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (change.license === null) {
|
||||
if (change.source_repository_url !== null) {
|
||||
ghChanges.push(change)
|
||||
} else {
|
||||
result.unlicensed.push(change)
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
truncatedDGLicense(change.license) &&
|
||||
change.source_repository_url !== null
|
||||
) {
|
||||
ghChanges.push(change)
|
||||
} else {
|
||||
result.licensed.push(change)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ghChanges.length > 0) {
|
||||
const ghLicenses = await setGHLicenses(ghChanges)
|
||||
for (const change of ghLicenses) {
|
||||
if (change.license === null) {
|
||||
result.unlicensed.push(change)
|
||||
} else {
|
||||
result.licensed.push(change)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
+34
-22
@@ -10,7 +10,7 @@ import {
|
||||
filterChangesByScopes,
|
||||
filterAllowedAdvisories
|
||||
} from '../src/filter'
|
||||
import {getDeniedLicenseChanges} from './licenses'
|
||||
import {getInvalidLicenseChanges} from './licenses'
|
||||
import * as summary from './summary'
|
||||
import {getRefs} from './git-refs'
|
||||
|
||||
@@ -45,7 +45,7 @@ async function run(): Promise<void> {
|
||||
change.vulnerabilities.length > 0
|
||||
)
|
||||
|
||||
const [licenseErrors, unknownLicenses] = await getDeniedLicenseChanges(
|
||||
const invalidLicenseChanges = await getInvalidLicenseChanges(
|
||||
filteredChanges,
|
||||
{
|
||||
allow: config.allow_licenses,
|
||||
@@ -53,13 +53,21 @@ async function run(): Promise<void> {
|
||||
}
|
||||
)
|
||||
|
||||
summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses)
|
||||
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity)
|
||||
summary.addLicensesToSummary(licenseErrors, unknownLicenses, config)
|
||||
summary.addScannedDependencies(changes)
|
||||
summary.addSummaryToSummary(
|
||||
config.vulnerability_check ? addedChanges : null,
|
||||
config.license_check ? invalidLicenseChanges : null
|
||||
)
|
||||
|
||||
printVulnerabilitiesBlock(addedChanges, minSeverity)
|
||||
printLicensesBlock(licenseErrors, unknownLicenses)
|
||||
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) {
|
||||
@@ -83,7 +91,7 @@ async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
function printVulnerabilitiesBlock(
|
||||
addedChanges: Change[],
|
||||
addedChanges: Changes,
|
||||
minSeverity: Severity
|
||||
): void {
|
||||
let failed = false
|
||||
@@ -119,24 +127,28 @@ function printChangeVulnerabilities(change: Change): void {
|
||||
}
|
||||
|
||||
function printLicensesBlock(
|
||||
licenseErrors: Change[],
|
||||
unknownLicenses: Change[]
|
||||
invalidLicenseChanges: Record<string, Changes>
|
||||
): void {
|
||||
core.group('Licenses', async () => {
|
||||
if (licenseErrors.length > 0) {
|
||||
printLicensesError(licenseErrors)
|
||||
if (invalidLicenseChanges.forbidden.length > 0) {
|
||||
core.info('\nThe following dependencies have incompatible licenses:')
|
||||
printLicensesError(invalidLicenseChanges.forbidden)
|
||||
core.setFailed('Dependency review detected incompatible licenses.')
|
||||
}
|
||||
printNullLicenses(unknownLicenses)
|
||||
if (invalidLicenseChanges.unresolved.length > 0) {
|
||||
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.'
|
||||
)
|
||||
}
|
||||
printNullLicenses(invalidLicenseChanges.unlicensed)
|
||||
})
|
||||
}
|
||||
|
||||
function printLicensesError(changes: Change[]): void {
|
||||
if (changes.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
core.info('\nThe following dependencies have incompatible licenses:\n')
|
||||
function printLicensesError(changes: Changes): void {
|
||||
for (const change of changes) {
|
||||
core.info(
|
||||
`${styles.bold.open}${change.manifest} » ${change.name}@${change.version}${styles.bold.close} – License: ${styles.color.red.open}${change.license}${styles.color.red.close}`
|
||||
@@ -144,12 +156,12 @@ function printLicensesError(changes: Change[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
function printNullLicenses(changes: Change[]): void {
|
||||
function printNullLicenses(changes: Changes): void {
|
||||
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(
|
||||
`${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
-47
@@ -1,18 +1,27 @@
|
||||
import * as core from '@actions/core'
|
||||
import {ConfigurationOptions, Change, Changes} from './schemas'
|
||||
import {ConfigurationOptions, Changes} from './schemas'
|
||||
import {SummaryTableRow} from '@actions/core/lib/summary'
|
||||
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
|
||||
|
||||
export function addSummaryToSummary(
|
||||
addedPackages: Changes,
|
||||
licenseErrors: Change[],
|
||||
unknownLicenses: Change[]
|
||||
addedPackages: Changes | null,
|
||||
invalidLicenseChanges: Record<string, Changes> | null
|
||||
): 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.`
|
||||
)
|
||||
.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(
|
||||
@@ -76,8 +85,7 @@ export function addChangeVulnerabilitiesToSummary(
|
||||
}
|
||||
|
||||
export function addLicensesToSummary(
|
||||
licenseErrors: Change[],
|
||||
unknownLicenses: Change[],
|
||||
invalidLicenseChanges: Record<string, Changes>,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
core.summary.addHeading('Licenses')
|
||||
@@ -93,62 +101,59 @@ export function addLicensesToSummary(
|
||||
)
|
||||
}
|
||||
|
||||
if (licenseErrors.length === 0 && unknownLicenses.length === 0) {
|
||||
if (Object.values(invalidLicenseChanges).every(item => item.length === 0)) {
|
||||
core.summary.addQuote('No license violations detected.')
|
||||
return
|
||||
}
|
||||
|
||||
if (licenseErrors.length > 0) {
|
||||
const rows: SummaryTableRow[] = []
|
||||
const manifests = getManifestsSet(licenseErrors)
|
||||
core.debug(
|
||||
`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`
|
||||
)
|
||||
|
||||
core.summary.addHeading('Incompatible Licenses', 3).addSeparator()
|
||||
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(changes)
|
||||
|
||||
for (const manifest of manifests) {
|
||||
core.summary.addHeading(`<em>${manifest}</em>`, 4)
|
||||
|
||||
for (const change of licenseErrors.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.license || ''
|
||||
formatLicense(change.license)
|
||||
])
|
||||
}
|
||||
|
||||
core.summary.addTable([['Package', 'Version', 'License'], ...rows])
|
||||
}
|
||||
} else {
|
||||
core.summary.addQuote('No license violations detected.')
|
||||
core.summary.addQuote(`No ${heading.toLowerCase()} detected.`)
|
||||
}
|
||||
}
|
||||
|
||||
core.debug(`found ${unknownLicenses.length} unknown licenses`)
|
||||
|
||||
if (unknownLicenses.length > 0) {
|
||||
const rows: SummaryTableRow[] = []
|
||||
const manifests = getManifestsSet(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 formatLicense(license: string | null): string {
|
||||
if (license === null || license === 'NOASSERTION') {
|
||||
return 'Null'
|
||||
}
|
||||
return license
|
||||
}
|
||||
|
||||
export function addScannedDependencies(changes: Changes): void {
|
||||
@@ -157,7 +162,7 @@ export function addScannedDependencies(changes: Changes): void {
|
||||
|
||||
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)
|
||||
@@ -165,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>`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import spdxParse from 'spdx-expression-parse'
|
||||
import {Changes} from './schemas'
|
||||
|
||||
export function groupDependenciesByManifest(
|
||||
@@ -28,3 +29,12 @@ export function renderUrl(url: string | null, text: string): string {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
export function isSPDXValid(license: string): boolean {
|
||||
try {
|
||||
spdxParse(license)
|
||||
return true
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user