Compare commits

..
Author SHA1 Message Date
Henri Maurer d3fa764646 fix 2023-06-14 10:38:45 +01:00
Henri Maurer 1856a6de19 fix 2023-06-14 10:26:22 +01:00
Henri Maurer 5573b58443 better logging 2023-06-14 10:24:40 +01:00
Henri Maurer c3c3c2e746 fix retry until 2023-06-14 10:12:19 +01:00
Henri Maurer f6f94a23a4 fix 2023-06-09 10:44:43 +01:00
Henri Maurer 50954e6a9a fix 2023-06-09 10:30:56 +01:00
Henri Maurer 66b6f67835 Add configs 2023-06-09 10:26:24 +01:00
Henri Maurer 1644401f8d rewrite retry logic 2023-06-08 18:11:13 +01:00
Henri Maurer 1a326fc7fa proceed even if warnings 2023-06-08 17:04:40 +01:00
Henri Maurer a82096e68a fix 2023-06-07 16:51:53 +01:00
Henri Maurer 90d3a94eb7 fix 2023-06-07 16:48:32 +01:00
Henri Maurer 9dde5949a8 retry every 10s 2023-06-07 16:39:16 +01:00
Henri Maurer cff142b535 includes_dependency_snapshots 2023-06-07 14:04:29 +01:00
Henri Maurer a4c5ac881a disable caching 2023-06-07 10:10:21 +01:00
Henri Maurer d35955ebf6 Prototype re-try on snapshot warnings 2023-06-06 16:44:27 +01:00
17 changed files with 3505 additions and 6101 deletions
+3 -5
View File
@@ -43,7 +43,7 @@ This action is available in Enterprise Server starting with version 3.6. Make su
Security](https://docs.github.com/en/[email protected]/admin/code-security/managing-github-advanced-security-for-your-enterprise/enabling-github-advanced-security-for-your-enterprise) Security](https://docs.github.com/en/[email protected]/admin/code-security/managing-github-advanced-security-for-your-enterprise/enabling-github-advanced-security-for-your-enterprise)
and [GitHub and [GitHub
Connect](https://docs.github.com/en/[email protected]/admin/github-actions/managing-access-to-actions-from-githubcom/enabling-automatic-access-to-githubcom-actions-using-github-connect) Connect](https://docs.github.com/en/[email protected]/admin/github-actions/managing-access-to-actions-from-githubcom/enabling-automatic-access-to-githubcom-actions-using-github-connect)
are enabled, and that you have installed the [dependency-review-action](https://github.com/actions/dependency-review-action) on the server. are enabled.
You can use the same workflow as above, replacing the `runs-on` value You can use the same workflow as above, replacing the `runs-on` value
with the label of any of your runners (the default label with the label of any of your runners (the default label
@@ -67,7 +67,7 @@ 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. 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 | | 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` | | `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 | | `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 | | `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 |
@@ -78,8 +78,6 @@ Configure this action by either inlining these options in your workflow file, or
| `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 | | `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 | | `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` | | `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` |
| `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 |
\*not supported for use with GitHub Enterprise Server \*not supported for use with GitHub Enterprise Server
@@ -146,7 +144,7 @@ For more examples of how to use this action and its configuration options, see t
### Considerations ### Considerations
- Checking for licenses is not supported on Enterprise Server as the API does not return license information. - Checking for licenses is not supported on Enterprise Server.
- The action will only accept one of the two `license` parameters; an error will be raised if you provide both. - 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**. - 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**.
-166
View File
@@ -1,166 +0,0 @@
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/[email protected]',
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/[email protected]',
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/[email protected]',
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/[email protected]',
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)
})
+7 -32
View File
@@ -24,9 +24,9 @@ const defaultConfig: ConfigurationOptions = {
allow_ghsas: [], allow_ghsas: [],
allow_licenses: [], allow_licenses: [],
deny_licenses: [], deny_licenses: [],
deny_packages: [], comment_summary_in_pr: true,
deny_groups: [], retry_on_snapshot_warnings: true,
comment_summary_in_pr: true retry_on_snapshot_warnings_timeout: 120
} }
const changesWithEmptyManifests: Changes = [ const changesWithEmptyManifests: Changes = [
@@ -72,7 +72,6 @@ test('prints headline as h1', () => {
summary.addSummaryToSummary( summary.addSummaryToSummary(
emptyChanges, emptyChanges,
emptyInvalidLicenseChanges, emptyInvalidLicenseChanges,
emptyChanges,
defaultConfig defaultConfig
) )
const text = core.summary.stringify() const text = core.summary.stringify()
@@ -84,7 +83,6 @@ test('only includes "No vulnerabilities or license issues found"-message if both
summary.addSummaryToSummary( summary.addSummaryToSummary(
emptyChanges, emptyChanges,
emptyInvalidLicenseChanges, emptyInvalidLicenseChanges,
emptyChanges,
defaultConfig defaultConfig
) )
const text = core.summary.stringify() const text = core.summary.stringify()
@@ -94,12 +92,7 @@ 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', () => { test('only includes "No vulnerabilities found"-message if "license_check" is set to false and nothing was found', () => {
const config = {...defaultConfig, license_check: false} const config = {...defaultConfig, license_check: false}
summary.addSummaryToSummary( summary.addSummaryToSummary(emptyChanges, emptyInvalidLicenseChanges, config)
emptyChanges,
emptyInvalidLicenseChanges,
emptyChanges,
config
)
const text = core.summary.stringify() const text = core.summary.stringify()
expect(text).toContain('✅ No vulnerabilities found.') expect(text).toContain('✅ No vulnerabilities found.')
@@ -107,12 +100,7 @@ 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', () => { 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} const config = {...defaultConfig, vulnerability_check: false}
summary.addSummaryToSummary( summary.addSummaryToSummary(emptyChanges, emptyInvalidLicenseChanges, config)
emptyChanges,
emptyInvalidLicenseChanges,
emptyChanges,
config
)
const text = core.summary.stringify() const text = core.summary.stringify()
expect(text).toContain('✅ No license issues found.') expect(text).toContain('✅ No license issues found.')
@@ -122,7 +110,6 @@ test('groups dependencies with empty manifest paths together', () => {
summary.addSummaryToSummary( summary.addSummaryToSummary(
changesWithEmptyManifests, changesWithEmptyManifests,
emptyInvalidLicenseChanges, emptyInvalidLicenseChanges,
emptyChanges,
defaultConfig defaultConfig
) )
summary.addScannedDependencies(changesWithEmptyManifests) summary.addScannedDependencies(changesWithEmptyManifests)
@@ -139,7 +126,6 @@ test('does not include status section if nothing was found', () => {
summary.addSummaryToSummary( summary.addSummaryToSummary(
emptyChanges, emptyChanges,
emptyInvalidLicenseChanges, emptyInvalidLicenseChanges,
emptyChanges,
defaultConfig defaultConfig
) )
const text = core.summary.stringify() const text = core.summary.stringify()
@@ -158,12 +144,7 @@ test('includes count and status icons for all findings', () => {
unlicensed: [createTestChange(), createTestChange(), createTestChange()] unlicensed: [createTestChange(), createTestChange(), createTestChange()]
} }
summary.addSummaryToSummary( summary.addSummaryToSummary(vulnerabilities, licenseIssues, defaultConfig)
vulnerabilities,
licenseIssues,
emptyChanges,
defaultConfig
)
const text = core.summary.stringify() const text = core.summary.stringify()
expect(text).toContain('❌ 2 vulnerable package(s)') expect(text).toContain('❌ 2 vulnerable package(s)')
@@ -180,7 +161,6 @@ test('uses checkmarks for license issues if only vulnerabilities were found', ()
summary.addSummaryToSummary( summary.addSummaryToSummary(
vulnerabilities, vulnerabilities,
emptyInvalidLicenseChanges, emptyInvalidLicenseChanges,
emptyChanges,
defaultConfig defaultConfig
) )
@@ -200,12 +180,7 @@ test('uses checkmarks for vulnerabilities if only license issues were found', ()
unlicensed: [] unlicensed: []
} }
summary.addSummaryToSummary( summary.addSummaryToSummary(emptyChanges, licenseIssues, defaultConfig)
emptyChanges,
licenseIssues,
emptyChanges,
defaultConfig
)
const text = core.summary.stringify() const text = core.summary.stringify()
expect(text).toContain('✅ 0 vulnerable package(s)') expect(text).toContain('✅ 0 vulnerable package(s)')
+6 -4
View File
@@ -47,12 +47,14 @@ inputs:
comment-summary-in-pr: 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: 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
required: false required: false
deny-packages: retry-on-snapshot-warnings:
description: A comma-separated list of package URLs to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto") description: Whether to retry on snapshot warnings
required: false required: false
deny-groups: default: true
description: A comma-separated list of package URLs for group(s)/namespace(s) to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto") retry-on-snapshot-warnings-timeout:
description: How many seconds before bailing on retries
required: false required: false
default: 120
runs: runs:
using: 'node16' using: 'node16'
main: 'dist/index.js' main: 'dist/index.js'
Generated Vendored
+2107 -3670
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
-28
View File
@@ -230,31 +230,3 @@ jobs:
comment-summary-in-pr: true comment-summary-in-pr: true
license-check: false 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'
```
+1201 -1964
View File
File diff suppressed because it is too large Load Diff
+13 -11
View File
@@ -1,6 +1,6 @@
{ {
"name": "dependency-review-action", "name": "dependency-review-action",
"version": "3.0.7", "version": "3.0.6",
"private": true, "private": true,
"description": "A GitHub Action for Dependency Review", "description": "A GitHub Action for Dependency Review",
"main": "lib/main.js", "main": "lib/main.js",
@@ -27,11 +27,12 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
"@actions/github": "^5.1.1", "@actions/github": "^5.1.1",
"@octokit/plugin-retry": "^5.0.4", "@octokit/plugin-retry": "^5.0.0",
"@octokit/request-error": "^2.1.0", "@octokit/request-error": "^2.1.0",
"ansi-styles": "^6.2.1", "ansi-styles": "^6.2.1",
"got": "^13.0.0", "got": "^13.0.0",
"octokit": "^2.1.0", "nodemon": "^2.0.22",
"octokit": "^2.0.16",
"packageurl-js": "^1.0.2", "packageurl-js": "^1.0.2",
"spdx-expression-parse": "^3.0.1", "spdx-expression-parse": "^3.0.1",
"spdx-satisfies": "^5.0.1", "spdx-satisfies": "^5.0.1",
@@ -40,21 +41,22 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/node": "^16.18.38", "@types/node": "^16.18.34",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.0",
"@types/spdx-expression-parse": "^3.0.2", "@types/spdx-expression-parse": "^3.0.2",
"@types/spdx-satisfies": "^0.1.0", "@types/spdx-satisfies": "^0.1.0",
"@typescript-eslint/eslint-plugin": "^6.2.0", "@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^6.2.1", "@typescript-eslint/parser": "^5.59.8",
"@vercel/ncc": "^0.36.1", "@vercel/ncc": "^0.36.1",
"esbuild-register": "^3.4.2", "esbuild-register": "^3.4.2",
"eslint": "^8.44.0", "eslint": "^8.41.0",
"eslint-plugin-github": "^4.8.0", "eslint-plugin-github": "^4.8.0",
"eslint-plugin-jest": "^27.2.2", "eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"nodemon": "^3.0.1", "nodemon": "^2.0.22",
"prettier": "3.0.1", "prettier": "2.8.8",
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"typescript": "^4.9.5" "typescript": "^4.9.5"
} }
+6 -8
View File
@@ -6,7 +6,7 @@
* npx ts-node scripts/create_summary.ts * npx ts-node scripts/create_summary.ts
*/ */
import {Change, Changes, ConfigurationOptions} from '../src/schemas' import {Changes, ConfigurationOptions} from '../src/schemas'
import {createTestChange} from '../__tests__/fixtures/create-test-change' import {createTestChange} from '../__tests__/fixtures/create-test-change'
import {InvalidLicenseChanges} from '../src/licenses' import {InvalidLicenseChanges} from '../src/licenses'
import * as fs from 'fs' import * as fs from 'fs'
@@ -22,15 +22,15 @@ const defaultConfig: ConfigurationOptions = {
allow_ghsas: [], allow_ghsas: [],
allow_licenses: ['MIT'], allow_licenses: ['MIT'],
deny_licenses: [], deny_licenses: [],
deny_packages: [],
deny_groups: [],
allow_dependencies_licenses: [ allow_dependencies_licenses: [
'pkg:npm/[email protected]', 'pkg:npm/[email protected]',
'pkg:pip/requests', 'pkg:pip/requests',
'pkg:pip/certifi', 'pkg:pip/certifi',
'pkg:pip/[email protected]' 'pkg:pip/[email protected]'
], ],
comment_summary_in_pr: true comment_summary_in_pr: true,
retry_on_snapshot_warnings: true,
retry_on_snapshot_warnings_timeout: 120
} }
const tmpDir = path.resolve(__dirname, '../tmp') const tmpDir = path.resolve(__dirname, '../tmp')
@@ -46,7 +46,6 @@ const createNonIssueSummary = async (): Promise<void> => {
await createSummary( await createSummary(
[], [],
{forbidden: [], unresolved: [], unlicensed: []}, {forbidden: [], unresolved: [], unlicensed: []},
[],
defaultConfig, defaultConfig,
'non-issue-summary.md' 'non-issue-summary.md'
) )
@@ -88,17 +87,16 @@ const createFullSummary = async (): Promise<void> => {
] ]
} }
await createSummary(changes, licenses, [], defaultConfig, 'full-summary.md') await createSummary(changes, licenses, defaultConfig, 'full-summary.md')
} }
async function createSummary( async function createSummary(
vulnerabilities: Changes, vulnerabilities: Changes,
licenseIssues: InvalidLicenseChanges, licenseIssues: InvalidLicenseChanges,
denied: Change[],
config: ConfigurationOptions, config: ConfigurationOptions,
fileName: string fileName: string
): Promise<void> { ): Promise<void> {
summary.addSummaryToSummary(vulnerabilities, licenseIssues, denied, config) summary.addSummaryToSummary(vulnerabilities, licenseIssues, config)
summary.addChangeVulnerabilitiesToSummary( summary.addChangeVulnerabilitiesToSummary(
vulnerabilities, vulnerabilities,
config.fail_on_severity config.fail_on_severity
+2 -2
View File
@@ -74,8 +74,8 @@ async function findCommentByMarker(
) )
for await (const {data: comments} of commentsIterator) { for await (const {data: comments} of commentsIterator) {
const existingComment = comments.find( const existingComment = comments.find(comment =>
comment => comment.body?.includes(commentBodyIncludes) comment.body?.includes(commentBodyIncludes)
) )
if (existingComment) return existingComment.id if (existingComment) return existingComment.id
} }
+16 -8
View File
@@ -33,14 +33,18 @@ function readInlineConfig(): ConfigurationOptionsPartial {
const allow_dependencies_licenses = parseList( const allow_dependencies_licenses = parseList(
getOptionalInput('allow-dependencies-licenses') 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 allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
const license_check = getOptionalBoolean('license-check') const license_check = getOptionalBoolean('license-check')
const vulnerability_check = getOptionalBoolean('vulnerability-check') const vulnerability_check = getOptionalBoolean('vulnerability-check')
const base_ref = getOptionalInput('base-ref') const base_ref = getOptionalInput('base-ref')
const head_ref = getOptionalInput('head-ref') const head_ref = getOptionalInput('head-ref')
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr') const comment_summary_in_pr = getOptionalBoolean('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) validatePURL(allow_dependencies_licenses)
validateLicenses('allow-licenses', allow_licenses) validateLicenses('allow-licenses', allow_licenses)
@@ -51,15 +55,15 @@ function readInlineConfig(): ConfigurationOptionsPartial {
fail_on_scopes, fail_on_scopes,
allow_licenses, allow_licenses,
deny_licenses, deny_licenses,
deny_packages,
deny_groups,
allow_dependencies_licenses, allow_dependencies_licenses,
allow_ghsas, allow_ghsas,
license_check, license_check,
vulnerability_check, vulnerability_check,
base_ref, base_ref,
head_ref, head_ref,
comment_summary_in_pr comment_summary_in_pr,
retry_on_snapshot_warnings,
retry_on_snapshot_warnings_timeout
} }
return Object.fromEntries( return Object.fromEntries(
@@ -67,6 +71,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 { function getOptionalBoolean(name: string): boolean | undefined {
const value = core.getInput(name) const value = core.getInput(name)
return value.length > 0 ? core.getBooleanInput(name) : undefined return value.length > 0 ? core.getBooleanInput(name) : undefined
@@ -141,9 +151,7 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
'deny-licenses', 'deny-licenses',
'fail-on-scopes', 'fail-on-scopes',
'allow-ghsas', 'allow-ghsas',
'allow-dependencies-licenses', 'allow-dependencies-licenses'
'deny-packages',
'deny-groups'
] ]
for (const key of Object.keys(data)) { for (const key of Object.keys(data)) {
-42
View File
@@ -1,42 +0,0 @@
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
}
+1 -1
View File
@@ -28,7 +28,7 @@ export async function compare({
const changes = await octo.paginate( const changes = await octo.paginate(
{ {
method: 'GET', method: 'GET',
url: '/repos/{owner}/{repo}/dependency-graph/compare/{basehead}', url: '/repos/{owner}/{repo}/dependency-graph/compare/{basehead}?includes_dependency_snapshots=true',
owner, owner,
repo, repo,
basehead: `${baseRef}...${headRef}` basehead: `${baseRef}...${headRef}`
+49 -38
View File
@@ -3,7 +3,7 @@ import * as dependencyGraph from './dependency-graph'
import * as github from '@actions/github' import * as github from '@actions/github'
import styles from 'ansi-styles' import styles from 'ansi-styles'
import {RequestError} from '@octokit/request-error' import {RequestError} from '@octokit/request-error'
import {Change, Severity, Changes, ConfigurationOptions} from './schemas' import {Change, Severity, Changes} from './schemas'
import {readConfig} from '../src/config' import {readConfig} from '../src/config'
import { import {
filterChangesBySeverity, filterChangesBySeverity,
@@ -16,7 +16,42 @@ import {getRefs} from './git-refs'
import {groupDependenciesByManifest} from './utils' import {groupDependenciesByManifest} from './utils'
import {commentPr} from './comment-pr' 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> { async function run(): Promise<void> {
try { try {
@@ -24,12 +59,18 @@ async function run(): Promise<void> {
const refs = getRefs(config, github.context) const refs = getRefs(config, github.context)
const comparison = await dependencyGraph.compare({ const comparison = await getComparison(
owner: github.context.repo.owner, refs.base,
repo: github.context.repo.repo, refs.head,
baseRef: refs.base, config.retry_on_snapshot_warnings
headRef: refs.head ? {
}) retryUntil:
Date.now() + config.retry_on_snapshot_warnings_timeout * 1000,
retryDelay: 10
}
: undefined
)
const changes = comparison.changes const changes = comparison.changes
const snapshot_warnings = comparison.snapshot_warnings const snapshot_warnings = comparison.snapshot_warnings
@@ -64,19 +105,9 @@ 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( summary.addSummaryToSummary(
vulnerableChanges, vulnerableChanges,
invalidLicenseChanges, invalidLicenseChanges,
deniedChanges,
config config
) )
@@ -92,10 +123,6 @@ async function run(): Promise<void> {
summary.addLicensesToSummary(invalidLicenseChanges, config) summary.addLicensesToSummary(invalidLicenseChanges, config)
printLicensesBlock(invalidLicenseChanges) printLicensesBlock(invalidLicenseChanges)
} }
if (config.deny_packages || config.deny_groups) {
summary.addDeniedToSummary(deniedChanges)
printDeniedDependencies(deniedChanges, config)
}
summary.addScannedDependencies(changes) summary.addScannedDependencies(changes)
printScannedDependencies(changes) printScannedDependencies(changes)
@@ -254,20 +281,4 @@ 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() run()
+3 -3
View File
@@ -42,14 +42,14 @@ export const ConfigurationOptionsSchema = z
deny_licenses: z.array(z.string()).optional(), deny_licenses: z.array(z.string()).optional(),
allow_dependencies_licenses: z.array(z.string()).optional(), allow_dependencies_licenses: z.array(z.string()).optional(),
allow_ghsas: z.array(z.string()).default([]), 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), license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),
base_ref: z.string().optional(), base_ref: z.string().optional(),
head_ref: z.string().optional(), head_ref: z.string().optional(),
comment_summary_in_pr: z.boolean().default(false) comment_summary_in_pr: z.boolean().default(false),
retry_on_snapshot_warnings: z.boolean().default(true),
retry_on_snapshot_warnings_timeout: z.number().default(120)
}) })
.superRefine((config, context) => { .superRefine((config, context) => {
if (config.allow_licenses && config.deny_licenses) { if (config.allow_licenses && config.deny_licenses) {
+2 -30
View File
@@ -1,5 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {ConfigurationOptions, Changes, Change} from './schemas' import {ConfigurationOptions, Changes} from './schemas'
import {SummaryTableRow} from '@actions/core/lib/summary' import {SummaryTableRow} from '@actions/core/lib/summary'
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses' import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils' import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
@@ -13,15 +13,13 @@ const icons = {
export function addSummaryToSummary( export function addSummaryToSummary(
vulnerableChanges: Changes, vulnerableChanges: Changes,
invalidLicenseChanges: InvalidLicenseChanges, invalidLicenseChanges: InvalidLicenseChanges,
deniedChanges: Changes,
config: ConfigurationOptions config: ConfigurationOptions
): void { ): void {
core.summary.addHeading('Dependency Review', 1) core.summary.addHeading('Dependency Review', 1)
if ( if (
vulnerableChanges.length === 0 && vulnerableChanges.length === 0 &&
countLicenseIssues(invalidLicenseChanges) === 0 && countLicenseIssues(invalidLicenseChanges) === 0
deniedChanges.length === 0
) { ) {
if (!config.license_check) { if (!config.license_check) {
core.summary.addRaw(`${icons.check} No vulnerabilities found.`) core.summary.addRaw(`${icons.check} No vulnerabilities found.`)
@@ -58,13 +56,6 @@ export function addSummaryToSummary(
invalidLicenseChanges.unlicensed.length invalidLicenseChanges.unlicensed.length
} package(s) with unknown licenses.` } package(s) with unknown licenses.`
] ]
: []),
...(deniedChanges.length > 0
? [
`${checkOrWarnIcon(deniedChanges.length)} ${
deniedChanges.length
} package(s) denied.`
]
: []) : [])
]) ])
.addRaw('See the Details below.') .addRaw('See the Details below.')
@@ -257,25 +248,6 @@ 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 { function checkOrFailIcon(count: number): string {
return count === 0 ? icons.check : icons.cross return count === 0 ? icons.check : icons.cross
} }