Files
dependency-review-action/src/main.ts
T

228 lines
6.4 KiB
TypeScript
Raw Normal View History

2022-03-31 18:31:39 +02:00
import * as core from '@actions/core'
import * as dependencyGraph from './dependency-graph'
import * as github from '@actions/github'
import styles from 'ansi-styles'
2022-06-01 12:09:11 +02:00
import {RequestError} from '@octokit/request-error'
2022-09-15 20:03:27 +00:00
import {Change, Severity, Scope} from './schemas'
2022-06-13 19:55:08 +02:00
import {readConfig} from '../src/config'
2022-09-22 22:25:21 +00:00
import {
filterChangesBySeverity,
filterChangesByScopes,
filterOutAllowedAdvisories
} from '../src/filter'
2022-06-08 17:45:42 +02:00
import {getDeniedLicenseChanges} from './licenses'
import * as summary from './summary'
import {getRefs} from './git-refs'
2022-03-31 18:31:39 +02:00
async function run(): Promise<void> {
try {
const config = readConfig()
const refs = getRefs(config, github.context)
2022-03-31 18:31:39 +02:00
const changes = await dependencyGraph.compare({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
baseRef: refs.base,
headRef: refs.head
2022-03-31 18:31:39 +02:00
})
2022-07-04 20:05:43 +09:00
const minSeverity = config.fail_on_severity
2022-03-31 18:31:39 +02:00
2022-07-04 20:05:43 +09:00
const licenses = {
2022-06-08 17:45:42 +02:00
allow: config.allow_licenses,
deny: config.deny_licenses
}
2022-09-15 20:03:27 +00:00
const scopes = config.fail_on_scopes
const scopedChanges = filterChangesByScopes(scopes as Scope[], changes)
2022-09-22 22:25:21 +00:00
const allowedGhsas: string[] = config.allow_ghsas || []
const filteredChanges = filterOutAllowedAdvisories(
allowedGhsas,
scopedChanges
)
const addedChanges = filterChangesBySeverity(
2022-06-06 18:06:48 +02:00
minSeverity as Severity,
2022-09-22 22:25:21 +00:00
filteredChanges
).filter(
change =>
2022-03-31 18:31:39 +02:00
change.change_type === 'added' &&
change.vulnerabilities !== undefined &&
change.vulnerabilities.length > 0
)
2022-03-31 18:31:39 +02:00
2022-07-04 20:05:43 +09:00
const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges(
2022-09-22 22:25:21 +00:00
filteredChanges,
2022-06-14 13:54:27 +02:00
licenses
)
summary.addSummaryToSummary(addedChanges, licenseErrors, unknownLicenses)
summary.addChangeVulnerabilitiesToSummary(addedChanges, minSeverity || '')
summary.addLicensesToSummary(licenseErrors, unknownLicenses, config)
2022-09-26 12:21:24 +02:00
printVulnerabilitiesBlock(addedChanges, minSeverity || 'low')
printLicensesBlock(licenseErrors, unknownLicenses)
printScannedDependencies(changes)
2022-03-31 18:31:39 +02:00
} catch (error) {
if (error instanceof RequestError && error.status === 404) {
core.setFailed(
`Dependency review could not obtain dependency data for the specified owner, repository, or revision range.`
)
} else if (error instanceof RequestError && error.status === 403) {
2022-03-31 18:31:39 +02:00
core.setFailed(
`Dependency review is not supported on this repository. Please ensure that Dependency graph is enabled, see https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/settings/security_analysis`
)
2022-05-23 11:45:36 -07:00
} else {
if (error instanceof Error) {
core.setFailed(error.message)
} else {
core.setFailed('Unexpected fatal error')
}
2022-03-31 18:31:39 +02:00
}
} finally {
await core.summary.write()
2022-03-31 18:31:39 +02:00
}
}
2022-09-26 12:21:24 +02:00
function printVulnerabilitiesBlock(
addedChanges: Change[],
minSeverity: Severity
): void {
let failed = false
core.group('Vulnerabilities', async () => {
if (addedChanges.length > 0) {
for (const change of addedChanges) {
printChangeVulnerabilities(change)
}
failed = true
}
if (failed) {
core.setFailed('Dependency review detected vulnerable packages.')
} else {
core.info(
`Dependency review did not detect any vulnerable packages with severity level "${minSeverity}" or higher.`
)
}
})
}
2022-07-04 20:09:44 +09:00
function printChangeVulnerabilities(change: Change): void {
2022-06-01 05:36:46 +02:00
for (const vuln of change.vulnerabilities) {
2022-05-31 06:03:42 +02:00
core.info(
2022-06-01 12:09:11 +02:00
`${styles.bold.open}${change.manifest} » ${change.name}@${
change.version
2022-05-31 06:03:42 +02:00
}${styles.bold.close} ${vuln.advisory_summary} ${renderSeverity(
vuln.severity
)}`
)
core.info(` ↪ ${vuln.advisory_url}`)
}
}
2022-03-31 18:31:39 +02:00
function renderSeverity(
severity: 'critical' | 'high' | 'moderate' | 'low'
): string {
const color = (
{
critical: 'red',
high: 'red',
moderate: 'yellow',
low: 'grey'
} as const
)[severity]
return `${styles.color[color].open}(${severity} severity)${styles.color[color].close}`
}
2022-09-26 12:01:39 +02:00
function renderScannedDependency(change: Change): string {
const changeType: string = change.change_type
if (changeType !== 'added' && changeType !== 'removed') {
throw new Error(`Unexpected change type: ${changeType}`)
}
2022-09-26 11:35:05 +02:00
const color = (
{
added: 'green',
removed: 'red'
} as const
2022-09-26 12:01:39 +02:00
)[changeType]
const icon = (
{
added: '+',
removed: '-'
} as const
)[changeType]
return `${styles.color[color].open}${icon} ${change.manifest}@${change.version}${styles.color[color].close}`
2022-09-26 11:35:05 +02:00
}
2022-09-26 12:21:24 +02:00
function printScannedDependencies(changes: Change[]): void {
2022-09-26 12:41:16 +02:00
core.group('Dependency Changes', async () => {
2022-09-26 12:21:24 +02:00
// group changes by manifest
2022-09-26 12:41:16 +02:00
const dependencies: Map<string, Change[]> = new Map()
2022-09-26 12:21:24 +02:00
for (const change of changes) {
2022-09-26 12:41:16 +02:00
const manifestName = change.manifest
if (dependencies.get(manifestName) === undefined) {
dependencies.set(manifestName, [])
2022-09-26 12:21:24 +02:00
}
2022-09-26 12:41:16 +02:00
dependencies.get(manifestName)?.push(change)
2022-09-26 12:21:24 +02:00
}
2022-09-26 12:41:16 +02:00
for (const manifestName of dependencies.keys()) {
const manifestChanges = dependencies.get(manifestName) || []
2022-09-26 12:21:24 +02:00
core.info(`File: ${styles.bold.open}${manifestName}${styles.bold.close}`)
for (const change of manifestChanges) {
core.info(`${renderScannedDependency(change)}`)
}
}
})
}
function printLicensesBlock(
licenseErrors: Change[],
unknownLicenses: Change[]
): void {
core.group('Licenses', async () => {
if (licenseErrors.length > 0) {
printLicensesError(licenseErrors)
core.setFailed('Dependency review detected incompatible licenses.')
}
printNullLicenses(unknownLicenses)
})
}
2022-07-04 20:09:44 +09:00
function printLicensesError(changes: Change[]): void {
if (changes.length === 0) {
2022-06-08 17:45:42 +02:00
return
2022-06-06 18:06:48 +02:00
}
2022-06-14 13:54:27 +02:00
core.info('\nThe following dependencies have incompatible licenses:\n')
2022-06-06 17:31:33 +02:00
for (const change of changes) {
core.info(
`${styles.bold.open}${change.manifest} » ${change.name}@${change.version}${styles.bold.close} License: ${styles.color.red.open}${change.license}${styles.color.red.close}`
2022-06-06 17:31:33 +02:00
)
}
}
2022-07-04 20:05:43 +09:00
function printNullLicenses(changes: Change[]): void {
2022-06-16 06:03:16 +02:00
if (changes.length === 0) {
return
}
2022-06-14 13:54:27 +02:00
core.info('\nWe could not detect a license for the following dependencies:\n')
for (const change of changes) {
core.info(
`${styles.bold.open}${change.manifest} » ${change.name}@${change.version}${styles.bold.close}`
)
}
}
2022-03-31 18:31:39 +02:00
run()