Merge pull request #91 from actions/adding-config-file
Adding configuration options
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
fail_on_severity: low
|
||||||
|
allow_licenses:
|
||||||
|
- 'GPL 3.0'
|
||||||
|
- 'BSD 3 Clause'
|
||||||
|
- 'MIT'
|
||||||
|
#deny_licenses:
|
||||||
|
# - "LGPL 2.0"
|
||||||
|
# - "BSD 2 Clause"
|
||||||
@@ -28,7 +28,34 @@ jobs:
|
|||||||
uses: actions/dependency-review-action@v1
|
uses: actions/dependency-review-action@v1
|
||||||
```
|
```
|
||||||
|
|
||||||
Please keep in mind that you need a GitHub Advanced Security license if you're running this Action on private repos.
|
Please keep in mind that you need a GitHub Advanced Security license
|
||||||
|
if you're running this Action on private repos.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The Dependency Review Action uses a YAML configuration file. It
|
||||||
|
expects this file to be named `dependency-review.yml`, inside your
|
||||||
|
`.github/` directory.
|
||||||
|
|
||||||
|
Here's a sample configuration file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
fail_on_severity: low
|
||||||
|
```
|
||||||
|
|
||||||
|
[Here](https://github.com/actions/dependency-review-action/blob/main/.github/dependency-review.yml)
|
||||||
|
you can see an example of the configuration file we use for this repository.
|
||||||
|
|
||||||
|
### Severity
|
||||||
|
|
||||||
|
By default this Action blocks any pull request that contains a
|
||||||
|
vulnerability of any severity level. You can override this behavior by
|
||||||
|
setting an option in your configuration file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# choose one of: 'critical', 'high', 'moderate' or 'low'
|
||||||
|
fail_on_severity: high
|
||||||
|
```
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {expect, test} from '@jest/globals'
|
||||||
|
import {readConfigFile} from '../src/config'
|
||||||
|
|
||||||
|
test('reads the config file', async () => {
|
||||||
|
let options = readConfigFile('./__tests__/fixtures/config-allow-sample.yml')
|
||||||
|
expect(options.fail_on_severity).toEqual('critical')
|
||||||
|
expect(options.allow_licenses).toEqual(['BSD', 'GPL 2'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the default config path handles .yml and .yaml', async () => {
|
||||||
|
expect(true).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns a default config when the config file was not found', async () => {
|
||||||
|
let options = readConfigFile('fixtures/i-dont-exist')
|
||||||
|
expect(options.fail_on_severity).toEqual('low')
|
||||||
|
expect(options.allow_licenses).toEqual([])
|
||||||
|
})
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {expect, test} from '@jest/globals'
|
||||||
|
import {Change, Changes} from '../src/schemas'
|
||||||
|
import {filterChangesBySeverity} from '../src/filter'
|
||||||
|
|
||||||
|
let npmChange: Change = {
|
||||||
|
manifest: 'package.json',
|
||||||
|
change_type: 'added',
|
||||||
|
ecosystem: 'npm',
|
||||||
|
name: 'Reeuhq',
|
||||||
|
version: '1.0.2',
|
||||||
|
package_url: 'somepurl',
|
||||||
|
license: 'MIT',
|
||||||
|
source_repository_url: 'github.com/some-repo',
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
severity: 'critical',
|
||||||
|
advisory_ghsa_id: 'first-random_string',
|
||||||
|
advisory_summary: 'very dangerouns',
|
||||||
|
advisory_url: 'github.com/future-funk'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let rubyChange: Change = {
|
||||||
|
change_type: 'added',
|
||||||
|
manifest: 'Gemfile.lock',
|
||||||
|
ecosystem: 'rubygems',
|
||||||
|
name: 'actionsomething',
|
||||||
|
version: '3.2.0',
|
||||||
|
package_url: 'somerubypurl',
|
||||||
|
license: 'BSD',
|
||||||
|
source_repository_url: 'github.com/some-repo',
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
severity: 'moderate',
|
||||||
|
advisory_ghsa_id: 'second-random_string',
|
||||||
|
advisory_summary: 'not so dangerouns',
|
||||||
|
advisory_url: 'github.com/future-funk'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
severity: 'low',
|
||||||
|
advisory_ghsa_id: 'third-random_string',
|
||||||
|
advisory_summary: 'dont page me',
|
||||||
|
advisory_url: 'github.com/future-funk'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
test('it properly filters changes by severity', async () => {
|
||||||
|
const changes = [npmChange, rubyChange]
|
||||||
|
let result = filterChangesBySeverity('high', changes)
|
||||||
|
expect(result).toEqual([npmChange])
|
||||||
|
|
||||||
|
result = filterChangesBySeverity('low', changes)
|
||||||
|
expect(changes).toEqual([npmChange, rubyChange])
|
||||||
|
|
||||||
|
result = filterChangesBySeverity('critical', changes)
|
||||||
|
expect(changes).toEqual([npmChange, rubyChange])
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fail_on_severity: critical
|
||||||
|
allow_licenses:
|
||||||
|
- "BSD"
|
||||||
|
- "GPL 2"
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import {expect, test} from '@jest/globals'
|
|
||||||
|
|
||||||
test('tests things', async () => {
|
|
||||||
expect(true).toEqual(true)
|
|
||||||
})
|
|
||||||
+8487
-9
File diff suppressed because it is too large
Load Diff
+1
-1
File diff suppressed because one or more lines are too long
+17
@@ -684,6 +684,23 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|||||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
yaml
|
||||||
|
ISC
|
||||||
|
Copyright Eemeli Aro <[email protected]>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||||
|
with or without fee is hereby granted, provided that the above copyright notice
|
||||||
|
and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||||
|
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
|
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
zod
|
zod
|
||||||
MIT
|
MIT
|
||||||
MIT License
|
MIT License
|
||||||
|
|||||||
Generated
+14
@@ -16,6 +16,7 @@
|
|||||||
"ansi-styles": "^6.1.0",
|
"ansi-styles": "^6.1.0",
|
||||||
"got": "^12.1.0",
|
"got": "^12.1.0",
|
||||||
"nodemon": "^2.0.16",
|
"nodemon": "^2.0.16",
|
||||||
|
"yaml": "^2.1.0",
|
||||||
"zod": "^3.17.3"
|
"zod": "^3.17.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -7435,6 +7436,14 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-OuAINfTsoJrY5H7CBWnKZhX6nZciXBydrMtTHr1dC4nP40X5jyTIVlogZHxSlVZM8zSgXRfgZGsaHF4+pV+JRw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "16.2.0",
|
"version": "16.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||||
@@ -13046,6 +13055,11 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"yaml": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-OuAINfTsoJrY5H7CBWnKZhX6nZciXBydrMtTHr1dC4nP40X5jyTIVlogZHxSlVZM8zSgXRfgZGsaHF4+pV+JRw=="
|
||||||
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "16.2.0",
|
"version": "16.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"ansi-styles": "^6.1.0",
|
"ansi-styles": "^6.1.0",
|
||||||
"got": "^12.1.0",
|
"got": "^12.1.0",
|
||||||
"nodemon": "^2.0.16",
|
"nodemon": "^2.0.16",
|
||||||
|
"yaml": "^2.1.0",
|
||||||
"zod": "^3.17.3"
|
"zod": "^3.17.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import * as fs from 'fs'
|
||||||
|
import YAML from 'yaml'
|
||||||
|
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export const CONFIG_FILEPATH = './.github/dependency-review.yml'
|
||||||
|
|
||||||
|
export function readConfigFile(
|
||||||
|
filePath: string = CONFIG_FILEPATH
|
||||||
|
): ConfigurationOptions {
|
||||||
|
// By default we want to fail on all severities and allow all licenses.
|
||||||
|
const defaultOptions: ConfigurationOptions = {
|
||||||
|
fail_on_severity: 'low',
|
||||||
|
allow_licenses: []
|
||||||
|
}
|
||||||
|
|
||||||
|
let data
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = fs.readFileSync(path.resolve(filePath), 'utf-8')
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code && error.code === 'ENOENT') {
|
||||||
|
return defaultOptions
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = YAML.parse(data)
|
||||||
|
const parsed = ConfigurationOptionsSchema.parse(values)
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import {Changes} from './schemas'
|
||||||
|
import {Severity, SEVERITIES} from './schemas'
|
||||||
|
|
||||||
|
export function filterChangesBySeverity(
|
||||||
|
severity: Severity,
|
||||||
|
changes: Changes
|
||||||
|
): Changes {
|
||||||
|
const severityIdx = SEVERITIES.indexOf(severity)
|
||||||
|
let filteredChanges = []
|
||||||
|
for (let change of changes) {
|
||||||
|
if (
|
||||||
|
change === undefined ||
|
||||||
|
change.vulnerabilities === undefined ||
|
||||||
|
change.vulnerabilities.length === 0
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let fChange = {
|
||||||
|
...change,
|
||||||
|
vulnerabilities: change.vulnerabilities.filter(vuln => {
|
||||||
|
const vulnIdx = SEVERITIES.indexOf(vuln.severity)
|
||||||
|
if (vulnIdx <= severityIdx) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
filteredChanges.push(fChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't want to deal with changes with no vulnerabilities
|
||||||
|
filteredChanges = filteredChanges.filter(
|
||||||
|
change => change.vulnerabilities.length > 0
|
||||||
|
)
|
||||||
|
return filteredChanges
|
||||||
|
}
|
||||||
+28
-13
@@ -3,7 +3,9 @@ import * as dependencyGraph from './dependency-graph'
|
|||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import styles from 'ansi-styles'
|
import styles from 'ansi-styles'
|
||||||
import {RequestError} from '@octokit/request-error'
|
import {RequestError} from '@octokit/request-error'
|
||||||
import {PullRequestSchema} from './schemas'
|
import {Change, PullRequestSchema, Severity} from './schemas'
|
||||||
|
import {readConfigFile} from '../src/config'
|
||||||
|
import {filterChangesBySeverity} from '../src/filter'
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -24,24 +26,22 @@ async function run(): Promise<void> {
|
|||||||
headRef: pull_request.head.sha
|
headRef: pull_request.head.sha
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let config = readConfigFile()
|
||||||
|
let minSeverity = config.fail_on_severity
|
||||||
let failed = false
|
let failed = false
|
||||||
|
|
||||||
for (const change of changes) {
|
let filteredChanges = filterChangesBySeverity(
|
||||||
|
minSeverity as Severity,
|
||||||
|
changes
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const change of filteredChanges) {
|
||||||
if (
|
if (
|
||||||
change.change_type === 'added' &&
|
change.change_type === 'added' &&
|
||||||
change.vulnerabilities !== undefined &&
|
change.vulnerabilities !== undefined &&
|
||||||
change.vulnerabilities.length > 0
|
change.vulnerabilities.length > 0
|
||||||
) {
|
) {
|
||||||
for (const vuln of change.vulnerabilities) {
|
printChangeVulnerabilities(change)
|
||||||
core.info(
|
|
||||||
`${styles.bold.open}${change.manifest} » ${change.name}@${
|
|
||||||
change.version
|
|
||||||
}${styles.bold.close} – ${vuln.advisory_summary} ${renderSeverity(
|
|
||||||
vuln.severity
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
core.info(` ↪ ${vuln.advisory_url}`)
|
|
||||||
}
|
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,9 @@ async function run(): Promise<void> {
|
|||||||
if (failed) {
|
if (failed) {
|
||||||
throw new Error('Dependency review detected vulnerable packages.')
|
throw new Error('Dependency review detected vulnerable packages.')
|
||||||
} else {
|
} else {
|
||||||
core.info('Dependency review did not detect any vulnerable packages.')
|
core.info(
|
||||||
|
`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or above.`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof RequestError && error.status === 404) {
|
if (error instanceof RequestError && error.status === 404) {
|
||||||
@@ -70,6 +72,19 @@ async function run(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function printChangeVulnerabilities(change: Change) {
|
||||||
|
for (const vuln of change.vulnerabilities) {
|
||||||
|
core.info(
|
||||||
|
`${styles.bold.open}${change.manifest} » ${change.name}@${
|
||||||
|
change.version
|
||||||
|
}${styles.bold.close} – ${vuln.advisory_summary} ${renderSeverity(
|
||||||
|
vuln.severity
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
core.info(` ↪ ${vuln.advisory_url}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderSeverity(
|
function renderSeverity(
|
||||||
severity: 'critical' | 'high' | 'moderate' | 'low'
|
severity: 'critical' | 'high' | 'moderate' | 'low'
|
||||||
): string {
|
): string {
|
||||||
|
|||||||
+19
-1
@@ -1,6 +1,8 @@
|
|||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
||||||
const ChangeSchema = z.object({
|
export const SEVERITIES = ['critical', 'high', 'moderate', 'low'] as const
|
||||||
|
|
||||||
|
export const ChangeSchema = z.object({
|
||||||
change_type: z.enum(['added', 'removed']),
|
change_type: z.enum(['added', 'removed']),
|
||||||
manifest: z.string(),
|
manifest: z.string(),
|
||||||
ecosystem: z.string(),
|
ecosystem: z.string(),
|
||||||
@@ -19,6 +21,7 @@ const ChangeSchema = z.object({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional()
|
.optional()
|
||||||
|
.default([])
|
||||||
})
|
})
|
||||||
|
|
||||||
export const PullRequestSchema = z.object({
|
export const PullRequestSchema = z.object({
|
||||||
@@ -27,6 +30,21 @@ export const PullRequestSchema = z.object({
|
|||||||
head: z.object({sha: z.string()})
|
head: z.object({sha: z.string()})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const ConfigurationOptionsSchema = z
|
||||||
|
.object({
|
||||||
|
fail_on_severity: z.enum(SEVERITIES).default('low'),
|
||||||
|
allow_licenses: z.array(z.string()).default([]),
|
||||||
|
deny_licenses: z.array(z.string()).default([])
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
.refine(
|
||||||
|
obj => !(obj.allow_licenses && obj.deny_licenses),
|
||||||
|
"Can't specify both allow_licenses and deny_licenses"
|
||||||
|
)
|
||||||
|
|
||||||
export const ChangesSchema = z.array(ChangeSchema)
|
export const ChangesSchema = z.array(ChangeSchema)
|
||||||
|
|
||||||
|
export type Change = z.infer<typeof ChangeSchema>
|
||||||
export type Changes = z.infer<typeof ChangesSchema>
|
export type Changes = z.infer<typeof ChangesSchema>
|
||||||
|
export type ConfigurationOptions = z.infer<typeof ConfigurationOptionsSchema>
|
||||||
|
export type Severity = typeof SEVERITIES[number]
|
||||||
|
|||||||
Reference in New Issue
Block a user