Compare commits

..
Author SHA1 Message Date
Sarah Aladetan 716b322ec9 add allow-ghsas input to action.yml 2022-09-23 19:59:39 +00:00
Sarah AladetanandFederico Builes 12ae1bd550 Update wording in README.md
Co-authored-by: Federico Builes <[email protected]>
2022-09-23 12:32:46 -07:00
Sarah Aladetan bcb52636bd build and package allow-ghsas 2022-09-22 22:58:43 +00:00
Sarah Aladetan 241ff73141 add doc on allow-ghsas to readme 2022-09-22 22:44:17 +00:00
Sarah Aladetan 062b749663 revise ghsa filter 2022-09-22 22:36:34 +00:00
Sarah Aladetan 4f00b72b84 filter allowed ghsas in action flow 2022-09-22 22:25:21 +00:00
Sarah Aladetan 602f968ea2 create a filter for vulns that are on the allowlist 2022-09-22 21:36:26 +00:00
Sarah Aladetan bd61ea0d9e create config option for ghsa allowlist 2022-09-22 21:34:18 +00:00
Federico Builes 2843194510 Updating version. 2022-09-22 14:27:24 +02:00
Federico Builes 6944531f76 Update README.md 2022-09-22 14:26:27 +02:00
Federico Builes 29cdbbed37 Merge pull request #228 from actions/external-config
Add external configuration file
2022-09-22 14:22:39 +02:00
12 changed files with 183 additions and 32 deletions
+33 -13
View File
@@ -67,19 +67,19 @@ jobs:
Configure this action by either using an external configuration file, Configure this action by either using an external configuration file,
or by inlining these options in your workflow file. or by inlining these options in your workflow file.
### Options ## Configuration Options
#### config-file ### config-file
A string representing the path to an external configuraton file. By A string representing the path to an external configuraton file. By
default external configuration files are not used. default external configuration files are not used.
**Possible values**: A string representing the absolute path to the **Possible values**: A string representing the absolute path to the
configuration file. configuration file.
**Example**: `config-file: ./.github/dependency-review-config.yml`. **Example**: `config-file: ./.github/dependency-review-config.yml`.
#### fail-on-severity ### fail-on-severity
Configure the severity level for alerting. See "[Vulnerability Severity](https://github.com/actions/dependency-review-action#vulnerability-severity)". Configure the severity level for alerting. See "[Vulnerability Severity](https://github.com/actions/dependency-review-action#vulnerability-severity)".
@@ -87,7 +87,7 @@ Configure the severity level for alerting. See "[Vulnerability Severity](https:/
**Example**: `fail-on-severity: moderate`. **Example**: `fail-on-severity: moderate`.
#### fail-on-scopes ### fail-on-scopes
A list of strings representing the build environments you want to A list of strings representing the build environments you want to
support. The default value is `development, runtime`. support. The default value is `development, runtime`.
@@ -97,13 +97,14 @@ support. The default value is `development, runtime`.
**Inline example**: `fail-on-scopes: development, runtime` **Inline example**: `fail-on-scopes: development, runtime`
**YAML example**: **YAML example**:
```yaml ```yaml
# this prevents scanning development dependencies # this prevents scanning development dependencies
fail-on-scopes: fail-on-scopes:
- runtime - runtime
``` ```
#### allow-licenses ### allow-licenses
Only allow the licenses in this list. See "[Licenses](https://github.com/actions/dependency-review-action#licenses)". Only allow the licenses in this list. See "[Licenses](https://github.com/actions/dependency-review-action#licenses)".
@@ -113,13 +114,14 @@ https://docs.github.com/en/rest/licenses.
**Inline example**: `allow-licenses: BSD-3-Clause, MIT` **Inline example**: `allow-licenses: BSD-3-Clause, MIT`
**YAML example**: **YAML example**:
```yaml ```yaml
allow-licenses: allow-licenses:
- BSD-3-Clause - BSD-3-Clause
- MIT - MIT
``` ```
#### deny-licenses ### deny-licenses
Add a custom list of licenses you want to block. See Add a custom list of licenses you want to block. See
"[Licenses](https://github.com/actions/dependency-review-action#licenses)". "[Licenses](https://github.com/actions/dependency-review-action#licenses)".
@@ -130,13 +132,30 @@ https://docs.github.com/en/rest/licenses.
**Inline example**: `deny-licenses: LGPL-2.0, BSD-2-Clause` **Inline example**: `deny-licenses: LGPL-2.0, BSD-2-Clause`
**YAML example**: **YAML example**:
```yaml ```yaml
deny-licenses: deny-licenses:
- LGPL-2.0 - LGPL-2.0
- BSD-2-Clause - BSD-2-Clause
``` ```
#### base-ref/head-ref ### allow-ghsas
Add a custom list of GitHub Advisory IDs that can be skipped during detection.
**Possible values**: Any valid advisory GHSA ids.
**Inline example**: `allow-ghsas: GHSA-abcd-1234-5679, GHSA-efgh-1234-5679`
**YAML example**:
```yaml
allow-ghsas:
- GHSA-abcd-1234-5679
- GHSA-efgh-1234-5679
```
### base-ref/head-ref
Provide custom git references for the git base/head when performing Provide custom git references for the git base/head when performing
the comparison. If you are using pull requests, or the comparison. If you are using pull requests, or
@@ -146,6 +165,7 @@ this. The values need to be specified for all other event types.
**Possible values**: Any valid git ref(s) in your project. **Possible values**: Any valid git ref(s) in your project.
**Example**: **Example**:
```yaml ```yaml
base-ref: 8bb8a58d6a4028b6c2e314d5caaf273f57644896 base-ref: 8bb8a58d6a4028b6c2e314d5caaf273f57644896
head-ref: 69af5638bf660cf218aad5709a4c100e42a2f37b head-ref: 69af5638bf660cf218aad5709a4c100e42a2f37b
@@ -163,18 +183,18 @@ file:
- name: Dependency Review - name: Dependency Review
uses: actions/dependency-review-action@v2 uses: actions/dependency-review-action@v2
with: with:
config-file: "./.github/dependency-review-config.yml" config-file: './.github/dependency-review-config.yml'
``` ```
And then create the file in the path you just specified. **All of these fields are And then create the file in the path you just specified. **All of these fields are
optional**: optional**:
```yaml ```yaml
fail-on-severity: "critical" fail-on-severity: 'critical'
allow-licenses: allow-licenses:
- "GPL-3.0" - 'GPL-3.0'
- "BSD-3-Clause" - 'BSD-3-Clause'
- "MIT" - 'MIT'
``` ```
### Inline Configuration ### Inline Configuration
+15
View File
@@ -16,6 +16,7 @@ function clearInputs() {
'FAIL-ON-SCOPES', 'FAIL-ON-SCOPES',
'ALLOW-LICENSES', 'ALLOW-LICENSES',
'DENY-LICENSES', 'DENY-LICENSES',
'ALLOW-GHSAS',
'CONFIG-FILE', 'CONFIG-FILE',
'BASE-REF', 'BASE-REF',
'HEAD-REF' 'HEAD-REF'
@@ -160,3 +161,17 @@ test('it raises an error when given invalid scope', async () => {
setInput('fail-on-scopes', 'runtime, zombies') setInput('fail-on-scopes', 'runtime, zombies')
expect(() => readConfig()).toThrow() expect(() => readConfig()).toThrow()
}) })
test('it defaults to an empty GHSA allowlist', async () => {
const options = readConfig()
expect(options.allow_ghsas).toEqual(undefined)
})
test('it successfully parses GHSA allowlist', async () => {
setInput('allow-ghsas', 'GHSA-abcd-1234-5679, GHSA-efgh-1234-5679')
const options = readConfig()
expect(options.allow_ghsas).toEqual([
'GHSA-abcd-1234-5679',
'GHSA-efgh-1234-5679'
])
})
+44 -1
View File
@@ -1,6 +1,10 @@
import {expect, test} from '@jest/globals' import {expect, test} from '@jest/globals'
import {Change, Changes} from '../src/schemas' import {Change, Changes} from '../src/schemas'
import {filterChangesBySeverity, filterChangesByScopes} from '../src/filter' import {
filterChangesBySeverity,
filterChangesByScopes,
filterOutAllowedAdvisories
} from '../src/filter'
let npmChange: Change = { let npmChange: Change = {
manifest: 'package.json', manifest: 'package.json',
@@ -48,6 +52,19 @@ let rubyChange: Change = {
] ]
} }
let noVulnNpmChange: Change = {
manifest: 'package.json',
change_type: 'added',
ecosystem: 'npm',
name: 'helpful',
version: '1.0.0',
package_url: 'pkg:npm/[email protected]',
license: 'MIT',
source_repository_url: 'github.com/some-repo',
scope: 'runtime',
vulnerabilities: []
}
test('it properly filters changes by severity', async () => { test('it properly filters changes by severity', async () => {
const changes = [npmChange, rubyChange] const changes = [npmChange, rubyChange]
let result = filterChangesBySeverity('high', changes) let result = filterChangesBySeverity('high', changes)
@@ -72,3 +89,29 @@ test('it properly filters changes by scope', async () => {
result = filterChangesByScopes(['runtime', 'development'], changes) result = filterChangesByScopes(['runtime', 'development'], changes)
expect(result).toEqual([npmChange, rubyChange]) expect(result).toEqual([npmChange, rubyChange])
}) })
test('it properly filters changes with allowed vulnerabilities', async () => {
const changes = [npmChange, rubyChange, noVulnNpmChange]
let result = filterOutAllowedAdvisories(['notrealGHSAID'], changes)
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
result = filterOutAllowedAdvisories(['first-random_string'], changes)
expect(result).toEqual([rubyChange, noVulnNpmChange])
result = filterOutAllowedAdvisories(
['second-random_string', 'third-random_string'],
changes
)
expect(result).toEqual([npmChange, noVulnNpmChange])
result = filterOutAllowedAdvisories(
['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 = filterOutAllowedAdvisories(['second-random_string'], changes)
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
})
+3
View File
@@ -29,6 +29,9 @@ inputs:
deny-licenses: deny-licenses:
description: Comma-separated list of forbidden licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause") description: Comma-separated list of forbidden licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
required: false required: false
allow-ghsas:
description: Comma-separated list of allowed Github Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
required: false
runs: runs:
using: 'node16' using: 'node16'
main: 'dist/index.js' main: 'dist/index.js'
Generated Vendored
+34 -7
View File
@@ -222,10 +222,12 @@ function run() {
}; };
const scopes = config.fail_on_scopes; const scopes = config.fail_on_scopes;
const scopedChanges = (0, filter_1.filterChangesByScopes)(scopes, changes); const scopedChanges = (0, filter_1.filterChangesByScopes)(scopes, changes);
const addedChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, scopedChanges).filter(change => change.change_type === 'added' && const allowedGhsas = config.allow_ghsas || [];
const filteredChanges = (0, filter_1.filterOutAllowedAdvisories)(allowedGhsas, scopedChanges);
const addedChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, filteredChanges).filter(change => change.change_type === 'added' &&
change.vulnerabilities !== undefined && change.vulnerabilities !== undefined &&
change.vulnerabilities.length > 0); change.vulnerabilities.length > 0);
const [licenseErrors, unknownLicenses] = (0, licenses_1.getDeniedLicenseChanges)(scopedChanges, licenses); const [licenseErrors, unknownLicenses] = (0, licenses_1.getDeniedLicenseChanges)(filteredChanges, licenses);
summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses); summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses);
if (addedChanges.length > 0) { if (addedChanges.length > 0) {
for (const change of addedChanges) { for (const change of addedChanges) {
@@ -371,6 +373,7 @@ exports.ConfigurationOptionsSchema = z
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']), fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
allow_licenses: z.array(z.string()).default([]), allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]), deny_licenses: z.array(z.string()).default([]),
allow_ghsas: z.array(z.string()).default([]),
config_file: z.string().optional().default('false'), config_file: z.string().optional().default('false'),
base_ref: z.string(), base_ref: z.string(),
head_ref: z.string() head_ref: z.string()
@@ -14971,18 +14974,20 @@ function readInlineConfig() {
.array(z.enum(schemas_1.SCOPES)) .array(z.enum(schemas_1.SCOPES))
.default(['runtime']) .default(['runtime'])
.parse(parseList(getOptionalInput('fail-on-scopes'))); .parse(parseList(getOptionalInput('fail-on-scopes')));
const allow_licenses = getOptionalInput('allow-licenses'); const allow_licenses = parseList(getOptionalInput('allow-licenses'));
const deny_licenses = getOptionalInput('deny-licenses'); const deny_licenses = parseList(getOptionalInput('deny-licenses'));
if (allow_licenses !== undefined && deny_licenses !== undefined) { if (allow_licenses !== undefined && deny_licenses !== undefined) {
throw new Error("Can't specify both allow_licenses and deny_licenses"); throw new Error("Can't specify both allow_licenses and deny_licenses");
} }
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'));
const base_ref = getOptionalInput('base-ref'); const base_ref = getOptionalInput('base-ref');
const head_ref = getOptionalInput('head-ref'); const head_ref = getOptionalInput('head-ref');
return { return {
fail_on_severity, fail_on_severity,
fail_on_scopes, fail_on_scopes,
allow_licenses: parseList(allow_licenses), allow_licenses,
deny_licenses: parseList(deny_licenses), deny_licenses,
allow_ghsas,
base_ref, base_ref,
head_ref head_ref
}; };
@@ -15018,7 +15023,7 @@ exports.readConfigFile = readConfigFile;
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.filterChangesByScopes = exports.filterChangesBySeverity = void 0; exports.filterOutAllowedAdvisories = exports.filterChangesByScopes = exports.filterChangesBySeverity = void 0;
const schemas_1 = __nccwpck_require__(1129); const schemas_1 = __nccwpck_require__(1129);
function filterChangesBySeverity(severity, changes) { function filterChangesBySeverity(severity, changes) {
const severityIdx = schemas_1.SEVERITIES.indexOf(severity); const severityIdx = schemas_1.SEVERITIES.indexOf(severity);
@@ -15051,6 +15056,27 @@ function filterChangesByScopes(scopes, changes) {
return filteredChanges; return filteredChanges;
} }
exports.filterChangesByScopes = filterChangesByScopes; exports.filterChangesByScopes = filterChangesByScopes;
function filterOutAllowedAdvisories(ghsas, changes) {
const filteredChanges = changes.filter(change => {
const noAdvisories = change.vulnerabilities === undefined ||
change.vulnerabilities.length === 0;
if (noAdvisories) {
return true;
}
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 filteredChanges;
}
exports.filterOutAllowedAdvisories = filterOutAllowedAdvisories;
/***/ }), /***/ }),
@@ -15120,6 +15146,7 @@ exports.ConfigurationOptionsSchema = z
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']), fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
allow_licenses: z.array(z.string()).default([]), allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]), deny_licenses: z.array(z.string()).default([]),
allow_ghsas: z.array(z.string()).default([]),
config_file: z.string().optional().default('false'), config_file: z.string().optional().default('false'),
base_ref: z.string(), base_ref: z.string(),
head_ref: z.string() head_ref: z.string()
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "dependency-review-action", "name": "dependency-review-action",
"version": "2.2.0", "version": "2.3.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "dependency-review-action", "name": "dependency-review-action",
"version": "2.2.0", "version": "2.3.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.9.1", "@actions/core": "^1.9.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "dependency-review-action", "name": "dependency-review-action",
"version": "2.2.0", "version": "2.3.0",
"private": true, "private": true,
"description": "A GitHub Action for Dependency Review", "description": "A GitHub Action for Dependency Review",
"main": "lib/main.js", "main": "lib/main.js",
+7 -4
View File
@@ -47,21 +47,24 @@ export function readInlineConfig(): ConfigurationOptions {
.default(['runtime']) .default(['runtime'])
.parse(parseList(getOptionalInput('fail-on-scopes'))) .parse(parseList(getOptionalInput('fail-on-scopes')))
const allow_licenses = getOptionalInput('allow-licenses') const allow_licenses = parseList(getOptionalInput('allow-licenses'))
const deny_licenses = getOptionalInput('deny-licenses') const deny_licenses = parseList(getOptionalInput('deny-licenses'))
if (allow_licenses !== undefined && deny_licenses !== undefined) { if (allow_licenses !== undefined && deny_licenses !== undefined) {
throw new Error("Can't specify both allow_licenses and deny_licenses") throw new Error("Can't specify both allow_licenses and deny_licenses")
} }
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
const base_ref = getOptionalInput('base-ref') const base_ref = getOptionalInput('base-ref')
const head_ref = getOptionalInput('head-ref') const head_ref = getOptionalInput('head-ref')
return { return {
fail_on_severity, fail_on_severity,
fail_on_scopes, fail_on_scopes,
allow_licenses: parseList(allow_licenses), allow_licenses,
deny_licenses: parseList(deny_licenses), deny_licenses,
allow_ghsas,
base_ref, base_ref,
head_ref head_ref
} }
+28
View File
@@ -46,3 +46,31 @@ export function filterChangesByScopes(
return filteredChanges return filteredChanges
} }
export function filterOutAllowedAdvisories(
ghsas: string[],
changes: Changes
): Changes {
const filteredChanges = changes.filter(change => {
const noAdvisories =
change.vulnerabilities === undefined ||
change.vulnerabilities.length === 0
if (noAdvisories) {
return true
}
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 filteredChanges
}
+14 -3
View File
@@ -5,7 +5,11 @@ import styles from 'ansi-styles'
import {RequestError} from '@octokit/request-error' import {RequestError} from '@octokit/request-error'
import {Change, Severity, Scope} from './schemas' import {Change, Severity, Scope} from './schemas'
import {readConfig} from '../src/config' import {readConfig} from '../src/config'
import {filterChangesBySeverity, filterChangesByScopes} from '../src/filter' import {
filterChangesBySeverity,
filterChangesByScopes,
filterOutAllowedAdvisories
} from '../src/filter'
import {getDeniedLicenseChanges} from './licenses' import {getDeniedLicenseChanges} from './licenses'
import * as summary from './summary' import * as summary from './summary'
import {getRefs} from './git-refs' import {getRefs} from './git-refs'
@@ -34,9 +38,16 @@ async function run(): Promise<void> {
const scopedChanges = filterChangesByScopes(scopes as Scope[], changes) const scopedChanges = filterChangesByScopes(scopes as Scope[], changes)
const allowedGhsas: string[] = config.allow_ghsas || []
const filteredChanges = filterOutAllowedAdvisories(
allowedGhsas,
scopedChanges
)
const addedChanges = filterChangesBySeverity( const addedChanges = filterChangesBySeverity(
minSeverity as Severity, minSeverity as Severity,
scopedChanges filteredChanges
).filter( ).filter(
change => change =>
change.change_type === 'added' && change.change_type === 'added' &&
@@ -45,7 +56,7 @@ async function run(): Promise<void> {
) )
const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges( const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges(
scopedChanges, filteredChanges,
licenses licenses
) )
+1
View File
@@ -40,6 +40,7 @@ export const ConfigurationOptionsSchema = z
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']), fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
allow_licenses: z.array(z.string()).default([]), allow_licenses: z.array(z.string()).default([]),
deny_licenses: z.array(z.string()).default([]), deny_licenses: z.array(z.string()).default([]),
allow_ghsas: z.array(z.string()).default([]),
config_file: z.string().optional().default('false'), config_file: z.string().optional().default('false'),
base_ref: z.string(), base_ref: z.string(),
head_ref: z.string() head_ref: z.string()