Compare commits
144 Commits
fix-purl-bug
...
v3.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c5ccdad46 | |||
| 51da82b3f5 | |||
| ca13810d94 | |||
| 8447b31d38 | |||
| 85df23de2c | |||
| 5da6fdbdf9 | |||
| 92837b0ca8 | |||
| 35a52fd146 | |||
| bed9726f78 | |||
| e4d20ce9ad | |||
| bb0ca79fcd | |||
| 07f52ce621 | |||
| c7e8727af4 | |||
| 5e4b90e080 | |||
| 7d0e0f61e8 | |||
| ffaf251c92 | |||
| 726ffc8aa8 | |||
| fcef41f1e0 | |||
| e81e6e582f | |||
| 511675e747 | |||
| dcdbff2f84 | |||
| 29513b58ad | |||
| 347cb43687 | |||
| dfe37bb356 | |||
| ada103783f | |||
| abc80cf6a0 | |||
| 15e91a3980 | |||
| c7d2795410 | |||
| eb07c6d763 | |||
| 4d8fe1e464 | |||
| ee86529290 | |||
| c17dea4c51 | |||
| 727ca667a3 | |||
| 84cd472b61 | |||
| 366fffb717 | |||
| 62a1d2d370 | |||
| 42c2f7100f | |||
| 608049acca | |||
| 32037a1d97 | |||
| f6fff72a32 | |||
| 61ee12c097 | |||
| 7d5babfc38 | |||
| ddb1b9361c | |||
| 7c3177d3c2 | |||
| 31afeba06d | |||
| 7ef37f3853 | |||
| 2e59943778 | |||
| 7d90b4f05f | |||
| 02aa4b66a7 | |||
| fe2a482baf | |||
| ce14e1f894 | |||
| eacc0328b1 | |||
| 98aae180cb | |||
| c280c303e6 | |||
| 1db9156f85 | |||
| c462e2e50e | |||
| 0796abb9cf | |||
| eab07548a7 | |||
| 00f1f5b642 | |||
| 6862f6f65f | |||
| 2f38ecd3fd | |||
| 309d082d5f | |||
| 0e6dece6c7 | |||
| 942409c937 | |||
| 6af66592ad | |||
| d5a7e34e39 | |||
| 328a08ea42 | |||
| 3f88e84ced | |||
| 4463280ae5 | |||
| ae11b24682 | |||
| 902e86c6f5 | |||
| 1e70f06e66 | |||
| 0ea885e7c5 | |||
| 498c8717d3 | |||
| fc8a06c798 | |||
| 8c593e9822 | |||
| 98d4fd7247 | |||
| 0a68c5dfa6 | |||
| f015f96b55 | |||
| 3290c85b0f | |||
| 6b0d5029d1 | |||
| 090b9fe2a1 | |||
| c5e57016d8 | |||
| 8cf6fcb693 | |||
| 9bf5053b8a | |||
| a213934318 | |||
| e301b1bd30 | |||
| c730d72f23 | |||
| a65c766d12 | |||
| 7599c4bc8e | |||
| 0f4e96f7e8 | |||
| a234018432 | |||
| 328eb79003 | |||
| 5bb28e508e | |||
| 11a4a75728 | |||
| c5ac6e1eba | |||
| a3753ba2c6 | |||
| ec3136c4ba | |||
| 38b79e2fbe | |||
| 01a70a14e2 | |||
| d32ada785e | |||
| c61b0a3941 | |||
| 38c1dbdffa | |||
| 84fe280943 | |||
| cf65a75df3 | |||
| 3d532eeb2e | |||
| 2a14180549 | |||
| 3958f9d2c8 | |||
| d561324ef9 | |||
| 5c03808159 | |||
| d3fa764646 | |||
| 1856a6de19 | |||
| 5573b58443 | |||
| c3c3c2e746 | |||
| 9617594ce4 | |||
| c10600ad00 | |||
| 86477f1ea0 | |||
| b6ef88155e | |||
| 1c01b75438 | |||
| 1590d3f795 | |||
| 90de8e47b4 | |||
| 6d3699baca | |||
| 87e767d41f | |||
| 554d5fa52b | |||
| 983fa12c36 | |||
| f6f94a23a4 | |||
| 50954e6a9a | |||
| 66b6f67835 | |||
| 1644401f8d | |||
| 1a326fc7fa | |||
| a82096e68a | |||
| 90d3a94eb7 | |||
| 9dde5949a8 | |||
| cff142b535 | |||
| a4c5ac881a | |||
| d35955ebf6 | |||
| 0342e75832 | |||
| 3daf1c6551 | |||
| 16cbdf9d97 | |||
| 59a0ce5dc2 | |||
| 6cc98d3032 | |||
| 617fd3907e | |||
| 537fc8f28d | |||
| 1360a344cc |
+1
-1
@@ -79,7 +79,7 @@ Here are a few things you can do that will increase the likelihood of your pull
|
||||
|
||||
- Write tests.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
- Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
## Cutting a new release
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ This action is available in Enterprise Server starting with version 3.6. Make su
|
||||
Security](https://docs.github.com/en/enterprise-server@3.6/admin/code-security/managing-github-advanced-security-for-your-enterprise/enabling-github-advanced-security-for-your-enterprise)
|
||||
and [GitHub
|
||||
Connect](https://docs.github.com/en/enterprise-server@3.6/admin/github-actions/managing-access-to-actions-from-githubcom/enabling-automatic-access-to-githubcom-actions-using-github-connect)
|
||||
are enabled.
|
||||
are enabled, and that you have installed the [dependency-review-action](https://github.com/actions/dependency-review-action) on the server.
|
||||
|
||||
You can use the same workflow as above, replacing the `runs-on` value
|
||||
with the label of any of your runners (the default label
|
||||
@@ -66,18 +66,22 @@ jobs:
|
||||
|
||||
Configure this action by either inlining these options in your workflow file, or by using an external configuration file. All configuration options are optional.
|
||||
|
||||
| Option | Usage | Possible values | Default value |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------- |
|
||||
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
|
||||
| `allow-licenses`\* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `deny-licenses`\* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `fail-on-scopes`† | Contains a list of strings of the build environments you want to support. The action will fail on pull requests that introduce vulnerabilities in the scopes that match the list. | `runtime`, `development`, `unknown` | `runtime` |
|
||||
| `allow-ghsas` | Contains a list of GitHub Advisory Database IDs that can be skipped during detection. | Any GHSAs from the [GitHub Advisory Database](https://github.com/advisories) | none |
|
||||
| `license-check` | Enable or disable the license check performed by the action. | `true`, `false` | `true` |
|
||||
| `vulnerability-check` | Enable or disable the vulnerability check performed by the action. | `true`, `false` | `true` |
|
||||
| `allow-dependencies-licenses`\* | Contains a list of packages that will be excluded from license checks. | Any package(s) in [purl](https://github.com/package-url/purl-spec) format | none |
|
||||
| `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none |
|
||||
| `comment-summary-in-pr` | Enable or disable reporting the review summary as a comment in the pull request. If enabled, you must give the workflow or job permission `pull-requests: write`. | `true`, `false` | `false` |
|
||||
| Option | Usage | Possible values | Default value |
|
||||
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
|
||||
| `allow-licenses`\* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `deny-licenses`\* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
|
||||
| `fail-on-scopes`† | Contains a list of strings of the build environments you want to support. The action will fail on pull requests that introduce vulnerabilities in the scopes that match the list. | `runtime`, `development`, `unknown` | `runtime` |
|
||||
| `allow-ghsas` | Contains a list of GitHub Advisory Database IDs that can be skipped during detection. | Any GHSAs from the [GitHub Advisory Database](https://github.com/advisories) | none |
|
||||
| `license-check` | Enable or disable the license check performed by the action. | `true`, `false` | `true` |
|
||||
| `vulnerability-check` | Enable or disable the vulnerability check performed by the action. | `true`, `false` | `true` |
|
||||
| `allow-dependencies-licenses`\* | Contains a list of packages that will be excluded from license checks. | Any package(s) in [purl](https://github.com/package-url/purl-spec) format | none |
|
||||
| `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none |
|
||||
| `comment-summary-in-pr` | Enable or disable reporting the review summary as a comment in the pull request. If enabled, you must give the workflow or job permission `pull-requests: write`. | `always`, `on-failure`, `never` | `never` |
|
||||
| `deny-packages` | Any number of packages to block in a PR. | Package(s) in [purl](https://github.com/package-url/purl-spec) format | empty |
|
||||
| `deny-groups` | Any number of groups (namespaces) to block in a PR. | Namespace(s) in [purl](https://github.com/package-url/purl-spec) format (no package name, no version number) | empty |
|
||||
| `retry-on-snapshot-warnings`\* | Enable or disable retrying the action every 10 seconds while waiting for dependency submission actions to complete. | `true`, `false` | `false` |
|
||||
| `retry-on-snapshot-warnings-timeout`\* | Maximum amount of time (in seconds) to retry the action while waiting for dependency submission actions to complete. | Any positive integer | 120 |
|
||||
|
||||
\*not supported for use with GitHub Enterprise Server
|
||||
|
||||
@@ -144,7 +148,7 @@ For more examples of how to use this action and its configuration options, see t
|
||||
|
||||
### Considerations
|
||||
|
||||
- Checking for licenses is not supported on Enterprise Server.
|
||||
- Checking for licenses is not supported on Enterprise Server as the API does not return license information.
|
||||
- The action will only accept one of the two `license` parameters; an error will be raised if you provide both.
|
||||
- We don't have license information for all of your dependents. If we can't detect the license for a dependency **we will inform you, but the action won't fail**.
|
||||
|
||||
|
||||
@@ -171,3 +171,29 @@ describe('licenses that are not valid SPDX licenses', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test('it parses the comment-summary-in-pr input', async () => {
|
||||
setInput('comment-summary-in-pr', 'true')
|
||||
let config = await readConfig()
|
||||
expect(config.comment_summary_in_pr).toBe('always')
|
||||
|
||||
clearInputs()
|
||||
setInput('comment-summary-in-pr', 'false')
|
||||
config = await readConfig()
|
||||
expect(config.comment_summary_in_pr).toBe('never')
|
||||
|
||||
clearInputs()
|
||||
setInput('comment-summary-in-pr', 'always')
|
||||
config = await readConfig()
|
||||
expect(config.comment_summary_in_pr).toBe('always')
|
||||
|
||||
clearInputs()
|
||||
setInput('comment-summary-in-pr', 'never')
|
||||
config = await readConfig()
|
||||
expect(config.comment_summary_in_pr).toBe('never')
|
||||
|
||||
clearInputs()
|
||||
setInput('comment-summary-in-pr', 'on-failure')
|
||||
config = await readConfig()
|
||||
expect(config.comment_summary_in_pr).toBe('on-failure')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import {expect, jest, test} from '@jest/globals'
|
||||
import {Change, Changes} from '../src/schemas'
|
||||
|
||||
let getDeniedChanges: Function
|
||||
|
||||
const npmChange: Change = {
|
||||
manifest: 'package.json',
|
||||
change_type: 'added',
|
||||
ecosystem: 'npm',
|
||||
name: 'Reeuhq',
|
||||
version: '1.0.2',
|
||||
package_url: 'pkg:npm/reeuhq@1.0.2',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'github.com/some-repo',
|
||||
scope: 'runtime',
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'critical',
|
||||
advisory_ghsa_id: 'first-random_string',
|
||||
advisory_summary: 'very dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const rubyChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'Gemfile.lock',
|
||||
ecosystem: 'rubygems',
|
||||
name: 'actionsomething',
|
||||
version: '3.2.0',
|
||||
package_url: 'pkg:gem/actionsomething@3.2.0',
|
||||
license: 'BSD',
|
||||
source_repository_url: 'github.com/some-repo',
|
||||
scope: 'runtime',
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'moderate',
|
||||
advisory_ghsa_id: 'second-random_string',
|
||||
advisory_summary: 'not so dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
},
|
||||
{
|
||||
severity: 'low',
|
||||
advisory_ghsa_id: 'third-random_string',
|
||||
advisory_summary: 'dont page me',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const pipChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'requirements.txt',
|
||||
ecosystem: 'pip',
|
||||
name: 'package-1',
|
||||
version: '1.1.1',
|
||||
package_url: 'pkg:pip/package-1@1.1.1',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'github.com/some-repo',
|
||||
scope: 'runtime',
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'moderate',
|
||||
advisory_ghsa_id: 'second-random_string',
|
||||
advisory_summary: 'not so dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
},
|
||||
{
|
||||
severity: 'low',
|
||||
advisory_ghsa_id: 'third-random_string',
|
||||
advisory_summary: 'dont page me',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mvnChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'pom.xml',
|
||||
ecosystem: 'maven',
|
||||
name: 'org.apache.logging.log4j:log4j-core',
|
||||
version: '2.15.0',
|
||||
package_url: 'pkg:maven/org.apache.logging.log4j/log4j-core@2.14.7',
|
||||
license: 'Apache-2.0',
|
||||
source_repository_url:
|
||||
'https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core',
|
||||
scope: 'unknown',
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'critical',
|
||||
advisory_ghsa_id: 'second-random_string',
|
||||
advisory_summary: 'not so dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
jest.mock('@actions/core')
|
||||
|
||||
const mockOctokit = {
|
||||
rest: {
|
||||
licenses: {
|
||||
getForRepo: jest
|
||||
.fn()
|
||||
.mockReturnValue({data: {license: {spdx_id: 'AGPL'}}})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('octokit', () => {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
Octokit: class {
|
||||
constructor() {
|
||||
return mockOctokit
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
;({getDeniedChanges} = require('../src/deny'))
|
||||
})
|
||||
|
||||
test('it adds packages in the deny packages list', async () => {
|
||||
const changes: Changes = [npmChange, rubyChange]
|
||||
const deniedChanges = await getDeniedChanges(
|
||||
changes,
|
||||
['pkg:gem/actionsomething'],
|
||||
[]
|
||||
)
|
||||
|
||||
expect(deniedChanges[0]).toBe(rubyChange)
|
||||
expect(deniedChanges.length).toEqual(1)
|
||||
})
|
||||
|
||||
test('it adds packages in the deny group list', async () => {
|
||||
const changes: Changes = [mvnChange, rubyChange]
|
||||
const deniedChanges = await getDeniedChanges(
|
||||
changes,
|
||||
[],
|
||||
['pkg:maven/org.apache.logging.log4j']
|
||||
)
|
||||
|
||||
expect(deniedChanges[0]).toBe(mvnChange)
|
||||
expect(deniedChanges.length).toEqual(1)
|
||||
})
|
||||
|
||||
test('it adds packages outside of the deny lists', async () => {
|
||||
const changes: Changes = [npmChange, pipChange]
|
||||
const deniedChanges = await getDeniedChanges(
|
||||
changes,
|
||||
['pkg:gem/actionsomething'],
|
||||
['pkg:maven:org.apache.logging.log4j']
|
||||
)
|
||||
|
||||
expect(deniedChanges.length).toEqual(0)
|
||||
})
|
||||
@@ -24,7 +24,11 @@ const defaultConfig: ConfigurationOptions = {
|
||||
allow_ghsas: [],
|
||||
allow_licenses: [],
|
||||
deny_licenses: [],
|
||||
comment_summary_in_pr: true
|
||||
deny_packages: [],
|
||||
deny_groups: [],
|
||||
comment_summary_in_pr: true,
|
||||
retry_on_snapshot_warnings: false,
|
||||
retry_on_snapshot_warnings_timeout: 120
|
||||
}
|
||||
|
||||
const changesWithEmptyManifests: Changes = [
|
||||
@@ -70,6 +74,7 @@ test('prints headline as h1', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -81,6 +86,7 @@ test('only includes "No vulnerabilities or license issues found"-message if both
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -90,7 +96,12 @@ test('only includes "No vulnerabilities or license issues found"-message if both
|
||||
|
||||
test('only includes "No vulnerabilities found"-message if "license_check" is set to false and nothing was found', () => {
|
||||
const config = {...defaultConfig, license_check: false}
|
||||
summary.addSummaryToSummary(emptyChanges, emptyInvalidLicenseChanges, config)
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
config
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('✅ No vulnerabilities found.')
|
||||
@@ -98,7 +109,12 @@ test('only includes "No vulnerabilities found"-message if "license_check" is set
|
||||
|
||||
test('only includes "No license issues found"-message if "vulnerability_check" is set to false and nothing was found', () => {
|
||||
const config = {...defaultConfig, vulnerability_check: false}
|
||||
summary.addSummaryToSummary(emptyChanges, emptyInvalidLicenseChanges, config)
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
config
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
|
||||
expect(text).toContain('✅ No license issues found.')
|
||||
@@ -108,6 +124,7 @@ test('groups dependencies with empty manifest paths together', () => {
|
||||
summary.addSummaryToSummary(
|
||||
changesWithEmptyManifests,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
summary.addScannedDependencies(changesWithEmptyManifests)
|
||||
@@ -124,6 +141,7 @@ test('does not include status section if nothing was found', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -142,7 +160,12 @@ test('includes count and status icons for all findings', () => {
|
||||
unlicensed: [createTestChange(), createTestChange(), createTestChange()]
|
||||
}
|
||||
|
||||
summary.addSummaryToSummary(vulnerabilities, licenseIssues, defaultConfig)
|
||||
summary.addSummaryToSummary(
|
||||
vulnerabilities,
|
||||
licenseIssues,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('❌ 2 vulnerable package(s)')
|
||||
@@ -159,6 +182,7 @@ test('uses checkmarks for license issues if only vulnerabilities were found', ()
|
||||
summary.addSummaryToSummary(
|
||||
vulnerabilities,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -178,7 +202,12 @@ test('uses checkmarks for vulnerabilities if only license issues were found', ()
|
||||
unlicensed: []
|
||||
}
|
||||
|
||||
summary.addSummaryToSummary(emptyChanges, licenseIssues, defaultConfig)
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
licenseIssues,
|
||||
emptyChanges,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
const text = core.summary.stringify()
|
||||
expect(text).toContain('✅ 0 vulnerable package(s)')
|
||||
|
||||
+15
-1
@@ -45,8 +45,22 @@ inputs:
|
||||
description: A boolean to determine if vulnerability checks should be performed
|
||||
required: false
|
||||
comment-summary-in-pr:
|
||||
description: A boolean to determine if the report should be posted as a comment in the PR itself. Setting this to true requires you to give the workflow the write permissions for pull-requests
|
||||
description: Determines if the summary is posted as a comment in the PR itself. Setting this to `always` or `on-failure` requires you to give the workflow the write permissions for pull-requests
|
||||
required: false
|
||||
deny-packages:
|
||||
description: A comma-separated list of package URLs to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto")
|
||||
required: false
|
||||
deny-groups:
|
||||
description: A comma-separated list of package URLs for group(s)/namespace(s) to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto")
|
||||
required: false
|
||||
retry-on-snapshot-warnings:
|
||||
description: Whether to retry on snapshot warnings
|
||||
required: false
|
||||
default: false
|
||||
retry-on-snapshot-warnings-timeout:
|
||||
description: Number of seconds to wait before stopping snapshot retries.
|
||||
required: false
|
||||
default: 120
|
||||
runs:
|
||||
using: 'node16'
|
||||
main: 'dist/index.js'
|
||||
|
||||
+4023
-2181
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+70
-6
@@ -83,7 +83,7 @@ jobs:
|
||||
config-file: './.github/dependency-review-config.yml'
|
||||
```
|
||||
|
||||
## Using a configuration file from a external repository
|
||||
## Using a configuration file from an external repository
|
||||
|
||||
The following example will use a configuration file from an external public GitHub repository to configure the action.
|
||||
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
config-file: 'github/octorepo/dependency-review-config.yml@main'
|
||||
```
|
||||
|
||||
## Using a configuration file from a external repository with a personal access token
|
||||
## Using a configuration file from an external repository with a personal access token
|
||||
|
||||
The following example will use a configuration file from an external private GtiHub repository to configure the action.
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
config-file: 'github/octorepo-private/dependency-review-config.yml@main'
|
||||
config-file-token: ${{ secrets.GITHUB_TOKEN }} # or a personal access token
|
||||
external-repo-token: ${{ secrets.GITHUB_TOKEN }} # or a personal access token
|
||||
```
|
||||
|
||||
## Getting the results of the action in the PR as a comment
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
comment-summary-in-pr: true
|
||||
comment-summary-in-pr: always
|
||||
```
|
||||
|
||||
## Exclude dependencies from the license check
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
comment-summary-in-pr: true
|
||||
comment-summary-in-pr: always
|
||||
allow-dependencies-licenses: 'pkg:npm/loadash, pkg:pip/requests'
|
||||
```
|
||||
|
||||
@@ -227,6 +227,70 @@ jobs:
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
comment-summary-in-pr: true
|
||||
comment-summary-in-pr: always
|
||||
license-check: false
|
||||
```
|
||||
|
||||
## Exclude dependencies from their name or groups
|
||||
|
||||
Using the `deny-packages` option you can exclude dependencies by their PURL. You can add multiple values separated by a commas.
|
||||
Using the `deny-groups` option you can exclude dependencies by their group name/namespace. You can add multiple values separated by a comma.
|
||||
|
||||
In this example, we are excluding `pkg:maven/org.apache.logging.log4j:log4j-api` and `pkg:maven/org.apache.logging.log4j/log4j-core` from `maven` and all packages in the group `pkg:maven/com.bazaarvoice.maven`
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
deny-packages: 'pkg:maven/org.apache.logging.log4j/log4j-api,pkg:maven/org.apache.logging.log4j/log4j-core'
|
||||
deny-groups: 'pkg:maven/com.bazaarvoice.jolt'
|
||||
```
|
||||
|
||||
## Waiting for dependency submission jobs to complete
|
||||
|
||||
When possible, this action will [include dependencies submitted through the dependency submission API][DSAPI]. In this case,
|
||||
it's important for the action not to complete until all of the relevant dependencies have been submitted for both the base
|
||||
and head commits.
|
||||
|
||||
When this action runs before one or more of the dependency submission actions, there will be an unequal number of dependency
|
||||
snapshots between the base and head commits. For example, there may be one snapshot available for the tip of `main` and none
|
||||
for the PR branch. In that case, the API response will contain a "snapshot warning" explaining the discrepancy.
|
||||
|
||||
In this example, when the action encounters one of these warnings it will retry every 10 seconds after that for 60 seconds
|
||||
or until there is no warning in the response.
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
retry-on-snapshot-warnings: true
|
||||
retry-on-snapshot-warnings-timeout: 60
|
||||
```
|
||||
|
||||
[DSAPI]: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#best-practices-for-using-the-dependency-review-api-and-the-dependency-submission-api-together
|
||||
|
||||
Generated
+1991
-1220
File diff suppressed because it is too large
Load Diff
+14
-16
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dependency-review-action",
|
||||
"version": "3.0.6",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"description": "A GitHub Action for Dependency Review",
|
||||
"main": "lib/main.js",
|
||||
@@ -27,36 +27,34 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/plugin-retry": "^4.1.1",
|
||||
"@octokit/plugin-retry": "^5.0.4",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"got": "^12.6.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"octokit": "^2.0.16",
|
||||
"got": "^13.0.0",
|
||||
"octokit": "^2.1.0",
|
||||
"packageurl-js": "^1.0.2",
|
||||
"spdx-expression-parse": "^3.0.1",
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"yaml": "^2.3.1",
|
||||
"yaml": "^2.3.2",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.34",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@types/node": "^16.18.48",
|
||||
"@types/spdx-expression-parse": "^3.0.2",
|
||||
"@types/spdx-satisfies": "^0.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
||||
"@typescript-eslint/parser": "^5.59.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"esbuild-register": "^3.4.2",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-plugin-github": "^4.7.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-plugin-github": "^4.8.0",
|
||||
"eslint-plugin-jest": "^27.2.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "2.8.8",
|
||||
"nodemon": "^3.0.1",
|
||||
"prettier": "3.0.2",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* npx ts-node scripts/create_summary.ts
|
||||
*/
|
||||
|
||||
import {Changes, ConfigurationOptions} from '../src/schemas'
|
||||
import {Change, Changes, ConfigurationOptions} from '../src/schemas'
|
||||
import {createTestChange} from '../__tests__/fixtures/create-test-change'
|
||||
import {InvalidLicenseChanges} from '../src/licenses'
|
||||
import * as fs from 'fs'
|
||||
@@ -22,13 +22,17 @@ const defaultConfig: ConfigurationOptions = {
|
||||
allow_ghsas: [],
|
||||
allow_licenses: ['MIT'],
|
||||
deny_licenses: [],
|
||||
deny_packages: [],
|
||||
deny_groups: [],
|
||||
allow_dependencies_licenses: [
|
||||
'pkg:npm/express@4.17.1',
|
||||
'pkg:pip/requests',
|
||||
'pkg:pip/certifi',
|
||||
'pkg:pip/pycrypto@2.6.1'
|
||||
],
|
||||
comment_summary_in_pr: true
|
||||
comment_summary_in_pr: true,
|
||||
retry_on_snapshot_warnings: false,
|
||||
retry_on_snapshot_warnings_timeout: 120
|
||||
}
|
||||
|
||||
const tmpDir = path.resolve(__dirname, '../tmp')
|
||||
@@ -44,6 +48,7 @@ const createNonIssueSummary = async (): Promise<void> => {
|
||||
await createSummary(
|
||||
[],
|
||||
{forbidden: [], unresolved: [], unlicensed: []},
|
||||
[],
|
||||
defaultConfig,
|
||||
'non-issue-summary.md'
|
||||
)
|
||||
@@ -85,16 +90,17 @@ const createFullSummary = async (): Promise<void> => {
|
||||
]
|
||||
}
|
||||
|
||||
await createSummary(changes, licenses, defaultConfig, 'full-summary.md')
|
||||
await createSummary(changes, licenses, [], defaultConfig, 'full-summary.md')
|
||||
}
|
||||
|
||||
async function createSummary(
|
||||
vulnerabilities: Changes,
|
||||
licenseIssues: InvalidLicenseChanges,
|
||||
denied: Change[],
|
||||
config: ConfigurationOptions,
|
||||
fileName: string
|
||||
): Promise<void> {
|
||||
summary.addSummaryToSummary(vulnerabilities, licenseIssues, config)
|
||||
summary.addSummaryToSummary(vulnerabilities, licenseIssues, denied, config)
|
||||
summary.addChangeVulnerabilitiesToSummary(
|
||||
vulnerabilities,
|
||||
config.fail_on_severity
|
||||
|
||||
+2
-2
@@ -74,8 +74,8 @@ async function findCommentByMarker(
|
||||
)
|
||||
|
||||
for await (const {data: comments} of commentsIterator) {
|
||||
const existingComment = comments.find(comment =>
|
||||
comment.body?.includes(commentBodyIncludes)
|
||||
const existingComment = comments.find(
|
||||
comment => comment.body?.includes(commentBodyIncludes)
|
||||
)
|
||||
if (existingComment) return existingComment.id
|
||||
}
|
||||
|
||||
+23
-3
@@ -33,12 +33,20 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
const allow_dependencies_licenses = parseList(
|
||||
getOptionalInput('allow-dependencies-licenses')
|
||||
)
|
||||
const deny_packages = parseList(getOptionalInput('deny-packages'))
|
||||
const deny_groups = parseList(getOptionalInput('deny-groups'))
|
||||
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
|
||||
const license_check = getOptionalBoolean('license-check')
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
||||
const base_ref = getOptionalInput('base-ref')
|
||||
const head_ref = getOptionalInput('head-ref')
|
||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
|
||||
const comment_summary_in_pr = getOptionalInput('comment-summary-in-pr')
|
||||
const retry_on_snapshot_warnings = getOptionalBoolean(
|
||||
'retry-on-snapshot-warnings'
|
||||
)
|
||||
const retry_on_snapshot_warnings_timeout = getOptionalNumber(
|
||||
'retry-on-snapshot-warnings-timeout'
|
||||
)
|
||||
|
||||
validatePURL(allow_dependencies_licenses)
|
||||
validateLicenses('allow-licenses', allow_licenses)
|
||||
@@ -49,13 +57,17 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
fail_on_scopes,
|
||||
allow_licenses,
|
||||
deny_licenses,
|
||||
deny_packages,
|
||||
deny_groups,
|
||||
allow_dependencies_licenses,
|
||||
allow_ghsas,
|
||||
license_check,
|
||||
vulnerability_check,
|
||||
base_ref,
|
||||
head_ref,
|
||||
comment_summary_in_pr
|
||||
comment_summary_in_pr,
|
||||
retry_on_snapshot_warnings,
|
||||
retry_on_snapshot_warnings_timeout
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
@@ -63,6 +75,12 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
)
|
||||
}
|
||||
|
||||
function getOptionalNumber(name: string): number | undefined {
|
||||
const value = core.getInput(name)
|
||||
const parsed = z.string().regex(/^\d+$/).transform(Number).safeParse(value)
|
||||
return parsed.success ? parsed.data : undefined
|
||||
}
|
||||
|
||||
function getOptionalBoolean(name: string): boolean | undefined {
|
||||
const value = core.getInput(name)
|
||||
return value.length > 0 ? core.getBooleanInput(name) : undefined
|
||||
@@ -137,7 +155,9 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
|
||||
'deny-licenses',
|
||||
'fail-on-scopes',
|
||||
'allow-ghsas',
|
||||
'allow-dependencies-licenses'
|
||||
'allow-dependencies-licenses',
|
||||
'deny-packages',
|
||||
'deny-groups'
|
||||
]
|
||||
|
||||
for (const key of Object.keys(data)) {
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import {Change} from './schemas'
|
||||
import * as core from '@actions/core'
|
||||
|
||||
export async function getDeniedChanges(
|
||||
changes: Change[],
|
||||
deniedPackages: string[],
|
||||
deniedGroups: string[]
|
||||
): Promise<Change[]> {
|
||||
const changesDenied: Change[] = []
|
||||
|
||||
let failed = false
|
||||
for (const change of changes) {
|
||||
change.name = change.name.toLowerCase()
|
||||
const packageUrl = change.package_url.toLowerCase().split('@')[0]
|
||||
|
||||
if (deniedPackages) {
|
||||
for (const denied of deniedPackages) {
|
||||
if (packageUrl === denied.split('@')[0].toLowerCase()) {
|
||||
changesDenied.push(change)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deniedGroups) {
|
||||
for (const denied of deniedGroups) {
|
||||
if (packageUrl.startsWith(denied.toLowerCase())) {
|
||||
changesDenied.push(change)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
core.setFailed('Dependency review detected denied packages.')
|
||||
} else {
|
||||
core.info('Dependency review did not detect any denied packages')
|
||||
}
|
||||
|
||||
return changesDenied
|
||||
}
|
||||
+86
-9
@@ -3,7 +3,7 @@ import * as dependencyGraph from './dependency-graph'
|
||||
import * as github from '@actions/github'
|
||||
import styles from 'ansi-styles'
|
||||
import {RequestError} from '@octokit/request-error'
|
||||
import {Change, Severity, Changes} from './schemas'
|
||||
import {Change, Severity, Changes, ConfigurationOptions} from './schemas'
|
||||
import {readConfig} from '../src/config'
|
||||
import {
|
||||
filterChangesBySeverity,
|
||||
@@ -16,6 +16,43 @@ import {getRefs} from './git-refs'
|
||||
|
||||
import {groupDependenciesByManifest} from './utils'
|
||||
import {commentPr} from './comment-pr'
|
||||
import {getDeniedChanges} from './deny'
|
||||
|
||||
async function delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
async function getComparison(
|
||||
baseRef: string,
|
||||
headRef: string,
|
||||
retryOpts?: {
|
||||
retryUntil: number
|
||||
retryDelay: number
|
||||
}
|
||||
): ReturnType<typeof dependencyGraph.compare> {
|
||||
const comparison = await dependencyGraph.compare({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
baseRef,
|
||||
headRef
|
||||
})
|
||||
|
||||
if (comparison.snapshot_warnings.trim() !== '') {
|
||||
core.info(comparison.snapshot_warnings)
|
||||
if (retryOpts !== undefined) {
|
||||
if (retryOpts.retryUntil < Date.now()) {
|
||||
core.info(`Retry timeout exceeded. Proceeding...`)
|
||||
return comparison
|
||||
} else {
|
||||
core.info(`Retrying in ${retryOpts.retryDelay} seconds...`)
|
||||
await delay(retryOpts.retryDelay * 1000)
|
||||
return getComparison(baseRef, headRef, retryOpts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return comparison
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
@@ -23,12 +60,18 @@ async function run(): Promise<void> {
|
||||
|
||||
const refs = getRefs(config, github.context)
|
||||
|
||||
const comparison = await dependencyGraph.compare({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
baseRef: refs.base,
|
||||
headRef: refs.head
|
||||
})
|
||||
const comparison = await getComparison(
|
||||
refs.base,
|
||||
refs.head,
|
||||
config.retry_on_snapshot_warnings
|
||||
? {
|
||||
retryUntil:
|
||||
Date.now() + config.retry_on_snapshot_warnings_timeout * 1000,
|
||||
retryDelay: 10
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
|
||||
const changes = comparison.changes
|
||||
const snapshot_warnings = comparison.snapshot_warnings
|
||||
|
||||
@@ -63,14 +106,24 @@ async function run(): Promise<void> {
|
||||
}
|
||||
)
|
||||
|
||||
core.debug(`Filtered Changes: ${JSON.stringify(filteredChanges)}`)
|
||||
core.debug(`Config Deny Packages: ${JSON.stringify(config)}`)
|
||||
|
||||
const deniedChanges = await getDeniedChanges(
|
||||
filteredChanges,
|
||||
config.deny_packages,
|
||||
config.deny_groups
|
||||
)
|
||||
|
||||
summary.addSummaryToSummary(
|
||||
vulnerableChanges,
|
||||
invalidLicenseChanges,
|
||||
deniedChanges,
|
||||
config
|
||||
)
|
||||
|
||||
if (snapshot_warnings) {
|
||||
summary.addSnapshotWarnings(snapshot_warnings)
|
||||
summary.addSnapshotWarnings(config, snapshot_warnings)
|
||||
}
|
||||
|
||||
if (config.vulnerability_check) {
|
||||
@@ -81,10 +134,18 @@ async function run(): Promise<void> {
|
||||
summary.addLicensesToSummary(invalidLicenseChanges, config)
|
||||
printLicensesBlock(invalidLicenseChanges)
|
||||
}
|
||||
if (config.deny_packages || config.deny_groups) {
|
||||
summary.addDeniedToSummary(deniedChanges)
|
||||
printDeniedDependencies(deniedChanges, config)
|
||||
}
|
||||
|
||||
summary.addScannedDependencies(changes)
|
||||
printScannedDependencies(changes)
|
||||
if (config.comment_summary_in_pr) {
|
||||
if (
|
||||
config.comment_summary_in_pr === 'always' ||
|
||||
(config.comment_summary_in_pr === 'on-failure' &&
|
||||
process.exitCode === core.ExitCode.Failure)
|
||||
) {
|
||||
await commentPr(core.summary)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -239,4 +300,20 @@ function printScannedDependencies(changes: Changes): void {
|
||||
})
|
||||
}
|
||||
|
||||
function printDeniedDependencies(
|
||||
changes: Change[],
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
core.group('Denied', async () => {
|
||||
for (const denied of config.deny_packages) {
|
||||
core.info(`Config: ${denied}`)
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
core.info(`Change: ${change.name}@${change.version} is denied`)
|
||||
core.info(`Change: ${change.package_url} is denied`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
+21
-1
@@ -42,12 +42,32 @@ export const ConfigurationOptionsSchema = z
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_dependencies_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
deny_packages: z.array(z.string()).default([]),
|
||||
deny_groups: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
config_file: z.string().optional(),
|
||||
base_ref: z.string().optional(),
|
||||
head_ref: z.string().optional(),
|
||||
comment_summary_in_pr: z.boolean().default(false)
|
||||
retry_on_snapshot_warnings: z.boolean().default(false),
|
||||
retry_on_snapshot_warnings_timeout: z.number().default(120),
|
||||
comment_summary_in_pr: z
|
||||
.union([
|
||||
z.preprocess(
|
||||
val => (val === 'true' ? true : val === 'false' ? false : val),
|
||||
z.boolean()
|
||||
),
|
||||
z.enum(['always', 'never', 'on-failure'])
|
||||
])
|
||||
.default('never')
|
||||
})
|
||||
.transform(config => {
|
||||
if (config.comment_summary_in_pr === true) {
|
||||
config.comment_summary_in_pr = 'always'
|
||||
} else if (config.comment_summary_in_pr === false) {
|
||||
config.comment_summary_in_pr = 'never'
|
||||
}
|
||||
return config
|
||||
})
|
||||
.superRefine((config, context) => {
|
||||
if (config.allow_licenses && config.deny_licenses) {
|
||||
|
||||
+54
-13
@@ -1,5 +1,5 @@
|
||||
import * as core from '@actions/core'
|
||||
import {ConfigurationOptions, Changes} from './schemas'
|
||||
import {ConfigurationOptions, Changes, Change} from './schemas'
|
||||
import {SummaryTableRow} from '@actions/core/lib/summary'
|
||||
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
|
||||
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
|
||||
@@ -13,13 +13,15 @@ const icons = {
|
||||
export function addSummaryToSummary(
|
||||
vulnerableChanges: Changes,
|
||||
invalidLicenseChanges: InvalidLicenseChanges,
|
||||
deniedChanges: Changes,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
core.summary.addHeading('Dependency Review', 1)
|
||||
|
||||
if (
|
||||
vulnerableChanges.length === 0 &&
|
||||
countLicenseIssues(invalidLicenseChanges) === 0
|
||||
countLicenseIssues(invalidLicenseChanges) === 0 &&
|
||||
deniedChanges.length === 0
|
||||
) {
|
||||
if (!config.license_check) {
|
||||
core.summary.addRaw(`${icons.check} No vulnerabilities found.`)
|
||||
@@ -56,6 +58,13 @@ export function addSummaryToSummary(
|
||||
invalidLicenseChanges.unlicensed.length
|
||||
} package(s) with unknown licenses.`
|
||||
]
|
||||
: []),
|
||||
...(deniedChanges.length > 0
|
||||
? [
|
||||
`${checkOrWarnIcon(deniedChanges.length)} ${
|
||||
deniedChanges.length
|
||||
} package(s) denied.`
|
||||
]
|
||||
: [])
|
||||
])
|
||||
.addRaw('See the Details below.')
|
||||
@@ -222,21 +231,34 @@ export function addScannedDependencies(changes: Changes): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function addSnapshotWarnings(warnings: string): void {
|
||||
// For now, we want to ignore warnings that just complain
|
||||
// about missing snapshots on the head SHA. This is a product
|
||||
// decision to avoid presenting warnings to users who simply
|
||||
// don't use snapshots.
|
||||
const ignore_regex = new RegExp(/No.*snapshot.*found.*head.*/, 'i')
|
||||
if (ignore_regex.test(warnings)) {
|
||||
return
|
||||
function snapshotWarningRecommendation(
|
||||
config: ConfigurationOptions,
|
||||
warnings: string
|
||||
): string {
|
||||
const no_pr_snaps = warnings.includes(
|
||||
'No snapshots were found for the head SHA'
|
||||
)
|
||||
const retries_disabled = !config.retry_on_snapshot_warnings
|
||||
if (no_pr_snaps && retries_disabled) {
|
||||
return 'Ensure that dependencies are being submitted on PR branches and consider enabling <em>retry-on-snapshot-warnings</em>.'
|
||||
} else if (no_pr_snaps) {
|
||||
return 'Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue.'
|
||||
} else if (retries_disabled) {
|
||||
return 'Consider enabling <em>retry-on-snapshot-warnings</em>.'
|
||||
}
|
||||
return 'Re-running this action after a short time may resolve the issue.'
|
||||
}
|
||||
|
||||
export function addSnapshotWarnings(
|
||||
config: ConfigurationOptions,
|
||||
warnings: string
|
||||
): void {
|
||||
core.summary.addHeading('Snapshot Warnings', 2)
|
||||
core.summary.addQuote(`${icons.warning}: ${warnings}`)
|
||||
core.summary.addRaw(
|
||||
'Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.'
|
||||
)
|
||||
const recommendation = snapshotWarningRecommendation(config, warnings)
|
||||
const docsLink =
|
||||
'See <a href="https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#best-practices-for-using-the-dependency-review-api-and-the-dependency-submission-api-together">the documentation</a> for more information and troubleshooting advice.'
|
||||
core.summary.addRaw(`${recommendation} ${docsLink}`)
|
||||
}
|
||||
|
||||
function countLicenseIssues(
|
||||
@@ -248,6 +270,25 @@ function countLicenseIssues(
|
||||
)
|
||||
}
|
||||
|
||||
export function addDeniedToSummary(deniedChanges: Change[]): void {
|
||||
if (deniedChanges.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
core.summary.addHeading('Denied dependencies', 2)
|
||||
for (const change of deniedChanges) {
|
||||
core.summary.addHeading(`<em>Denied dependencies</em>`, 4)
|
||||
core.summary.addTable([
|
||||
['Package', 'Version', 'License'],
|
||||
[
|
||||
renderUrl(change.source_repository_url, change.name),
|
||||
change.version,
|
||||
change.license || ''
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
function checkOrFailIcon(count: number): string {
|
||||
return count === 0 ? icons.check : icons.cross
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user