Merge pull request #432 from tgrall/issue-431-fail-on-severity-none

Add none as option for fail-on-severity
This commit is contained in:
Justin Holguín
2024-02-11 14:00:39 -08:00
committed by GitHub
11 changed files with 105 additions and 45 deletions
+19 -16
View File
@@ -66,27 +66,30 @@ jobs:
Configure this action by either inlining these options in your workflow file, or by using an external configuration file. All configuration options are optional. Configure this action by either inlining these options in your workflow file, or by using an external configuration file. All configuration options are optional.
| Option | Usage | Possible values | Default value | | Option | Usage | Possible values | Default value |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------- | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
| `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` | | `fail-on-severity` | Defines the threshold for the level of severity. The action will fail on any pull requests that introduce vulnerabilities of the specified severity level or higher. | `low`, `moderate`, `high`, `critical` | `low` |
| `allow-licenses`\* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none | | `allow-licenses`\* | Contains a list of allowed licenses. The action will fail on pull requests that introduce dependencies with licenses that do not match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
| `deny-licenses`\* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none | | `deny-licenses`\* | Contains a list of prohibited licenses. The action will fail on pull requests that introduce dependencies with licenses that match the list. | Any [SPDX-compliant identifier(s)](https://spdx.org/licenses/) | none |
| `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` | | `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 | | `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` | | `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` | | `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 | | `allow-dependencies-licenses`\* | Contains a list of packages that will be excluded from license checks. | Any package(s) in [purl](https://github.com/package-url/purl-spec) format | none |
| `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none | | `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none |
| `comment-summary-in-pr` | Enable or disable reporting the review summary as a comment in the pull request. If enabled, you must give the workflow or job permission `pull-requests: write`. | `always`, `on-failure`, `never` | `never` | | `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-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 | | `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`\* | 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 | | `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` |
\*not supported for use with GitHub Enterprise Server \*not supported for use with GitHub Enterprise Server
†will be supported with GitHub Enterprise Server 3.8 †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 ### Inline Configuration
You can pass options to the Dependency Review GitHub Action using your workflow file. You can pass options to the Dependency Review GitHub Action using your workflow file.
+5
View File
@@ -26,6 +26,11 @@ test('it reads custom configs', async () => {
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2']) 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 () => { test('it defaults to empty allow/deny lists ', async () => {
const config = await readConfig() const config = await readConfig()
+2 -1
View File
@@ -28,7 +28,8 @@ const defaultConfig: ConfigurationOptions = {
deny_groups: [], deny_groups: [],
comment_summary_in_pr: true, comment_summary_in_pr: true,
retry_on_snapshot_warnings: false, retry_on_snapshot_warnings: false,
retry_on_snapshot_warnings_timeout: 120 retry_on_snapshot_warnings_timeout: 120,
warn_only: false
} }
const changesWithEmptyManifests: Changes = [ const changesWithEmptyManifests: Changes = [
+2 -1
View File
@@ -18,7 +18,8 @@ export function clearInputs(): void {
'CONFIG-FILE', 'CONFIG-FILE',
'BASE-REF', 'BASE-REF',
'HEAD-REF', 'HEAD-REF',
'COMMENT-SUMMARY-IN-PR' 'COMMENT-SUMMARY-IN-PR',
'WARN-ONLY'
] ]
// eslint-disable-next-line github/array-foreach // eslint-disable-next-line github/array-foreach
+5
View File
@@ -61,6 +61,11 @@ inputs:
description: Number of seconds to wait before stopping snapshot retries. description: Number of seconds to wait before stopping snapshot retries.
required: false required: false
default: 120 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
runs: runs:
using: 'node20' using: 'node20'
main: 'dist/index.js' main: 'dist/index.js'
Generated Vendored
+35 -13
View File
@@ -609,7 +609,13 @@ function run() {
} }
const scopedChanges = (0, filter_1.filterChangesByScopes)(config.fail_on_scopes, changes); const scopedChanges = (0, filter_1.filterChangesByScopes)(config.fail_on_scopes, changes);
const filteredChanges = (0, filter_1.filterAllowedAdvisories)(config.allow_ghsas, scopedChanges); const filteredChanges = (0, filter_1.filterAllowedAdvisories)(config.allow_ghsas, scopedChanges);
const minSeverity = config.fail_on_severity; const failOnSeverityParams = config.fail_on_severity;
const warnOnly = config.warn_only;
let minSeverity = '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 = (0, filter_1.filterChangesBySeverity)(minSeverity, filteredChanges); const vulnerableChanges = (0, filter_1.filterChangesBySeverity)(minSeverity, filteredChanges);
const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, { const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, {
allow: config.allow_licenses, allow: config.allow_licenses,
@@ -625,11 +631,11 @@ function run() {
} }
if (config.vulnerability_check) { if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity); summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity);
printVulnerabilitiesBlock(vulnerableChanges, minSeverity); printVulnerabilitiesBlock(vulnerableChanges, minSeverity, warnOnly);
} }
if (config.license_check) { if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config); summary.addLicensesToSummary(invalidLicenseChanges, config);
printLicensesBlock(invalidLicenseChanges); printLicensesBlock(invalidLicenseChanges, warnOnly);
} }
if (config.deny_packages || config.deny_groups) { if (config.deny_packages || config.deny_groups) {
summary.addDeniedToSummary(deniedChanges); summary.addDeniedToSummary(deniedChanges);
@@ -664,17 +670,23 @@ function run() {
} }
}); });
} }
function printVulnerabilitiesBlock(addedChanges, minSeverity) { function printVulnerabilitiesBlock(addedChanges, minSeverity, warnOnly) {
let failed = false; let vulFound = false;
core.group('Vulnerabilities', () => __awaiter(this, void 0, void 0, function* () { core.group('Vulnerabilities', () => __awaiter(this, void 0, void 0, function* () {
if (addedChanges.length > 0) { if (addedChanges.length > 0) {
for (const change of addedChanges) { for (const change of addedChanges) {
printChangeVulnerabilities(change); printChangeVulnerabilities(change);
} }
failed = true; vulFound = true;
} }
if (failed) { if (vulFound) {
core.setFailed('Dependency review detected vulnerable packages.'); const msg = 'Dependency review detected vulnerable packages.';
if (warnOnly) {
core.warning(msg);
}
else {
core.setFailed(msg);
}
} }
else { else {
core.info(`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.`); core.info(`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.`);
@@ -687,12 +699,18 @@ function printChangeVulnerabilities(change) {
core.info(`${vuln.advisory_url}`); core.info(`${vuln.advisory_url}`);
} }
} }
function printLicensesBlock(invalidLicenseChanges) { function printLicensesBlock(invalidLicenseChanges, warnOnly) {
core.group('Licenses', () => __awaiter(this, void 0, void 0, function* () { core.group('Licenses', () => __awaiter(this, void 0, void 0, function* () {
if (invalidLicenseChanges.forbidden.length > 0) { if (invalidLicenseChanges.forbidden.length > 0) {
core.info('\nThe following dependencies have incompatible licenses:'); core.info('\nThe following dependencies have incompatible licenses:');
printLicensesError(invalidLicenseChanges.forbidden); 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) { if (invalidLicenseChanges.unresolved.length > 0) {
core.warning('\nThe validity of the licenses of the dependencies below could not be determined. Ensure that they are valid SPDX licenses:'); core.warning('\nThe validity of the licenses of the dependencies below could not be determined. Ensure that they are valid SPDX licenses:');
@@ -849,7 +867,8 @@ exports.ConfigurationOptionsSchema = z
z.preprocess(val => (val === 'true' ? true : val === 'false' ? false : val), z.boolean()), z.preprocess(val => (val === 'true' ? true : val === 'false' ? false : val), z.boolean()),
z.enum(['always', 'never', 'on-failure']) z.enum(['always', 'never', 'on-failure'])
]) ])
.default('never') .default('never'),
warn_only: z.boolean().default(false)
}) })
.transform(config => { .transform(config => {
if (config.comment_summary_in_pr === true) { if (config.comment_summary_in_pr === true) {
@@ -49005,6 +49024,7 @@ function readInlineConfig() {
const comment_summary_in_pr = getOptionalInput('comment-summary-in-pr'); const comment_summary_in_pr = getOptionalInput('comment-summary-in-pr');
const retry_on_snapshot_warnings = getOptionalBoolean('retry-on-snapshot-warnings'); const retry_on_snapshot_warnings = getOptionalBoolean('retry-on-snapshot-warnings');
const retry_on_snapshot_warnings_timeout = getOptionalNumber('retry-on-snapshot-warnings-timeout'); const retry_on_snapshot_warnings_timeout = getOptionalNumber('retry-on-snapshot-warnings-timeout');
const warn_only = getOptionalBoolean('warn-only');
validatePURL(allow_dependencies_licenses); validatePURL(allow_dependencies_licenses);
validateLicenses('allow-licenses', allow_licenses); validateLicenses('allow-licenses', allow_licenses);
validateLicenses('deny-licenses', deny_licenses); validateLicenses('deny-licenses', deny_licenses);
@@ -49023,7 +49043,8 @@ function readInlineConfig() {
head_ref, head_ref,
comment_summary_in_pr, comment_summary_in_pr,
retry_on_snapshot_warnings, retry_on_snapshot_warnings,
retry_on_snapshot_warnings_timeout retry_on_snapshot_warnings_timeout,
warn_only
}; };
return Object.fromEntries(Object.entries(keys).filter(([_, value]) => value !== undefined)); return Object.fromEntries(Object.entries(keys).filter(([_, value]) => value !== undefined));
} }
@@ -49326,7 +49347,8 @@ exports.ConfigurationOptionsSchema = z
z.preprocess(val => (val === 'true' ? true : val === 'false' ? false : val), z.boolean()), z.preprocess(val => (val === 'true' ? true : val === 'false' ? false : val), z.boolean()),
z.enum(['always', 'never', 'on-failure']) z.enum(['always', 'never', 'on-failure'])
]) ])
.default('never') .default('never'),
warn_only: z.boolean().default(false)
}) })
.transform(config => { .transform(config => {
if (config.comment_summary_in_pr === true) { if (config.comment_summary_in_pr === true) {
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -32,7 +32,8 @@ const defaultConfig: ConfigurationOptions = {
], ],
comment_summary_in_pr: true, comment_summary_in_pr: true,
retry_on_snapshot_warnings: false, retry_on_snapshot_warnings: false,
retry_on_snapshot_warnings_timeout: 120 retry_on_snapshot_warnings_timeout: 120,
warn_only: false
} }
const tmpDir = path.resolve(__dirname, '../tmp') const tmpDir = path.resolve(__dirname, '../tmp')
+3 -1
View File
@@ -47,6 +47,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
const retry_on_snapshot_warnings_timeout = getOptionalNumber( const retry_on_snapshot_warnings_timeout = getOptionalNumber(
'retry-on-snapshot-warnings-timeout' 'retry-on-snapshot-warnings-timeout'
) )
const warn_only = getOptionalBoolean('warn-only')
validatePURL(allow_dependencies_licenses) validatePURL(allow_dependencies_licenses)
validateLicenses('allow-licenses', allow_licenses) validateLicenses('allow-licenses', allow_licenses)
@@ -67,7 +68,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
head_ref, head_ref,
comment_summary_in_pr, comment_summary_in_pr,
retry_on_snapshot_warnings, retry_on_snapshot_warnings,
retry_on_snapshot_warnings_timeout retry_on_snapshot_warnings_timeout,
warn_only
} }
return Object.fromEntries( return Object.fromEntries(
+29 -10
View File
@@ -87,7 +87,14 @@ async function run(): Promise<void> {
scopedChanges scopedChanges
) )
const minSeverity = config.fail_on_severity 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( const vulnerableChanges = filterChangesBySeverity(
minSeverity, minSeverity,
filteredChanges filteredChanges
@@ -124,11 +131,11 @@ async function run(): Promise<void> {
if (config.vulnerability_check) { if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity) summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
printVulnerabilitiesBlock(vulnerableChanges, minSeverity) printVulnerabilitiesBlock(vulnerableChanges, minSeverity, warnOnly)
} }
if (config.license_check) { if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config) summary.addLicensesToSummary(invalidLicenseChanges, config)
printLicensesBlock(invalidLicenseChanges) printLicensesBlock(invalidLicenseChanges, warnOnly)
} }
if (config.deny_packages || config.deny_groups) { if (config.deny_packages || config.deny_groups) {
summary.addDeniedToSummary(deniedChanges) summary.addDeniedToSummary(deniedChanges)
@@ -167,19 +174,25 @@ async function run(): Promise<void> {
function printVulnerabilitiesBlock( function printVulnerabilitiesBlock(
addedChanges: Changes, addedChanges: Changes,
minSeverity: Severity minSeverity: Severity,
warnOnly: boolean
): void { ): void {
let failed = false let vulFound = false
core.group('Vulnerabilities', async () => { core.group('Vulnerabilities', async () => {
if (addedChanges.length > 0) { if (addedChanges.length > 0) {
for (const change of addedChanges) { for (const change of addedChanges) {
printChangeVulnerabilities(change) printChangeVulnerabilities(change)
} }
failed = true vulFound = true
} }
if (failed) { if (vulFound) {
core.setFailed('Dependency review detected vulnerable packages.') const msg = 'Dependency review detected vulnerable packages.'
if (warnOnly) {
core.warning(msg)
} else {
core.setFailed(msg)
}
} else { } else {
core.info( core.info(
`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.` `Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.`
@@ -202,13 +215,19 @@ function printChangeVulnerabilities(change: Change): void {
} }
function printLicensesBlock( function printLicensesBlock(
invalidLicenseChanges: Record<string, Changes> invalidLicenseChanges: Record<string, Changes>,
warnOnly: boolean
): void { ): void {
core.group('Licenses', async () => { core.group('Licenses', async () => {
if (invalidLicenseChanges.forbidden.length > 0) { if (invalidLicenseChanges.forbidden.length > 0) {
core.info('\nThe following dependencies have incompatible licenses:') core.info('\nThe following dependencies have incompatible licenses:')
printLicensesError(invalidLicenseChanges.forbidden) 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) { if (invalidLicenseChanges.unresolved.length > 0) {
core.warning( core.warning(
+2 -1
View File
@@ -59,7 +59,8 @@ export const ConfigurationOptionsSchema = z
), ),
z.enum(['always', 'never', 'on-failure']) z.enum(['always', 'never', 'on-failure'])
]) ])
.default('never') .default('never'),
warn_only: z.boolean().default(false)
}) })
.transform(config => { .transform(config => {
if (config.comment_summary_in_pr === true) { if (config.comment_summary_in_pr === true) {