Compare commits
249 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fa40c3c10 | |||
| 1f6240f54c | |||
| b751d41e7e | |||
| 6183eb9d2b | |||
| 6585cc5f01 | |||
| 218a76cbd5 | |||
| d78d095945 | |||
| 36297aa214 | |||
| 1e69a8c24a | |||
| 2f10643938 | |||
| 1eb83b5560 | |||
| 47b7acc8e3 | |||
| 16bfb3360c | |||
| bc6a1f0dd4 | |||
| e38e634e54 | |||
| 5574be321f | |||
| aef51371b7 | |||
| 3b70c9966e | |||
| adaed32746 | |||
| 4ce120135b | |||
| 0e8bc32a54 | |||
| f875e6ec1d | |||
| 72666694f0 | |||
| 7dc5f537be | |||
| ac600387ca | |||
| d186d663df | |||
| b7fdb4c8e2 | |||
| ba6b805e18 | |||
| 70801db78f | |||
| 5bc19761c5 | |||
| 5ba0d0fe17 | |||
| 6a74ebd41e | |||
| 250250e73d | |||
| cb0a0415fb | |||
| 296bf3ab1b | |||
| 59d4782b76 | |||
| e878bf8824 | |||
| 1b21f392ca | |||
| 111227a118 | |||
| a1258f2a2e | |||
| 29b9ef447a | |||
| b5a1aee21a | |||
| b3d2872ac7 | |||
| 5bace73db3 | |||
| f8ebb4b946 | |||
| 84b80e6e84 | |||
| 1251834b92 | |||
| 94125c4b1e | |||
| 9843156266 | |||
| 2fcc6a1c72 | |||
| ea64ae9d4d | |||
| 5955069e69 | |||
| 05fcfa49e0 | |||
| 75be7f0c0c | |||
| 7d2e20d06d | |||
| 97f7ba06d0 | |||
| 2bc3ecb19b | |||
| c286ea91b0 | |||
| 6bcbf042ff | |||
| 43286afc54 | |||
| 764e39e792 | |||
| bf2683a10c | |||
| f1b66d10c9 | |||
| ffd129c285 | |||
| 72d5b06a68 | |||
| b2ddac1749 | |||
| f357c751be | |||
| e16e218fdc | |||
| f87cc241f7 | |||
| d641d3a261 | |||
| 4230610a70 | |||
| af5438b06f | |||
| 781bff117a | |||
| 7a8ce509c9 | |||
| f419e37c19 | |||
| 3d70a3cf05 | |||
| e7aef164a1 | |||
| ab8c3848de | |||
| eecc9aab88 | |||
| 7f632dbe1f | |||
| ba18fafa8d | |||
| 40eb2b8b00 | |||
| db6a5119ce | |||
| 9129d7d40b | |||
| a1be843151 | |||
| 587ff57efd | |||
| be8bc500ee | |||
| cb180bf383 | |||
| b2ea187fd2 | |||
| c94f57ba90 | |||
| 124fafe31e | |||
| 26174d80a2 | |||
| a87338ac8a | |||
| 64f81cd2da | |||
| 0ca1f606a4 | |||
| d416fb5267 | |||
| 81bba5eb54 | |||
| f9daaa3561 | |||
| 60c44a0894 | |||
| 7911825c25 | |||
| ad040f4b88 | |||
| 2876926e7f | |||
| 47a0fcbcd4 | |||
| da507e61ac | |||
| 0034949d8d | |||
| f1706f5a9d | |||
| a569f6fc5c | |||
| fd07d42ce8 | |||
| 77290ae4a1 | |||
| 9411082069 | |||
| 73d8c1b981 | |||
| 80f10bf419 | |||
| 17728c80ab | |||
| 0ac4f80276 | |||
| 1ebcf1475c | |||
| 5777ce6aec | |||
| 37dd5f9e8a | |||
| 6c2af06a9d | |||
| 58d70bd41a | |||
| 972c2b36d8 | |||
| 60f93ef4a0 | |||
| c2936a6e3e | |||
| ba2d570913 | |||
| 629b4c97dd | |||
| 58e8c75f3b | |||
| 8db04ed44f | |||
| b3aa197a26 | |||
| 4e78eb60ef | |||
| 65f749b96d | |||
| 0043ed5ccb | |||
| 52933765bf | |||
| 6a6f26102b | |||
| 8f3df4d674 | |||
| 98e8293881 | |||
| 748888b3fd | |||
| 4dffb75625 | |||
| 9e50351924 | |||
| 0812876f7c | |||
| 4f37a60d4f | |||
| c0518321c3 | |||
| 0229309241 | |||
| c664fc5964 | |||
| a7da313c35 | |||
| 8953f45584 | |||
| d93026fc89 | |||
| 5a2ac62566 | |||
| 0f007f69b1 | |||
| 05d861260a | |||
| 2c526853b4 | |||
| 9e251a5913 | |||
| ee5bd475ba | |||
| b0a705da21 | |||
| 0bab6ffc2c | |||
| f91404ca86 | |||
| d6f324d18a | |||
| f1576849e6 | |||
| fc49851780 | |||
| d53388efe8 | |||
| 56991330a3 | |||
| a824acd5d7 | |||
| 935098a950 | |||
| b658b91622 | |||
| d16453ab26 | |||
| 0381eac2bc | |||
| 1967b21a03 | |||
| 4cd9eb2d23 | |||
| 4901385134 | |||
| dbf82a4a5e | |||
| 78aeb2a948 | |||
| 4e510006f5 | |||
| 9560737c5e | |||
| 4125f47f7e | |||
| 07cc93e0c8 | |||
| e2c203b8b7 | |||
| f0b304d0bc | |||
| e41543eaf0 | |||
| 8ded6194d1 | |||
| b5f60d5e37 | |||
| 45fc3f5adc | |||
| c8593625f2 | |||
| d2ca024914 | |||
| 9649fc68a8 | |||
| 2ac94ccf28 | |||
| 925e3a5871 | |||
| ab6f73d40f | |||
| 746e9675d6 | |||
| 3735443721 | |||
| 44bab84b22 | |||
| 8690720eb6 | |||
| e03c8a14eb | |||
| 194e338d30 | |||
| 1d740b64ec | |||
| c74b580d73 | |||
| cc4f6536e3 | |||
| d2ed7c0d19 | |||
| 9e77cc7329 | |||
| b383a9aa6e | |||
| 8a49820431 | |||
| a10a70d24c | |||
| 0de163860f | |||
| 522f0218d0 | |||
| 2597ca4eee | |||
| e5c6735807 | |||
| 94f992f10e | |||
| c45cbd720f | |||
| 5ccb7d478c | |||
| 02456f4a00 | |||
| 1c9a424cbc | |||
| 2425542aca | |||
| b39e17ba5e | |||
| b8a398b675 | |||
| 1612de9646 | |||
| 53de591348 | |||
| 288d543806 | |||
| 359e1ffa80 | |||
| 63e1558807 | |||
| 069cbabe02 | |||
| 2e3c709016 | |||
| 01bc87099b | |||
| 4b4f0de8e1 | |||
| a93fa86c77 | |||
| 550520e2c5 | |||
| 2d0fb60634 | |||
| c07c2375ed | |||
| 4d842d754e | |||
| a6d4686316 | |||
| 4366dbae42 | |||
| 50dafeb5e4 | |||
| 1cbb048907 | |||
| ee69e92054 | |||
| 5991d7a97d | |||
| c409735e58 | |||
| 7bbfa034e7 | |||
| 26f1ad9120 | |||
| 152d8e2def | |||
| b99756ecd3 | |||
| fde92acd08 | |||
| 3b37a4ef1c | |||
| 13c4496f31 | |||
| 7ed3405bdc | |||
| 9b290a185a | |||
| 995bb847a3 | |||
| f1e6d67732 | |||
| d833109d4d | |||
| a3a8a9c756 | |||
| 0b053fccb4 | |||
| 78f160dece | |||
| cc302f4c2b | |||
| 621d03bf3a |
@@ -23,10 +23,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set Node.js 18.x
|
||||
- name: Set Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
id: diff
|
||||
|
||||
# If index.js was different than expected, upload the expected version as an artifact
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
|
||||
with:
|
||||
name: dist
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '21 0 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 360
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript-typescript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
config: |
|
||||
paths-ignore:
|
||||
- dist/index.js
|
||||
- dist/sourcemap-register.js
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Close stale PRs and Issues
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "00 0 * * *" # runs at 00:00 daily
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9.0.0
|
||||
name: Clean up stale PRs and Issues
|
||||
with:
|
||||
stale-pr-message: "👋 This pull request has been marked as stale because it has been open with no activity. You can: comment on the issue or remove the stale label to hold stale off for a while, add the `Keep` label to hold stale off permanently, or do nothing. If you do nothing, this pull request will be closed eventually by the stale bot. Please see CONTRIBUTING.md for more policy details."
|
||||
stale-pr-label: "Stale"
|
||||
stale-issue-label: "Stale"
|
||||
exempt-pr-labels: "Keep" # a "Keep" label will keep the PR from being closed as stale
|
||||
exempt-issue-labels: "Keep" # a "Keep" label will keep the issue from being closed as stale
|
||||
days-before-pr-stale: 180 # when the PR is considered stale
|
||||
days-before-pr-close: 15 # when the PR is closed by the bot
|
||||
days-before-issue-stale: 180 # when the issue is considered stale
|
||||
days-before-issue-close: 15 # when the issue is closed by the bot
|
||||
exempt-assignees: 'advanced-security-dependency-graph'
|
||||
ascending: true
|
||||
+9
-3
@@ -77,9 +77,11 @@ $ GITHUB_TOKEN=<token> ./scripts/scan_pr --config-file my_custom_config.yml <pr_
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
- Write tests.
|
||||
- Add unit tests for new features.
|
||||
- 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](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
- Add examples of the usage to [examples.md](examples.md)
|
||||
- Link to a sample PR in a custom repository running your version of the Action.
|
||||
|
||||
## Cutting a new release
|
||||
|
||||
@@ -112,10 +114,14 @@ minor/patch updates.
|
||||
To do this just checkout `main`, force-create a new annotated tag, and push it:
|
||||
|
||||
```
|
||||
git tag -fa v3 -m "Updating v3 to 3.0.1"
|
||||
git push origin v3 --force
|
||||
git tag -fa v4 -m "Updating v4 to 4.0.1"
|
||||
git push origin v4 --force
|
||||
```
|
||||
|
||||
## Stalebot
|
||||
|
||||
We have begun using a [Stalebot action](https://github.com/actions/stale) to help keep the Issues and Pull requests backlogs tidy. You can see the configuration [here](.github/workflows/stalebot.yml). If you'd like to keep an issue open after getting a stalebot warning, simply comment on it and it'll reset the clock.
|
||||
|
||||
## Resources
|
||||
|
||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
# dependency-review-action
|
||||
|
||||
This action scans your pull requests for dependency changes, and will
|
||||
raise an error if any vulnerabilities or invalid licenses are being introduced. The action is supported by an [API endpoint](https://docs.github.com/en/rest/reference/dependency-graph#dependency-review) that diffs the dependencies between any two revisions on your default branch.
|
||||
raise an error if any vulnerabilities or invalid licenses are being introduced. The action is supported by an [API endpoint](https://docs.github.com/rest/dependency-graph/dependency-review) that diffs the dependencies between any two revisions on your default branch.
|
||||
|
||||
The action is available for all public repositories, as well as private repositories that have GitHub Advanced Security licensed.
|
||||
|
||||
You can see the results on the job logs:
|
||||
|
||||
<img width="854" alt="Screen Shot 2022-03-31 at 1 10 51 PM" src="https://user-images.githubusercontent.com/2161/161042286-b22d7dd3-13cb-458d-8744-ce70ed9bf562.png">
|
||||
<img width="850" alt="GitHub workflow run log showing Dependency Review job output" src="https://user-images.githubusercontent.com/2161/161042286-b22d7dd3-13cb-458d-8744-ce70ed9bf562.png">
|
||||
|
||||
or on the job summary:
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/7847935/182871416-50332bbb-b279-4621-a136-ca72a4314301.png">
|
||||
<img width="850" alt="GitHub job summary showing Dependency Review output" src="https://github.com/actions/dependency-review-action/assets/2161/42fbed1d-64a7-42bf-9b05-c416bc67493f">
|
||||
|
||||
## Installation
|
||||
|
||||
**Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/en/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.**
|
||||
**Please keep in mind that you need a [GitHub Advanced Security](https://docs.github.com/enterprise-cloud@latest/get-started/learning-about-github/about-github-advanced-security) license if you're running this action on private repositories.**
|
||||
|
||||
1. Add a new YAML workflow to your `.github/workflows` folder:
|
||||
|
||||
@@ -33,16 +33,16 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
```
|
||||
|
||||
### GitHub Enterprise Server
|
||||
|
||||
This action is available in Enterprise Server starting with version 3.6. Make sure
|
||||
Make sure
|
||||
[GitHub Advanced
|
||||
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)
|
||||
Security](https://docs.github.com/enterprise-server@3.8/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)
|
||||
Connect](https://docs.github.com/enterprise-server@3.8/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.
|
||||
|
||||
You can use the same workflow as above, replacing the `runs-on` value
|
||||
@@ -59,33 +59,36 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
```
|
||||
|
||||
## Configuration options
|
||||
|
||||
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`. | `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 |
|
||||
| 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 the `pull-requests: write` permission. | `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 |
|
||||
| `warn-only`+ | When set to `true`, the action will log all vulnerabilities as warnings regardless of the severity, and the action will complete with a `success` status. This overrides the `fail-on-severity` option. | `true`, `false` | `false` |
|
||||
| `show-openssf-scorecard-levels` | When set to `true`, the action will output information about all the known OpenSSF Scorecard scores for the dependencies changed in this pull request. | `true`, `false` | `true` |
|
||||
| `warn-on-openssf-scorecard-level` | When `show-openssf-scorecard-levels` is set to `true`, this option lets you configure the threshold for when a score is considered too low and gets a :warning: warning in the CI. | Any positive integer | 3 |
|
||||
|
||||
\*not supported for use with GitHub Enterprise Server
|
||||
|
||||
†will be supported with GitHub Enterprise Server 3.8
|
||||
+when `warn-only` is set to `true`, all vulnerabilities, independently of the severity, will be reported as warnings and the action will not fail.
|
||||
|
||||
### Inline Configuration
|
||||
|
||||
@@ -105,7 +108,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: moderate
|
||||
|
||||
@@ -128,7 +131,7 @@ Start by specifying that you will be using an external configuration file:
|
||||
|
||||
```yaml
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
config-file: './.github/dependency-review-config.yml'
|
||||
```
|
||||
@@ -149,12 +152,35 @@ 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 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.
|
||||
- The `allow-licenses` and `deny-licenses` options are mutually exclusive; 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**.
|
||||
|
||||
## Blocking pull requests
|
||||
|
||||
The Dependency Review GitHub Action check will only block a pull request from being merged if the repository owner has required the check to pass before merging. For more information, see the [documentation on protected branches](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches#require-status-checks-before-merging).
|
||||
The Dependency Review GitHub Action check will only block a pull request from being merged if the repository owner has required the check to pass before merging. For more information, see the [documentation on protected branches](https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches#require-status-checks-before-merging).
|
||||
|
||||
## Outputs
|
||||
|
||||
- `comment-content` is generated with the same content as would be present in a Dependency Review Action comment.
|
||||
- `dependency-changes` holds all dependency changes in a JSON format. The following outputs are subsets of `dependency-changes` filtered based on the configuration:
|
||||
- `vulnerable-changes` holds information about dependency changes with vulnerable dependencies in a JSON format.
|
||||
- `invalid-license-changes` holds information about invalid or non-compliant license dependency changes in a JSON format.
|
||||
- `denied-changes` holds information about denied dependency changes in a JSON format.
|
||||
|
||||
> [!NOTE]
|
||||
> Action outputs are unicode strings [with a 1MB size limit](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you use these outputs in a run-step, you must store the ouput data in an envrioment variable instead of using the output directly. Using an output directly might break shell scripts. For example:
|
||||
>
|
||||
> ```yaml
|
||||
> env:
|
||||
> VULNERABLE_CHANGES: ${{ steps.review.outputs.vulnerable-changes }}
|
||||
> run: |
|
||||
> echo "$VULNERABLE_CHANGES" | jq
|
||||
> ```
|
||||
>
|
||||
> instead of direct `echo '${{ steps.review.outputs.vulnerable-changes }}'`. See [examples](docs/examples.md) for more.
|
||||
|
||||
## Getting help
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ test('it reads custom configs', async () => {
|
||||
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
||||
})
|
||||
|
||||
test('it defaults to false for warn-only', async () => {
|
||||
const config = await readConfig()
|
||||
expect(config.warn_only).toEqual(false)
|
||||
})
|
||||
|
||||
test('it defaults to empty allow/deny lists ', async () => {
|
||||
const config = await readConfig()
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ const pipChange: Change = {
|
||||
ecosystem: 'pip',
|
||||
name: 'package-1',
|
||||
version: '1.1.1',
|
||||
package_url: 'pkg:pip/package-1@1.1.1',
|
||||
package_url: 'pkg:pypi/package-1@1.1.1',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'github.com/some-repo',
|
||||
scope: 'runtime',
|
||||
|
||||
+118
-24
@@ -19,7 +19,7 @@ const npmChange: Change = {
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'critical',
|
||||
advisory_ghsa_id: 'first-random_string',
|
||||
advisory_ghsa_id: 'vulnerable-ghsa-id',
|
||||
advisory_summary: 'very dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
@@ -39,13 +39,13 @@ const rubyChange: Change = {
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'moderate',
|
||||
advisory_ghsa_id: 'second-random_string',
|
||||
advisory_ghsa_id: 'moderate-ghsa-id',
|
||||
advisory_summary: 'not so dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
},
|
||||
{
|
||||
severity: 'low',
|
||||
advisory_ghsa_id: 'third-random_string',
|
||||
advisory_ghsa_id: 'low-ghsa-id',
|
||||
advisory_summary: 'dont page me',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
@@ -65,6 +65,64 @@ const noVulnNpmChange: Change = {
|
||||
vulnerabilities: []
|
||||
}
|
||||
|
||||
const lodashChange: Change = {
|
||||
change_type: 'added',
|
||||
manifest: 'package.json',
|
||||
ecosystem: 'npm',
|
||||
name: 'lodash',
|
||||
version: '4.17.0',
|
||||
package_url: 'pkg:npm/lodash@4.17.0',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'https://github.com/lodash/lodash',
|
||||
scope: 'runtime',
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'critical',
|
||||
advisory_ghsa_id: 'GHSA-jf85-cpcp-j695',
|
||||
advisory_summary: 'Prototype Pollution in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-jf85-cpcp-j695'
|
||||
},
|
||||
{
|
||||
severity: 'high',
|
||||
advisory_ghsa_id: 'GHSA-4xc9-xhrj-v574',
|
||||
advisory_summary: 'Prototype Pollution in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-4xc9-xhrj-v574'
|
||||
},
|
||||
{
|
||||
severity: 'high',
|
||||
advisory_ghsa_id: 'GHSA-35jh-r3h4-6jhm',
|
||||
advisory_summary: 'Command Injection in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-35jh-r3h4-6jhm'
|
||||
},
|
||||
{
|
||||
severity: 'high',
|
||||
advisory_ghsa_id: 'GHSA-p6mc-m468-83gw',
|
||||
advisory_summary: 'Prototype Pollution in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-p6mc-m468-83gw'
|
||||
},
|
||||
{
|
||||
severity: 'moderate',
|
||||
advisory_ghsa_id: 'GHSA-x5rq-j2xg-h7qm',
|
||||
advisory_summary:
|
||||
'Regular Expression Denial of Service (ReDoS) in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-x5rq-j2xg-h7qm'
|
||||
},
|
||||
{
|
||||
severity: 'moderate',
|
||||
advisory_ghsa_id: 'GHSA-29mw-wpgm-hmr9',
|
||||
advisory_summary:
|
||||
'Regular Expression Denial of Service (ReDoS) in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-29mw-wpgm-hmr9'
|
||||
},
|
||||
{
|
||||
severity: 'low',
|
||||
advisory_ghsa_id: 'GHSA-fvqr-27wr-82fm',
|
||||
advisory_summary: 'Prototype Pollution in lodash',
|
||||
advisory_url: 'https://github.com/advisories/GHSA-fvqr-27wr-82fm'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
test('it properly filters changes by severity', async () => {
|
||||
const changes = [npmChange, rubyChange]
|
||||
let result = filterChangesBySeverity('high', changes)
|
||||
@@ -99,25 +157,61 @@ test('it properly handles undefined advisory IDs', async () => {
|
||||
test('it properly filters changes with allowed vulnerabilities', async () => {
|
||||
const changes = [npmChange, rubyChange, noVulnNpmChange]
|
||||
|
||||
let result = filterAllowedAdvisories(['notrealGHSAID'], changes)
|
||||
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
|
||||
|
||||
result = filterAllowedAdvisories(['first-random_string'], changes)
|
||||
expect(result).toEqual([rubyChange, noVulnNpmChange])
|
||||
|
||||
result = filterAllowedAdvisories(
|
||||
['second-random_string', 'third-random_string'],
|
||||
changes
|
||||
)
|
||||
expect(result).toEqual([npmChange, noVulnNpmChange])
|
||||
|
||||
result = filterAllowedAdvisories(
|
||||
['first-random_string', 'second-random_string', 'third-random_string'],
|
||||
changes
|
||||
)
|
||||
expect(result).toEqual([noVulnNpmChange])
|
||||
|
||||
// if we have a change with multiple vulnerabilities but only one is allowed, we still should not filter out that change
|
||||
result = filterAllowedAdvisories(['second-random_string'], changes)
|
||||
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
|
||||
const fakeGHSAChanges = filterAllowedAdvisories(['notrealGHSAID'], changes)
|
||||
expect(fakeGHSAChanges).toEqual([npmChange, rubyChange, noVulnNpmChange])
|
||||
})
|
||||
|
||||
test('it properly filters only allowed vulnerabilities', async () => {
|
||||
const changes = [npmChange, rubyChange, noVulnNpmChange]
|
||||
const oldVulns = [
|
||||
...npmChange.vulnerabilities,
|
||||
...rubyChange.vulnerabilities,
|
||||
...noVulnNpmChange.vulnerabilities
|
||||
]
|
||||
|
||||
const vulnerable = filterAllowedAdvisories(['vulnerable-ghsa-id'], changes)
|
||||
|
||||
const newVulns = vulnerable.map(change => change.vulnerabilities).flat()
|
||||
|
||||
expect(newVulns.length).toEqual(oldVulns.length - 1)
|
||||
expect(newVulns).not.toContainEqual(
|
||||
expect.objectContaining({advisory_ghsa_id: 'vulnerable-ghsa-id'})
|
||||
)
|
||||
})
|
||||
|
||||
test('does not drop dependencies when filtering by GHSA', async () => {
|
||||
const changes = [npmChange, rubyChange, noVulnNpmChange]
|
||||
const result = filterAllowedAdvisories(
|
||||
['moderate-ghsa-id', 'low-ghsa-id', 'GHSA-jf85-cpcp-j695'],
|
||||
changes
|
||||
)
|
||||
|
||||
expect(result.map(change => change.name)).toEqual(
|
||||
changes.map(change => change.name)
|
||||
)
|
||||
})
|
||||
|
||||
test('it properly filters multiple GHSAs', async () => {
|
||||
const allowedGHSAs = ['vulnerable-ghsa-id', 'moderate-ghsa-id', 'low-ghsa-id']
|
||||
const changes = [npmChange, rubyChange, noVulnNpmChange]
|
||||
const oldVulns = changes.map(change => change.vulnerabilities).flat()
|
||||
|
||||
const result = filterAllowedAdvisories(allowedGHSAs, changes)
|
||||
|
||||
const newVulns = result.map(change => change.vulnerabilities).flat()
|
||||
|
||||
expect(newVulns.length).toEqual(oldVulns.length - 3)
|
||||
})
|
||||
|
||||
test('it filters out GHSA dependencies', async () => {
|
||||
const lodash = filterAllowedAdvisories(
|
||||
['GHSA-jf85-cpcp-j695'],
|
||||
[lodashChange]
|
||||
)[0]
|
||||
// the filter should have removed a single GHSA from the list
|
||||
const expected = lodashChange.vulnerabilities.filter(
|
||||
vuln => vuln.advisory_ghsa_id !== 'GHSA-jf85-cpcp-j695'
|
||||
)
|
||||
expect(expected.length).toEqual(lodashChange.vulnerabilities.length - 1)
|
||||
expect(lodash.vulnerabilities).toEqual(expected)
|
||||
})
|
||||
|
||||
@@ -55,7 +55,7 @@ const pipChange: Change = {
|
||||
ecosystem: 'pip',
|
||||
name: 'package-1',
|
||||
version: '1.1.1',
|
||||
package_url: 'pkg:pip/package-1@1.1.1',
|
||||
package_url: 'pkg:pypi/package-1@1.1.1',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'github.com/some-repo',
|
||||
scope: 'runtime',
|
||||
@@ -183,7 +183,7 @@ test('it does not filter out changes that are on the exclusions list', async ()
|
||||
const changes: Changes = [pipChange, npmChange, rubyChange]
|
||||
const licensesConfig = {
|
||||
allow: ['BSD'],
|
||||
licenseExclusions: ['pkg:pip/package-1@1.1.1', 'pkg:npm/reeuhq@1.0.2']
|
||||
licenseExclusions: ['pkg:pypi/package-1@1.1.1', 'pkg:npm/reeuhq@1.0.2']
|
||||
}
|
||||
const invalidLicenses = await getInvalidLicenseChanges(
|
||||
changes,
|
||||
@@ -199,7 +199,7 @@ test('it does not fail when the packages dont have a valid PURL', async () => {
|
||||
const changes: Changes = [emptyPurlChange, npmChange, rubyChange]
|
||||
const licensesConfig = {
|
||||
allow: ['BSD'],
|
||||
licenseExclusions: ['pkg:pip/package-1@1.1.1', 'pkg:npm/reeuhq@1.0.2']
|
||||
licenseExclusions: ['pkg:pypi/package-1@1.1.1', 'pkg:npm/reeuhq@1.0.2']
|
||||
}
|
||||
|
||||
const invalidLicenses = await getInvalidLicenseChanges(
|
||||
@@ -213,7 +213,10 @@ test('it does filters out changes if they are not on the exclusions list', async
|
||||
const changes: Changes = [pipChange, npmChange, rubyChange]
|
||||
const licensesConfig = {
|
||||
allow: ['BSD'],
|
||||
licenseExclusions: ['pkg:pip/notmypackage-1@1.1.1', 'pkg:npm/alsonot@1.0.2']
|
||||
licenseExclusions: [
|
||||
'pkg:pypi/notmypackage-1@1.1.1',
|
||||
'pkg:npm/alsonot@1.0.2'
|
||||
]
|
||||
}
|
||||
const invalidLicenses = await getInvalidLicenseChanges(
|
||||
changes,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import {expect, test} from '@jest/globals'
|
||||
import {Change, Changes} from '../src/schemas'
|
||||
import {getScorecardLevels, getProjectUrl} from '../src/scorecard'
|
||||
|
||||
const npmChange: Change = {
|
||||
manifest: 'package.json',
|
||||
change_type: 'added',
|
||||
ecosystem: 'npm',
|
||||
name: 'type-is',
|
||||
version: '1.6.18',
|
||||
package_url: 'pkg:npm/type-is@1.6.18',
|
||||
license: 'MIT',
|
||||
source_repository_url: 'github.com/jshttp/type-is',
|
||||
scope: 'runtime',
|
||||
vulnerabilities: [
|
||||
{
|
||||
severity: 'critical',
|
||||
advisory_ghsa_id: 'first-random_string',
|
||||
advisory_summary: 'very dangerous',
|
||||
advisory_url: 'github.com/future-funk'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
test('Get scorecard from API', async () => {
|
||||
const changes: Changes = [npmChange]
|
||||
const scorecard = await getScorecardLevels(changes)
|
||||
expect(scorecard).not.toBeNull()
|
||||
expect(scorecard.dependencies).toHaveLength(1)
|
||||
expect(scorecard.dependencies[0].scorecard?.score).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('Get project URL from deps.dev API', async () => {
|
||||
const result = await getProjectUrl(
|
||||
npmChange.ecosystem,
|
||||
npmChange.name,
|
||||
npmChange.version
|
||||
)
|
||||
expect(result).not.toBeNull()
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import {expect, jest, test} from '@jest/globals'
|
||||
import {Changes, ConfigurationOptions} from '../src/schemas'
|
||||
import {Changes, ConfigurationOptions, Scorecard} from '../src/schemas'
|
||||
import * as summary from '../src/summary'
|
||||
import * as core from '@actions/core'
|
||||
import {createTestChange} from './fixtures/create-test-change'
|
||||
@@ -16,6 +16,9 @@ const emptyInvalidLicenseChanges = {
|
||||
unresolved: [],
|
||||
unlicensed: []
|
||||
}
|
||||
const emptyScorecard: Scorecard = {
|
||||
dependencies: []
|
||||
}
|
||||
const defaultConfig: ConfigurationOptions = {
|
||||
vulnerability_check: true,
|
||||
license_check: true,
|
||||
@@ -28,7 +31,10 @@ const defaultConfig: ConfigurationOptions = {
|
||||
deny_groups: [],
|
||||
comment_summary_in_pr: true,
|
||||
retry_on_snapshot_warnings: false,
|
||||
retry_on_snapshot_warnings_timeout: 120
|
||||
retry_on_snapshot_warnings_timeout: 120,
|
||||
warn_only: false,
|
||||
warn_on_openssf_scorecard_level: 3,
|
||||
show_openssf_scorecard: false
|
||||
}
|
||||
|
||||
const changesWithEmptyManifests: Changes = [
|
||||
@@ -70,11 +76,32 @@ const changesWithEmptyManifests: Changes = [
|
||||
}
|
||||
]
|
||||
|
||||
const scorecard: Scorecard = {
|
||||
dependencies: [
|
||||
{
|
||||
change: {
|
||||
change_type: 'added',
|
||||
manifest: '',
|
||||
ecosystem: 'unknown',
|
||||
name: 'castore',
|
||||
version: '0.1.17',
|
||||
package_url: 'pkg:hex/castore@0.1.17',
|
||||
license: null,
|
||||
source_repository_url: null,
|
||||
scope: 'runtime',
|
||||
vulnerabilities: []
|
||||
},
|
||||
scorecard: null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
test('prints headline as h1', () => {
|
||||
summary.addSummaryToSummary(
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
scorecard,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -87,6 +114,7 @@ test('only includes "No vulnerabilities or license issues found"-message if both
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -100,6 +128,7 @@ test('only includes "No vulnerabilities found"-message if "license_check" is set
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
config
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -113,6 +142,7 @@ test('only includes "No license issues found"-message if "vulnerability_check" i
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
config
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -125,6 +155,7 @@ test('groups dependencies with empty manifest paths together', () => {
|
||||
changesWithEmptyManifests,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
defaultConfig
|
||||
)
|
||||
summary.addScannedDependencies(changesWithEmptyManifests)
|
||||
@@ -142,6 +173,7 @@ test('does not include status section if nothing was found', () => {
|
||||
emptyChanges,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
defaultConfig
|
||||
)
|
||||
const text = core.summary.stringify()
|
||||
@@ -164,6 +196,7 @@ test('includes count and status icons for all findings', () => {
|
||||
vulnerabilities,
|
||||
licenseIssues,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -183,6 +216,7 @@ test('uses checkmarks for license issues if only vulnerabilities were found', ()
|
||||
vulnerabilities,
|
||||
emptyInvalidLicenseChanges,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
@@ -206,6 +240,7 @@ test('uses checkmarks for vulnerabilities if only license issues were found', ()
|
||||
emptyChanges,
|
||||
licenseIssues,
|
||||
emptyChanges,
|
||||
emptyScorecard,
|
||||
defaultConfig
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ export function clearInputs(): void {
|
||||
'CONFIG-FILE',
|
||||
'BASE-REF',
|
||||
'HEAD-REF',
|
||||
'COMMENT-SUMMARY-IN-PR'
|
||||
'COMMENT-SUMMARY-IN-PR',
|
||||
'WARN-ONLY'
|
||||
]
|
||||
|
||||
// eslint-disable-next-line github/array-foreach
|
||||
|
||||
+28
-4
@@ -30,7 +30,7 @@ inputs:
|
||||
description: Comma-separated list of forbidden licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
|
||||
required: false
|
||||
allow-dependencies-licenses:
|
||||
description: Comma-separated list of dependencies in purl format (e.g. "pkg:npm/express, pkg:pip/pycrypto"). These dependencies will be permitted to use any license, no matter what license policy is enforced otherwise.
|
||||
description: Comma-separated list of dependencies in purl format (e.g. "pkg:npm/express, pkg:pypi/pycrypto"). These dependencies will be permitted to use any license, no matter what license policy is enforced otherwise.
|
||||
required: false
|
||||
allow-ghsas:
|
||||
description: Comma-separated list of allowed GitHub Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
|
||||
@@ -48,10 +48,10 @@ inputs:
|
||||
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")
|
||||
description: A comma-separated list of package URLs to deny (e.g. "pkg:npm/express, pkg:pypi/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")
|
||||
description: A comma-separated list of package URLs for group(s)/namespace(s) to deny (e.g. "pkg:npm/express, pkg:pypi/pycrypto")
|
||||
required: false
|
||||
retry-on-snapshot-warnings:
|
||||
description: Whether to retry on snapshot warnings
|
||||
@@ -61,6 +61,30 @@ inputs:
|
||||
description: Number of seconds to wait before stopping snapshot retries.
|
||||
required: false
|
||||
default: 120
|
||||
warn-only:
|
||||
description: When set to `true` this action will always complete with success, overriding the `fail-on-severity` parameter.
|
||||
required: false
|
||||
default: false
|
||||
show-openssf-scorecard:
|
||||
description: Show a summary of the OpenSSF Scorecard scores.
|
||||
required: false
|
||||
default: true
|
||||
warn-on-openssf-scorecard-level:
|
||||
description: Numeric threshold for the OpenSSF Scorecard score. If the score is below this threshold, the action will warn you.
|
||||
required: false
|
||||
default: 3
|
||||
outputs:
|
||||
comment-content:
|
||||
description: Prepared dependency report comment
|
||||
dependency-changes:
|
||||
description: All dependency changes (JSON)
|
||||
vulnerable-changes:
|
||||
description: Vulnerable dependency changes (JSON)
|
||||
invalid-license-changes:
|
||||
description: Invalid license dependency changes (JSON)
|
||||
denied-changes:
|
||||
description: Denied dependency changes (JSON)
|
||||
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node20'
|
||||
main: 'dist/index.js'
|
||||
|
||||
+3871
-9832
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+11
-129
@@ -344,6 +344,17 @@ 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.
|
||||
|
||||
|
||||
@octokit/plugin-paginate-graphql
|
||||
MIT
|
||||
MIT License Copyright (c) 2019 Octokit 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 (including the next paragraph) 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.
|
||||
|
||||
|
||||
@octokit/plugin-paginate-rest
|
||||
MIT
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
@@ -503,16 +514,6 @@ 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.
|
||||
|
||||
|
||||
@vercel/ncc
|
||||
MIT
|
||||
Copyright 2018 ZEIT, Inc.
|
||||
|
||||
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.
|
||||
|
||||
aggregate-error
|
||||
MIT
|
||||
MIT License
|
||||
@@ -1068,30 +1069,6 @@ Apache License
|
||||
limitations under the License.
|
||||
|
||||
|
||||
fromentries
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Feross Aboukhadijeh
|
||||
|
||||
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.
|
||||
|
||||
|
||||
indent-string
|
||||
MIT
|
||||
MIT License
|
||||
@@ -1105,31 +1082,6 @@ 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.
|
||||
|
||||
|
||||
is-plain-object
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
jsonwebtoken
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
@@ -1523,32 +1475,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
node-fetch
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 David Frank
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
octokit
|
||||
MIT
|
||||
The MIT License
|
||||
@@ -1742,9 +1668,6 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
tr46
|
||||
MIT
|
||||
|
||||
tunnel
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
@@ -1844,47 +1767,6 @@ 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.
|
||||
|
||||
|
||||
webidl-conversions
|
||||
BSD-2-Clause
|
||||
# The BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2014, Domenic Denicola
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
whatwg-url
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015–2016 Sebastian Mayr
|
||||
|
||||
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.
|
||||
|
||||
|
||||
wrappy
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
+57
-14
@@ -1,4 +1,4 @@
|
||||
# Examples on how to use the Dependancy Review Action
|
||||
# Examples on how to use the Dependency Review Action
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
```
|
||||
|
||||
## Using an inline configuration
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
@@ -62,7 +62,7 @@ allow_licenses:
|
||||
- 'BSD-2-Clause'
|
||||
```
|
||||
|
||||
The Dependancy Review Action workflow file will then look like this:
|
||||
The Dependency Review Action workflow file will then look like this:
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
config-file: './.github/dependency-review-config.yml'
|
||||
```
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
config-file: 'github/octorepo/dependency-review-config.yml@main'
|
||||
```
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
config-file: 'github/octorepo-private/dependency-review-config.yml@main'
|
||||
external-repo-token: ${{ secrets.GITHUB_TOKEN }} # or a personal access token
|
||||
@@ -157,13 +157,56 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
comment-summary-in-pr: always
|
||||
```
|
||||
|
||||
## Getting the results of the action in a later step
|
||||
|
||||
- `comment-content` contains the output of the results comment for the entire run.
|
||||
`dependency-changes`, `vulnerable-changes`, `invalid-license-changes` and `denied-changes` are all JSON objects that allow you to access individual sets of changes.
|
||||
|
||||
```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@v4
|
||||
- name: 'Dependency Review'
|
||||
id: review
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
- name: 'Report'
|
||||
# make sure this step runs even if the previous failed
|
||||
if: ${{ failure() && steps.review.conclusion == 'failure' }}
|
||||
shell: bash
|
||||
env: # store comment HTML data in an environment variable
|
||||
COMMENT: ${{ steps.review.outputs.comment-content }}
|
||||
run: | # do something with the comment:
|
||||
echo "$COMMENT"
|
||||
- name: 'List vulnerable dependencies'
|
||||
# make sure this step runs even if the previous failed
|
||||
if: ${{ failure() && steps.review.conclusion == 'failure' }}
|
||||
shell: bash
|
||||
env: # store JSON data in an environment variable
|
||||
VULNERABLE_CHANGES: ${{ steps.review.outputs.vulnerable-changes }}
|
||||
run: | # do something with the JSON:
|
||||
echo "$VULNERABLE_CHANGES" | jq '.[].package_url'
|
||||
```
|
||||
|
||||
## Exclude dependencies from the license check
|
||||
|
||||
Using the `allow-dependencies-licenses` you can exclude dependencies from the license check. The values should be provided in [purl](https://github.com/package-url/purl-spec) format.
|
||||
@@ -185,12 +228,12 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
comment-summary-in-pr: always
|
||||
allow-dependencies-licenses: 'pkg:npm/loadash, pkg:pip/requests'
|
||||
allow-dependencies-licenses: 'pkg:npm/loadash, pkg:pypi/requests'
|
||||
```
|
||||
|
||||
If we were to use configuration file, the configuration would look like this:
|
||||
@@ -202,7 +245,7 @@ allow-licenses:
|
||||
- 'BSD-2-Clause'
|
||||
allow-dependencies-licenses:
|
||||
- 'pkg:npm/loadash'
|
||||
- 'pkg:pip/requests'
|
||||
- 'pkg:pypi/requests'
|
||||
```
|
||||
|
||||
## Only check for vulnerabilities
|
||||
@@ -224,7 +267,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
comment-summary-in-pr: always
|
||||
@@ -253,7 +296,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
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'
|
||||
@@ -287,7 +330,7 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
retry-on-snapshot-warnings: true
|
||||
retry-on-snapshot-warnings-timeout: 60
|
||||
|
||||
Generated
+384
-1002
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dependency-review-action",
|
||||
"version": "3.1.2",
|
||||
"version": "4.2.3",
|
||||
"private": true,
|
||||
"description": "A GitHub Action for Dependency Review",
|
||||
"main": "lib/main.js",
|
||||
@@ -26,36 +26,36 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/plugin-retry": "^5.0.4",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@octokit/plugin-retry": "^6.0.1",
|
||||
"@octokit/request-error": "^5.0.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"ansi-styles": "^6.2.1",
|
||||
"got": "^13.0.0",
|
||||
"got": "^14.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"octokit": "^2.1.0",
|
||||
"octokit": "^3.1.2",
|
||||
"packageurl-js": "^1.2.0",
|
||||
"spdx-expression-parse": "^3.0.1",
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"yaml": "^2.3.4",
|
||||
"zod": "^3.22.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.18.58",
|
||||
"@types/node": "^20",
|
||||
"@types/spdx-expression-parse": "^3.0.4",
|
||||
"@types/spdx-satisfies": "^0.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||
"@typescript-eslint/parser": "^6.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vercel/ncc": "^0.38.0",
|
||||
"esbuild-register": "^3.5.0",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"prettier": "3.0.3",
|
||||
"typescript": "^5.2.2"
|
||||
"nodemon": "^3.1.0",
|
||||
"prettier": "3.2.5",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* npx ts-node scripts/create_summary.ts
|
||||
*/
|
||||
|
||||
import {Change, Changes, ConfigurationOptions} from '../src/schemas'
|
||||
import {Change, Changes, ConfigurationOptions, Scorecard} from '../src/schemas'
|
||||
import {createTestChange} from '../__tests__/fixtures/create-test-change'
|
||||
import {InvalidLicenseChanges} from '../src/licenses'
|
||||
import * as fs from 'fs'
|
||||
@@ -26,13 +26,36 @@ const defaultConfig: ConfigurationOptions = {
|
||||
deny_groups: [],
|
||||
allow_dependencies_licenses: [
|
||||
'pkg:npm/express@4.17.1',
|
||||
'pkg:pip/requests',
|
||||
'pkg:pip/certifi',
|
||||
'pkg:pip/pycrypto@2.6.1'
|
||||
'pkg:pypi/requests',
|
||||
'pkg:pypi/certifi',
|
||||
'pkg:pypi/pycrypto@2.6.1'
|
||||
],
|
||||
comment_summary_in_pr: true,
|
||||
retry_on_snapshot_warnings: false,
|
||||
retry_on_snapshot_warnings_timeout: 120
|
||||
retry_on_snapshot_warnings_timeout: 120,
|
||||
warn_only: false,
|
||||
warn_on_openssf_scorecard_level: 3,
|
||||
show_openssf_scorecard: true
|
||||
}
|
||||
|
||||
const scorecard: Scorecard = {
|
||||
dependencies: [
|
||||
{
|
||||
change: {
|
||||
change_type: 'added',
|
||||
manifest: '',
|
||||
ecosystem: 'unknown',
|
||||
name: 'castore',
|
||||
version: '0.1.17',
|
||||
package_url: 'pkg:hex/castore@0.1.17',
|
||||
license: null,
|
||||
source_repository_url: null,
|
||||
scope: 'runtime',
|
||||
vulnerabilities: []
|
||||
},
|
||||
scorecard: null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const tmpDir = path.resolve(__dirname, '../tmp')
|
||||
@@ -100,7 +123,13 @@ async function createSummary(
|
||||
config: ConfigurationOptions,
|
||||
fileName: string
|
||||
): Promise<void> {
|
||||
summary.addSummaryToSummary(vulnerabilities, licenseIssues, denied, config)
|
||||
summary.addSummaryToSummary(
|
||||
vulnerabilities,
|
||||
licenseIssues,
|
||||
denied,
|
||||
scorecard,
|
||||
config
|
||||
)
|
||||
summary.addChangeVulnerabilitiesToSummary(
|
||||
vulnerabilities,
|
||||
config.fail_on_severity
|
||||
|
||||
+22
-4
@@ -3,6 +3,7 @@ import * as core from '@actions/core'
|
||||
import * as githubUtils from '@actions/github/lib/utils'
|
||||
import * as retry from '@octokit/plugin-retry'
|
||||
import {RequestError} from '@octokit/request-error'
|
||||
import {ConfigurationOptions} from './schemas'
|
||||
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry)
|
||||
const octo = new retryingOctokit(
|
||||
@@ -12,7 +13,24 @@ const octo = new retryingOctokit(
|
||||
// Comment Marker to identify an existing comment to update, so we don't spam the PR with comments
|
||||
const COMMENT_MARKER = '<!-- dependency-review-pr-comment-marker -->'
|
||||
|
||||
export async function commentPr(summary: typeof core.summary): Promise<void> {
|
||||
export async function commentPr(
|
||||
summary: typeof core.summary,
|
||||
config: ConfigurationOptions
|
||||
): Promise<void> {
|
||||
const commentContent = summary.stringify()
|
||||
|
||||
core.setOutput('comment-content', commentContent)
|
||||
|
||||
if (
|
||||
!(
|
||||
config.comment_summary_in_pr === 'always' ||
|
||||
(config.comment_summary_in_pr === 'on-failure' &&
|
||||
process.exitCode === core.ExitCode.Failure)
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!github.context.payload.pull_request) {
|
||||
core.warning(
|
||||
'Not in the context of a pull request. Skipping comment creation.'
|
||||
@@ -20,7 +38,7 @@ export async function commentPr(summary: typeof core.summary): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const commentBody = `${summary.stringify()}\n\n${COMMENT_MARKER}`
|
||||
const commentBody = `${commentContent}\n\n${COMMENT_MARKER}`
|
||||
|
||||
try {
|
||||
const existingCommentId = await findCommentByMarker(COMMENT_MARKER)
|
||||
@@ -74,8 +92,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
|
||||
}
|
||||
|
||||
+9
-1
@@ -47,6 +47,11 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
const retry_on_snapshot_warnings_timeout = getOptionalNumber(
|
||||
'retry-on-snapshot-warnings-timeout'
|
||||
)
|
||||
const warn_only = getOptionalBoolean('warn-only')
|
||||
const show_openssf_scorecard = getOptionalBoolean('show-openssf-scorecard')
|
||||
const warn_on_openssf_scorecard_level = getOptionalNumber(
|
||||
'warn-on-openssf-scorecard-level'
|
||||
)
|
||||
|
||||
validatePURL(allow_dependencies_licenses)
|
||||
validateLicenses('allow-licenses', allow_licenses)
|
||||
@@ -67,7 +72,10 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
head_ref,
|
||||
comment_summary_in_pr,
|
||||
retry_on_snapshot_warnings,
|
||||
retry_on_snapshot_warnings_timeout
|
||||
retry_on_snapshot_warnings_timeout,
|
||||
warn_only,
|
||||
show_openssf_scorecard,
|
||||
warn_on_openssf_scorecard_level
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
|
||||
@@ -31,7 +31,8 @@ export async function compare({
|
||||
url: '/repos/{owner}/{repo}/dependency-graph/compare/{basehead}',
|
||||
owner,
|
||||
repo,
|
||||
basehead: `${baseRef}...${headRef}`
|
||||
basehead: `${baseRef}...${headRef}`,
|
||||
per_page: 5
|
||||
},
|
||||
response => {
|
||||
if (
|
||||
|
||||
+23
-13
@@ -1,5 +1,13 @@
|
||||
import {Changes, Severity, SEVERITIES, Scope} from './schemas'
|
||||
|
||||
/**
|
||||
* Filters changes by a severity level. Only vulnerable
|
||||
* dependencies will be returned.
|
||||
*
|
||||
* @param severity - The severity level to filter by.
|
||||
* @param changes - The array of changes to filter.
|
||||
* @returns The filtered array of changes that match the specified severity level and have vulnerabilities.
|
||||
*/
|
||||
export function filterChangesBySeverity(
|
||||
severity: Severity,
|
||||
changes: Changes
|
||||
@@ -31,7 +39,14 @@ export function filterChangesBySeverity(
|
||||
filteredChanges = filteredChanges.filter(
|
||||
change => change.vulnerabilities.length > 0
|
||||
)
|
||||
return filteredChanges
|
||||
|
||||
// only report vulnerability additions
|
||||
return filteredChanges.filter(
|
||||
change =>
|
||||
change.change_type === 'added' &&
|
||||
change.vulnerabilities !== undefined &&
|
||||
change.vulnerabilities.length > 0
|
||||
)
|
||||
}
|
||||
|
||||
export function filterChangesByScopes(
|
||||
@@ -67,25 +82,20 @@ export function filterAllowedAdvisories(
|
||||
return changes
|
||||
}
|
||||
|
||||
const filteredChanges = changes.filter(change => {
|
||||
const filteredChanges = changes.map(change => {
|
||||
const noAdvisories =
|
||||
change.vulnerabilities === undefined ||
|
||||
change.vulnerabilities.length === 0
|
||||
|
||||
if (noAdvisories) {
|
||||
return true
|
||||
return change
|
||||
}
|
||||
const newChange = {...change}
|
||||
newChange.vulnerabilities = change.vulnerabilities.filter(
|
||||
vuln => !ghsas.includes(vuln.advisory_ghsa_id)
|
||||
)
|
||||
|
||||
let allAllowedAdvisories = true
|
||||
// if there's at least one advisory that is not allowlisted, we will keep the change
|
||||
for (const vulnerability of change.vulnerabilities) {
|
||||
if (!ghsas.includes(vulnerability.advisory_ghsa_id)) {
|
||||
allAllowedAdvisories = false
|
||||
}
|
||||
if (!allAllowedAdvisories) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return newChange
|
||||
})
|
||||
|
||||
return filteredChanges
|
||||
|
||||
+5
-2
@@ -21,16 +21,19 @@ export function getRefs(
|
||||
if (!base_ref && !head_ref) {
|
||||
throw new Error(
|
||||
'Both a base ref and head ref must be provided, either via the `base_ref`/`head_ref` ' +
|
||||
'config options, or by running a `pull_request`/`pull_request_target` workflow.'
|
||||
'config options, `base-ref`/`head-ref` workflow action options, or by running a ' +
|
||||
'`pull_request`/`pull_request_target` workflow.'
|
||||
)
|
||||
} else if (!base_ref) {
|
||||
throw new Error(
|
||||
'A base ref must be provided, either via the `base_ref` config option, ' +
|
||||
'or by running a `pull_request`/`pull_request_target` workflow.'
|
||||
'`base-ref` workflow action option, or by running a ' +
|
||||
'`pull_request`/`pull_request_target` workflow.'
|
||||
)
|
||||
} else if (!head_ref) {
|
||||
throw new Error(
|
||||
'A head ref must be provided, either via the `head_ref` config option, ' +
|
||||
'`head-ref` workflow action option, or by running a ' +
|
||||
'or by running a `pull_request`/`pull_request_target` workflow.'
|
||||
)
|
||||
}
|
||||
|
||||
+4
-2
@@ -32,7 +32,7 @@ export async function getInvalidLicenseChanges(
|
||||
const {allow, deny} = licenses
|
||||
const licenseExclusions = licenses.licenseExclusions?.map(
|
||||
(pkgUrl: string) => {
|
||||
return PackageURL.fromString(pkgUrl)
|
||||
return PackageURL.fromString(encodeURI(pkgUrl))
|
||||
}
|
||||
)
|
||||
|
||||
@@ -45,7 +45,9 @@ export async function getInvalidLicenseChanges(
|
||||
return true
|
||||
}
|
||||
|
||||
const changeAsPackageURL = PackageURL.fromString(change.package_url)
|
||||
const changeAsPackageURL = PackageURL.fromString(
|
||||
encodeURI(change.package_url)
|
||||
)
|
||||
|
||||
// We want to find if the licenseExclussion list contains the PackageURL of the Change
|
||||
// If it does, we want to filter it out and therefore return false
|
||||
|
||||
+97
-23
@@ -3,7 +3,13 @@ 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, ConfigurationOptions} from './schemas'
|
||||
import {
|
||||
Change,
|
||||
Severity,
|
||||
Changes,
|
||||
ConfigurationOptions,
|
||||
Scorecard
|
||||
} from './schemas'
|
||||
import {readConfig} from '../src/config'
|
||||
import {
|
||||
filterChangesBySeverity,
|
||||
@@ -11,6 +17,7 @@ import {
|
||||
filterAllowedAdvisories
|
||||
} from '../src/filter'
|
||||
import {getInvalidLicenseChanges} from './licenses'
|
||||
import {getScorecardLevels} from './scorecard'
|
||||
import * as summary from './summary'
|
||||
import {getRefs} from './git-refs'
|
||||
|
||||
@@ -80,21 +87,24 @@ async function run(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const minSeverity = config.fail_on_severity
|
||||
const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
|
||||
|
||||
const filteredChanges = filterAllowedAdvisories(
|
||||
config.allow_ghsas,
|
||||
scopedChanges
|
||||
)
|
||||
|
||||
const failOnSeverityParams = config.fail_on_severity
|
||||
const warnOnly = config.warn_only
|
||||
let minSeverity: Severity = 'low'
|
||||
// If failOnSeverityParams is not set or warnOnly is true, the minSeverity is low, to allow all vulnerabilities to be reported as warnings
|
||||
if (failOnSeverityParams && !warnOnly) {
|
||||
minSeverity = failOnSeverityParams
|
||||
}
|
||||
|
||||
const vulnerableChanges = filterChangesBySeverity(
|
||||
minSeverity,
|
||||
filteredChanges
|
||||
).filter(
|
||||
change =>
|
||||
change.change_type === 'added' &&
|
||||
change.vulnerabilities !== undefined &&
|
||||
change.vulnerabilities.length > 0
|
||||
)
|
||||
|
||||
const invalidLicenseChanges = await getInvalidLicenseChanges(
|
||||
@@ -115,10 +125,13 @@ async function run(): Promise<void> {
|
||||
config.deny_groups
|
||||
)
|
||||
|
||||
const scorecard = await getScorecardLevels(filteredChanges)
|
||||
|
||||
summary.addSummaryToSummary(
|
||||
vulnerableChanges,
|
||||
invalidLicenseChanges,
|
||||
deniedChanges,
|
||||
scorecard,
|
||||
config
|
||||
)
|
||||
|
||||
@@ -127,27 +140,33 @@ async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
if (config.vulnerability_check) {
|
||||
core.setOutput('vulnerable-changes', JSON.stringify(vulnerableChanges))
|
||||
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
|
||||
printVulnerabilitiesBlock(vulnerableChanges, minSeverity)
|
||||
printVulnerabilitiesBlock(vulnerableChanges, minSeverity, warnOnly)
|
||||
}
|
||||
if (config.license_check) {
|
||||
core.setOutput(
|
||||
'invalid-license-changes',
|
||||
JSON.stringify(invalidLicenseChanges)
|
||||
)
|
||||
summary.addLicensesToSummary(invalidLicenseChanges, config)
|
||||
printLicensesBlock(invalidLicenseChanges)
|
||||
printLicensesBlock(invalidLicenseChanges, warnOnly)
|
||||
}
|
||||
if (config.deny_packages || config.deny_groups) {
|
||||
core.setOutput('denied-changes', JSON.stringify(deniedChanges))
|
||||
summary.addDeniedToSummary(deniedChanges)
|
||||
printDeniedDependencies(deniedChanges, config)
|
||||
}
|
||||
if (config.show_openssf_scorecard) {
|
||||
summary.addScorecardToSummary(scorecard, config)
|
||||
printScorecardBlock(scorecard, config)
|
||||
createScorecardWarnings(scorecard, config)
|
||||
}
|
||||
|
||||
core.setOutput('dependency-changes', JSON.stringify(changes))
|
||||
summary.addScannedDependencies(changes)
|
||||
printScannedDependencies(changes)
|
||||
if (
|
||||
config.comment_summary_in_pr === 'always' ||
|
||||
(config.comment_summary_in_pr === 'on-failure' &&
|
||||
process.exitCode === core.ExitCode.Failure)
|
||||
) {
|
||||
await commentPr(core.summary)
|
||||
}
|
||||
await commentPr(core.summary, config)
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError && error.status === 404) {
|
||||
core.setFailed(
|
||||
@@ -171,19 +190,25 @@ async function run(): Promise<void> {
|
||||
|
||||
function printVulnerabilitiesBlock(
|
||||
addedChanges: Changes,
|
||||
minSeverity: Severity
|
||||
minSeverity: Severity,
|
||||
warnOnly: boolean
|
||||
): void {
|
||||
let failed = false
|
||||
let vulFound = false
|
||||
core.group('Vulnerabilities', async () => {
|
||||
if (addedChanges.length > 0) {
|
||||
for (const change of addedChanges) {
|
||||
printChangeVulnerabilities(change)
|
||||
}
|
||||
failed = true
|
||||
vulFound = true
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
core.setFailed('Dependency review detected vulnerable packages.')
|
||||
if (vulFound) {
|
||||
const msg = 'Dependency review detected vulnerable packages.'
|
||||
if (warnOnly) {
|
||||
core.warning(msg)
|
||||
} else {
|
||||
core.setFailed(msg)
|
||||
}
|
||||
} else {
|
||||
core.info(
|
||||
`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.`
|
||||
@@ -206,13 +231,19 @@ function printChangeVulnerabilities(change: Change): void {
|
||||
}
|
||||
|
||||
function printLicensesBlock(
|
||||
invalidLicenseChanges: Record<string, Changes>
|
||||
invalidLicenseChanges: Record<string, Changes>,
|
||||
warnOnly: boolean
|
||||
): void {
|
||||
core.group('Licenses', async () => {
|
||||
if (invalidLicenseChanges.forbidden.length > 0) {
|
||||
core.info('\nThe following dependencies have incompatible licenses:')
|
||||
printLicensesError(invalidLicenseChanges.forbidden)
|
||||
core.setFailed('Dependency review detected incompatible licenses.')
|
||||
const msg = 'Dependency review detected incompatible licenses.'
|
||||
if (warnOnly) {
|
||||
core.warning(msg)
|
||||
} else {
|
||||
core.setFailed(msg)
|
||||
}
|
||||
}
|
||||
if (invalidLicenseChanges.unresolved.length > 0) {
|
||||
core.warning(
|
||||
@@ -248,6 +279,29 @@ function printNullLicenses(changes: Changes): void {
|
||||
}
|
||||
}
|
||||
|
||||
function printScorecardBlock(
|
||||
scorecard: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
core.group('Scorecard', async () => {
|
||||
if (scorecard) {
|
||||
for (const dependency of scorecard.dependencies) {
|
||||
if (
|
||||
dependency.scorecard?.score &&
|
||||
dependency.scorecard?.score < config.warn_on_openssf_scorecard_level
|
||||
) {
|
||||
core.info(
|
||||
`${styles.color.red.open}${dependency.change.ecosystem}/${dependency.change.name}: OpenSSF Scorecard Score: ${dependency?.scorecard?.score}${styles.red.close}`
|
||||
)
|
||||
}
|
||||
core.info(
|
||||
`${dependency.change.ecosystem}/${dependency.change.name}: OpenSSF Scorecard Score: ${dependency?.scorecard?.score}`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function renderSeverity(
|
||||
severity: 'critical' | 'high' | 'moderate' | 'low'
|
||||
): string {
|
||||
@@ -316,4 +370,24 @@ function printDeniedDependencies(
|
||||
})
|
||||
}
|
||||
|
||||
async function createScorecardWarnings(
|
||||
scorecards: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
): Promise<void> {
|
||||
// Iterate through the list of scorecards, and if the score is less than the threshold, send a warning
|
||||
for (const dependency of scorecards.dependencies) {
|
||||
if (
|
||||
dependency.scorecard?.score &&
|
||||
dependency.scorecard?.score < config.warn_on_openssf_scorecard_level
|
||||
) {
|
||||
core.warning(
|
||||
`${dependency.change.ecosystem}/${dependency.change.name} has an OpenSSF Scorecard of ${dependency.scorecard?.score}, which is less than this repository's threshold of ${config.warn_on_openssf_scorecard_level}.`,
|
||||
{
|
||||
title: 'OpenSSF Scorecard Warning'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
+46
-1
@@ -51,6 +51,8 @@ export const ConfigurationOptionsSchema = z
|
||||
head_ref: z.string().optional(),
|
||||
retry_on_snapshot_warnings: z.boolean().default(false),
|
||||
retry_on_snapshot_warnings_timeout: z.number().default(120),
|
||||
show_openssf_scorecard: z.boolean().optional().default(true),
|
||||
warn_on_openssf_scorecard_level: z.number().default(3),
|
||||
comment_summary_in_pr: z
|
||||
.union([
|
||||
z.preprocess(
|
||||
@@ -59,7 +61,8 @@ export const ConfigurationOptionsSchema = z
|
||||
),
|
||||
z.enum(['always', 'never', 'on-failure'])
|
||||
])
|
||||
.default('never')
|
||||
.default('never'),
|
||||
warn_only: z.boolean().default(false)
|
||||
})
|
||||
.transform(config => {
|
||||
if (config.comment_summary_in_pr === true) {
|
||||
@@ -99,9 +102,51 @@ export const ComparisonResponseSchema = z.object({
|
||||
snapshot_warnings: z.string()
|
||||
})
|
||||
|
||||
export const ScorecardApiSchema = z.object({
|
||||
date: z.string(),
|
||||
repo: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
commit: z.string()
|
||||
})
|
||||
.nullish(),
|
||||
scorecard: z
|
||||
.object({
|
||||
version: z.string(),
|
||||
commit: z.string()
|
||||
})
|
||||
.nullish(),
|
||||
checks: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
documentation: z.object({
|
||||
shortDescription: z.string(),
|
||||
url: z.string()
|
||||
}),
|
||||
score: z.string(),
|
||||
reason: z.string(),
|
||||
details: z.array(z.string())
|
||||
})
|
||||
)
|
||||
.nullish(),
|
||||
score: z.number().nullish()
|
||||
})
|
||||
|
||||
export const ScorecardSchema = z.object({
|
||||
dependencies: z.array(
|
||||
z.object({
|
||||
change: ChangeSchema,
|
||||
scorecard: ScorecardApiSchema.nullish()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
export type Change = z.infer<typeof ChangeSchema>
|
||||
export type Changes = z.infer<typeof ChangesSchema>
|
||||
export type ComparisonResponse = z.infer<typeof ComparisonResponseSchema>
|
||||
export type ConfigurationOptions = z.infer<typeof ConfigurationOptionsSchema>
|
||||
export type Severity = z.infer<typeof SeveritySchema>
|
||||
export type Scope = (typeof SCOPES)[number]
|
||||
export type Scorecard = z.infer<typeof ScorecardSchema>
|
||||
export type ScorecardApi = z.infer<typeof ScorecardApiSchema>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import {Change, Scorecard, ScorecardApi} from './schemas'
|
||||
import * as core from '@actions/core'
|
||||
|
||||
export async function getScorecardLevels(
|
||||
changes: Change[]
|
||||
): Promise<Scorecard> {
|
||||
const data: Scorecard = {dependencies: []} as Scorecard
|
||||
for (const change of changes) {
|
||||
const ecosystem = change.ecosystem
|
||||
const packageName = change.name
|
||||
const version = change.version
|
||||
|
||||
//Get the project repository
|
||||
let repositoryUrl = change.source_repository_url
|
||||
//If the repository_url includes the protocol, remove it
|
||||
if (repositoryUrl?.startsWith('https://')) {
|
||||
repositoryUrl = repositoryUrl.replace('https://', '')
|
||||
}
|
||||
|
||||
// If GitHub API doesn't have the repository URL, query deps.dev for it.
|
||||
if (repositoryUrl) {
|
||||
// Call the deps.dev API to get the repository URL from there
|
||||
repositoryUrl = await getProjectUrl(ecosystem, packageName, version)
|
||||
}
|
||||
|
||||
// Get the scorecard API response from the scorecards API
|
||||
let scorecardApi: ScorecardApi | null = null
|
||||
if (repositoryUrl) {
|
||||
try {
|
||||
scorecardApi = await getScorecard(repositoryUrl)
|
||||
} catch (error: unknown) {
|
||||
core.debug(`Error querying for scorecard: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
data.dependencies.push({
|
||||
change,
|
||||
scorecard: scorecardApi
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
async function getScorecard(repositoryUrl: string): Promise<ScorecardApi> {
|
||||
const apiRoot = 'https://api.securityscorecards.dev/'
|
||||
let scorecardResponse: ScorecardApi = {} as ScorecardApi
|
||||
|
||||
const url = `${apiRoot}/projects/${repositoryUrl}`
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
scorecardResponse = await response.json()
|
||||
} else {
|
||||
core.debug(`Couldn't get scorecard data for ${repositoryUrl}`)
|
||||
}
|
||||
return scorecardResponse
|
||||
}
|
||||
|
||||
export async function getProjectUrl(
|
||||
ecosystem: string,
|
||||
packageName: string,
|
||||
version: string
|
||||
): Promise<string> {
|
||||
core.debug(`Getting deps.dev data for ${packageName} ${version}`)
|
||||
const depsDevAPIRoot = 'https://api.deps.dev'
|
||||
const url = `${depsDevAPIRoot}/v3alpha/systems/${ecosystem}/packages/${packageName}/versions/${version}`
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.relatedProjects.length > 0) {
|
||||
return data.relatedProjects[0].projectKey.id
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
+95
-8
@@ -1,5 +1,5 @@
|
||||
import * as core from '@actions/core'
|
||||
import {ConfigurationOptions, Changes, Change} from './schemas'
|
||||
import {ConfigurationOptions, Changes, Change, Scorecard} from './schemas'
|
||||
import {SummaryTableRow} from '@actions/core/lib/summary'
|
||||
import {InvalidLicenseChanges, InvalidLicenseChangeTypes} from './licenses'
|
||||
import {groupDependenciesByManifest, getManifestsSet, renderUrl} from './utils'
|
||||
@@ -14,22 +14,30 @@ export function addSummaryToSummary(
|
||||
vulnerableChanges: Changes,
|
||||
invalidLicenseChanges: InvalidLicenseChanges,
|
||||
deniedChanges: Changes,
|
||||
scorecard: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
const scorecardWarnings = countScorecardWarnings(scorecard, config)
|
||||
const licenseIssues = countLicenseIssues(invalidLicenseChanges)
|
||||
|
||||
core.summary.addHeading('Dependency Review', 1)
|
||||
|
||||
if (
|
||||
vulnerableChanges.length === 0 &&
|
||||
countLicenseIssues(invalidLicenseChanges) === 0 &&
|
||||
deniedChanges.length === 0
|
||||
licenseIssues === 0 &&
|
||||
deniedChanges.length === 0 &&
|
||||
scorecardWarnings === 0
|
||||
) {
|
||||
if (!config.license_check) {
|
||||
core.summary.addRaw(`${icons.check} No vulnerabilities found.`)
|
||||
} else if (!config.vulnerability_check) {
|
||||
core.summary.addRaw(`${icons.check} No license issues found.`)
|
||||
const issueTypes = [
|
||||
config.vulnerability_check ? 'vulnerabilities' : '',
|
||||
config.license_check ? 'license issues' : '',
|
||||
config.show_openssf_scorecard ? 'OpenSSF Scorecard issues' : ''
|
||||
]
|
||||
if (issueTypes.filter(Boolean).length === 0) {
|
||||
core.summary.addRaw(`${icons.check} No issues found.`)
|
||||
} else {
|
||||
core.summary.addRaw(
|
||||
`${icons.check} No vulnerabilities or license issues found.`
|
||||
`${icons.check} No ${issueTypes.filter(Boolean).join(' or ')} found.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,11 +73,31 @@ export function addSummaryToSummary(
|
||||
deniedChanges.length
|
||||
} package(s) denied.`
|
||||
]
|
||||
: []),
|
||||
...(config.show_openssf_scorecard && scorecardWarnings > 0
|
||||
? [
|
||||
`${checkOrWarnIcon(scorecardWarnings)} ${scorecardWarnings ? scorecardWarnings : 'No'} packages with OpenSSF Scorecard issues.`
|
||||
]
|
||||
: [])
|
||||
])
|
||||
.addRaw('See the Details below.')
|
||||
}
|
||||
|
||||
function countScorecardWarnings(
|
||||
scorecard: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
): number {
|
||||
return scorecard.dependencies.reduce(
|
||||
(total, dependency) =>
|
||||
total +
|
||||
(dependency.scorecard?.score &&
|
||||
dependency.scorecard?.score < config.warn_on_openssf_scorecard_level
|
||||
? 1
|
||||
: 0),
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
export function addChangeVulnerabilitiesToSummary(
|
||||
vulnerableChanges: Changes,
|
||||
severity: string
|
||||
@@ -249,6 +277,65 @@ function snapshotWarningRecommendation(
|
||||
return 'Re-running this action after a short time may resolve the issue.'
|
||||
}
|
||||
|
||||
export function addScorecardToSummary(
|
||||
scorecard: Scorecard,
|
||||
config: ConfigurationOptions
|
||||
): void {
|
||||
core.summary.addHeading('OpenSSF Scorecard', 2)
|
||||
if (scorecard.dependencies.length > 10) {
|
||||
core.summary.addRaw(`<details><summary>Scorecard details</summary>`, true)
|
||||
}
|
||||
core.summary.addRaw(
|
||||
`<table><tr><th>Package</th><th>Version</th><th>Score</th><th>Details</th></tr>`,
|
||||
true
|
||||
)
|
||||
for (const dependency of scorecard.dependencies) {
|
||||
core.debug('Adding scorecard to summary')
|
||||
core.debug(`Overall score ${dependency.scorecard?.score}`)
|
||||
|
||||
// Set the icon based on the overall score value
|
||||
let overallIcon = ''
|
||||
if (dependency.scorecard?.score) {
|
||||
overallIcon =
|
||||
dependency.scorecard?.score < config.warn_on_openssf_scorecard_level
|
||||
? ':warning:'
|
||||
: ':green_circle:'
|
||||
}
|
||||
|
||||
//Add a row for the dependency
|
||||
core.summary.addRaw(
|
||||
`<tr><td>${dependency.change.source_repository_url ? `<a href="https://${dependency.change.source_repository_url}">` : ''} ${dependency.change.ecosystem}/${dependency.change.name} ${dependency.change.source_repository_url ? `</a>` : ''}</td><td>${dependency.change.version}</td>
|
||||
<td>${overallIcon} ${dependency.scorecard?.score === undefined || dependency.scorecard?.score === null ? 'Unknown' : dependency.scorecard?.score}</td>`,
|
||||
false
|
||||
)
|
||||
|
||||
//Add details table in the last column
|
||||
if (dependency.scorecard?.checks !== undefined) {
|
||||
let detailsTable =
|
||||
'<table><tr><th>Check</th><th>Score</th><th>Reason</th></tr>'
|
||||
for (const check of dependency.scorecard?.checks || []) {
|
||||
const icon =
|
||||
parseFloat(check.score) < config.warn_on_openssf_scorecard_level
|
||||
? ':warning:'
|
||||
: ':green_circle:'
|
||||
|
||||
detailsTable += `<tr><td>${check.name}</td><td>${icon} ${check.score}</td><td>${check.reason}</td></tr>`
|
||||
}
|
||||
detailsTable += `</table>`
|
||||
core.summary.addRaw(
|
||||
`<td><details><summary>Details</summary>${detailsTable}</details></td></tr>`,
|
||||
true
|
||||
)
|
||||
} else {
|
||||
core.summary.addRaw('<td>Unknown</td></tr>', true)
|
||||
}
|
||||
}
|
||||
core.summary.addRaw(`</table>`)
|
||||
if (scorecard.dependencies.length > 10) {
|
||||
core.summary.addRaw(`</details>`)
|
||||
}
|
||||
}
|
||||
|
||||
export function addSnapshotWarnings(
|
||||
config: ConfigurationOptions,
|
||||
warnings: string
|
||||
|
||||
Reference in New Issue
Block a user