Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add none as option for fail-on-severity #432

Merged
merged 25 commits into from Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -78,11 +78,15 @@ Configure this action by either inlining these options in your workflow file, or
| `allow-dependencies-licenses`\* | Contains a list of packages that will be excluded from license checks. | Any package(s) in [purl](https://github.com/package-url/purl-spec) format | none |
| `base-ref`/`head-ref` | Provide custom git references for the git base/head when performing the comparison check. This is only used for event types other than `pull_request` and `pull_request_target`. | Any valid git ref(s) in your project | none |
| `comment-summary-in-pr` | Enable or disable reporting the review summary as a comment in the pull request. If enabled, you must give the workflow or job permission `pull-requests: write`. | `true`, `false` | `false` |
| `warn-only`+ | Enable or disable the action to fail, when set to `true` the action will log all vulnarabilities as warning. | `true`, `false` | `false` |

\*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 vulnarabilities, independently of the severity, will be reported as warnings and the action will not fail.


### Inline Configuration

You can pass options to the Dependency Review GitHub Action using your workflow file.
Expand Down
6 changes: 6 additions & 0 deletions __tests__/config.test.ts
Expand Up @@ -26,6 +26,12 @@ test('it reads custom configs', async () => {
expect(config.allow_licenses).toEqual(['BSD', 'GPL 2'])
})

test('it defaults to false for warn-only', async () => {
setInput('warn-only', 'false')
tgrall marked this conversation as resolved.
Show resolved Hide resolved
const config = await readConfig()
expect(config.warn_only).toEqual(false)
})

test('it defaults to empty allow/deny lists ', async () => {
const config = await readConfig()

Expand Down
3 changes: 2 additions & 1 deletion __tests__/summary.test.ts
Expand Up @@ -24,7 +24,8 @@ const defaultConfig: ConfigurationOptions = {
allow_ghsas: [],
allow_licenses: [],
deny_licenses: [],
comment_summary_in_pr: true
comment_summary_in_pr: true,
warn_only: false
}

const changesWithEmptyManifests: Changes = [
Expand Down
3 changes: 2 additions & 1 deletion __tests__/test-helpers.ts
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Expand Up @@ -47,6 +47,11 @@ inputs:
comment-summary-in-pr:
description: A boolean to determine if the report should be posted as a comment in the PR itself. Setting this to true requires you to give the workflow the write permissions for pull-requests
required: false
warn-only:
description: When set to `true` this action will not make the action to fail, this override the `fail-on-severity` parameter.
tgrall marked this conversation as resolved.
Show resolved Hide resolved
required: false
default: false

runs:
using: 'node16'
main: 'dist/index.js'
70 changes: 53 additions & 17 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion scripts/create_summary.ts
Expand Up @@ -28,7 +28,8 @@ const defaultConfig: ConfigurationOptions = {
'pkg:pip/certifi',
'pkg:pip/pycrypto@2.6.1'
],
comment_summary_in_pr: true
comment_summary_in_pr: true,
warn_only: false
}

const tmpDir = path.resolve(__dirname, '../tmp')
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Expand Up @@ -39,6 +39,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
const base_ref = getOptionalInput('base-ref')
const head_ref = getOptionalInput('head-ref')
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
const warn_only = getOptionalBoolean('warn-only')

validatePURL(allow_dependencies_licenses)
validateLicenses('allow-licenses', allow_licenses)
Expand All @@ -55,7 +56,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
vulnerability_check,
base_ref,
head_ref,
comment_summary_in_pr
comment_summary_in_pr,
warn_only
}

return Object.fromEntries(
Expand Down
39 changes: 29 additions & 10 deletions src/main.ts
Expand Up @@ -37,7 +37,14 @@ async function run(): Promise<void> {
return
}

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 scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
const filteredChanges = filterAllowedAdvisories(
config.allow_ghsas,
Expand Down Expand Up @@ -75,11 +82,11 @@ async function run(): Promise<void> {

if (config.vulnerability_check) {
summary.addChangeVulnerabilitiesToSummary(vulnerableChanges, minSeverity)
printVulnerabilitiesBlock(vulnerableChanges, minSeverity)
printVulnerabilitiesBlock(vulnerableChanges, minSeverity, warnOnly)
}
if (config.license_check) {
summary.addLicensesToSummary(invalidLicenseChanges, config)
printLicensesBlock(invalidLicenseChanges)
printLicensesBlock(invalidLicenseChanges, warnOnly)
}

summary.addScannedDependencies(changes)
Expand Down Expand Up @@ -110,19 +117,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.`
Expand All @@ -145,13 +158,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(
Expand Down
13 changes: 11 additions & 2 deletions src/schemas.ts
@@ -1,8 +1,15 @@
import * as z from 'zod'

export const FAIL_ON_SEVERITIES = [
'critical',
'high',
'moderate',
'low'
] as const
export const SEVERITIES = ['critical', 'high', 'moderate', 'low'] as const
export const SCOPES = ['unknown', 'runtime', 'development'] as const

export const FailOnSeveritySchema = z.enum(FAIL_ON_SEVERITIES).default('low')
export const SeveritySchema = z.enum(SEVERITIES).default('low')

export const ChangeSchema = z.object({
Expand Down Expand Up @@ -36,7 +43,7 @@ export const PullRequestSchema = z.object({

export const ConfigurationOptionsSchema = z
.object({
fail_on_severity: SeveritySchema,
fail_on_severity: FailOnSeveritySchema,
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
allow_licenses: z.array(z.string()).optional(),
deny_licenses: z.array(z.string()).optional(),
Expand All @@ -47,7 +54,8 @@ export const ConfigurationOptionsSchema = z
config_file: z.string().optional(),
base_ref: z.string().optional(),
head_ref: z.string().optional(),
comment_summary_in_pr: z.boolean().default(false)
comment_summary_in_pr: z.boolean().default(false),
warn_only: z.boolean().default(false)
})
.superRefine((config, context) => {
if (config.allow_licenses && config.deny_licenses) {
Expand Down Expand Up @@ -83,5 +91,6 @@ 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 FailOnSeverity = z.infer<typeof FailOnSeveritySchema>
export type Severity = z.infer<typeof SeveritySchema>
export type Scope = (typeof SCOPES)[number]