Compare commits

..

1 Commits

Author SHA1 Message Date
Federico Builes 1ade604b58 adding spdx-satisfies 2022-10-04 15:46:53 +02:00
10 changed files with 487 additions and 19850 deletions
+6 -12
View File
@@ -3,7 +3,7 @@ import {Change, Changes} from '../src/schemas'
import {
filterChangesBySeverity,
filterChangesByScopes,
filterAllowedAdvisories
filterOutAllowedAdvisories
} from '../src/filter'
let npmChange: Change = {
@@ -90,34 +90,28 @@ test('it properly filters changes by scope', async () => {
expect(result).toEqual([npmChange, rubyChange])
})
test('it properly handles undefined advisory IDs', async () => {
const changes = [npmChange, rubyChange, noVulnNpmChange]
let result = filterAllowedAdvisories(undefined, changes)
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
})
test('it properly filters changes with allowed vulnerabilities', async () => {
const changes = [npmChange, rubyChange, noVulnNpmChange]
let result = filterAllowedAdvisories(['notrealGHSAID'], changes)
let result = filterOutAllowedAdvisories(['notrealGHSAID'], changes)
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
result = filterAllowedAdvisories(['first-random_string'], changes)
result = filterOutAllowedAdvisories(['first-random_string'], changes)
expect(result).toEqual([rubyChange, noVulnNpmChange])
result = filterAllowedAdvisories(
result = filterOutAllowedAdvisories(
['second-random_string', 'third-random_string'],
changes
)
expect(result).toEqual([npmChange, noVulnNpmChange])
result = filterAllowedAdvisories(
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 = filterAllowedAdvisories(['second-random_string'], changes)
result = filterOutAllowedAdvisories(['second-random_string'], changes)
expect(result).toEqual([npmChange, rubyChange, noVulnNpmChange])
})
+7 -78
View File
@@ -1,4 +1,4 @@
import {expect, jest, test} from '@jest/globals'
import {expect, test} from '@jest/globals'
import {Change, Changes} from '../src/schemas'
import {getDeniedLicenseChanges} from '../src/licenses'
@@ -48,41 +48,15 @@ let rubyChange: Change = {
]
}
jest.mock('@actions/core')
const mockOctokit = {
rest: {
licenses: {
getForRepo: jest
.fn()
.mockReturnValue({data: {license: {spdx_id: 'AGPL'}}})
}
}
}
jest.mock('octokit', () => {
return {
Octokit: class {
constructor() {
return mockOctokit
}
}
}
})
test('it fails if a license outside the allow list is found', async () => {
const changes: Changes = [npmChange, rubyChange]
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
allow: ['BSD']
})
const [invalidChanges, _] = getDeniedLicenseChanges(changes, {allow: ['BSD']})
expect(invalidChanges[0]).toBe(npmChange)
})
test('it fails if a license inside the deny list is found', async () => {
const changes: Changes = [npmChange, rubyChange]
const [invalidChanges] = await getDeniedLicenseChanges(changes, {
deny: ['BSD']
})
const [invalidChanges] = getDeniedLicenseChanges(changes, {deny: ['BSD']})
expect(invalidChanges[0]).toBe(rubyChange)
})
@@ -90,7 +64,7 @@ test('it fails if a license inside the deny list is found', async () => {
// thing we want in the system. Please remove this test after refactoring.
test('it fails all license checks when allow is provided an empty array', async () => {
const changes: Changes = [npmChange, rubyChange]
let [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
let [invalidChanges, _] = getDeniedLicenseChanges(changes, {
allow: [],
deny: ['BSD']
})
@@ -102,9 +76,7 @@ test('it does not fail if a license outside the allow list is found in removed c
{...npmChange, change_type: 'removed'},
{...rubyChange, change_type: 'removed'}
]
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
allow: ['BSD']
})
const [invalidChanges, _] = getDeniedLicenseChanges(changes, {allow: ['BSD']})
expect(invalidChanges).toStrictEqual([])
})
@@ -113,9 +85,7 @@ test('it does not fail if a license inside the deny list is found in removed cha
{...npmChange, change_type: 'removed'},
{...rubyChange, change_type: 'removed'}
]
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
deny: ['BSD']
})
const [invalidChanges, _] = getDeniedLicenseChanges(changes, {deny: ['BSD']})
expect(invalidChanges).toStrictEqual([])
})
@@ -125,47 +95,6 @@ test('it fails if a license outside the allow list is found in both of added and
npmChange,
{...rubyChange, change_type: 'removed'}
]
const [invalidChanges, _] = await getDeniedLicenseChanges(changes, {
allow: ['BSD']
})
const [invalidChanges, _] = getDeniedLicenseChanges(changes, {allow: ['BSD']})
expect(invalidChanges).toStrictEqual([npmChange])
})
describe('GH License API fallback', () => {
test('it calls licenses endpoint if atleast one of the changes has null license and valid source_repository_url', async () => {
const nullLicenseChange = {
...npmChange,
license: null,
source_repository_url: 'http://github.com/some-owner/some-repo'
}
const [_, unknownChanges] = await getDeniedLicenseChanges(
[nullLicenseChange, rubyChange],
{}
)
expect(mockOctokit.rest.licenses.getForRepo).toHaveBeenNthCalledWith(1, {
owner: 'some-owner',
repo: 'some-repo'
})
expect(unknownChanges.length).toEqual(0)
})
test('it does not call licenses API endpoint for change with null license and invalid source_repository_url ', async () => {
const [_, unknownChanges] = await getDeniedLicenseChanges(
[{...npmChange, license: null}],
{}
)
expect(mockOctokit.rest.licenses.getForRepo).not.toHaveBeenCalled()
expect(unknownChanges.length).toEqual(1)
})
test('it does not call licenses API endpoint if licenses for all changes are present', async () => {
const [_, unknownChanges] = await getDeniedLicenseChanges(
[npmChange, rubyChange],
{}
)
expect(mockOctokit.rest.licenses.getForRepo).not.toHaveBeenCalled()
expect(unknownChanges.length).toEqual(0)
})
})
Generated Vendored
+100 -16646
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
Generated Vendored
-1049
View File
File diff suppressed because it is too large Load Diff
+352 -1973
View File
File diff suppressed because it is too large Load Diff
+11 -12
View File
@@ -1,6 +1,6 @@
{
"name": "dependency-review-action",
"version": "2.5.0",
"version": "2.4.0",
"private": true,
"description": "A GitHub Action for Dependency Review",
"main": "lib/main.js",
@@ -29,23 +29,22 @@
"@actions/github": "^5.1.1",
"@octokit/plugin-retry": "^3.0.9",
"@octokit/request-error": "^3.0.1",
"ansi-styles": "^6.2.1",
"got": "^12.5.2",
"ansi-styles": "^6.1.1",
"got": "^12.5.1",
"nodemon": "^2.0.20",
"octokit": "^2.0.7",
"yaml": "^2.1.3",
"spdx-satisfies": "^5.0.1",
"yaml": "^2.1.2",
"zod": "^3.19.1"
},
"devDependencies": {
"@types/jest": "^27.5.2",
"@types/node": "^16.11.65",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.0",
"@types/node": "^16.11.63",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"@vercel/ncc": "^0.34.0",
"esbuild-register": "^3.3.3",
"eslint": "^8.25.0",
"eslint-plugin-github": "^4.4.0",
"eslint-plugin-jest": "^27.1.1",
"eslint": "^8.24.0",
"eslint-plugin-github": "^4.3.7",
"eslint-plugin-jest": "^27.0.4",
"jest": "^27.5.1",
"js-yaml": "^4.1.0",
"nodemon": "^2.0.20",
+2 -10
View File
@@ -51,20 +51,12 @@ export function filterChangesByScopes(
return filteredChanges
}
/**
* Filter out changes that are allowed by the allow_ghsas config
* option. We want to remove these changes before we do any
* processing.
* @param ghsas - list of GHSA IDs to allow
* @param changes - list of changes to filter
* @returns a list of changes with the allowed GHSAs removed
*/
export function filterAllowedAdvisories(
export function filterOutAllowedAdvisories(
ghsas: string[] | undefined,
changes: Changes
): Changes {
if (ghsas === undefined) {
return changes
return []
}
const filteredChanges = changes.filter(change => {
+4 -65
View File
@@ -1,5 +1,3 @@
import * as core from '@actions/core'
import {Octokit} from 'octokit'
import {Change} from './schemas'
/**
@@ -12,27 +10,21 @@ import {Change} from './schemas'
* we will ignore the deny list.
* @param {Change[]} changes The list of changes to filter.
* @param { { allow?: string[], deny?: string[]}} licenses An object with `allow`/`deny` keys, each containing a list of licenses.
* @returns {Promise<[Array.<Change>, Array.<Change>]>} A promise to a 2 element tuple. The first element is the list of denied changes and the second one is the list of changes with unknown licenses
* @returns {[Array<Change>, Array<Change]} A tuple where the first element is the list of denied changes and the second one is the list of changes with unknown licenses
*/
export async function getDeniedLicenseChanges(
export function getDeniedLicenseChanges(
changes: Change[],
licenses: {
allow?: string[]
deny?: string[]
}
): Promise<[Change[], Change[]]> {
): [Change[], Change[]] {
const {allow, deny} = licenses
const disallowed: Change[] = []
const unknown: Change[] = []
const consolidatedChanges = changes.some(
({source_repository_url, license}) => !license && source_repository_url
)
? await setGHLicenses(changes)
: changes
for (const change of consolidatedChanges) {
for (const change of changes) {
if (change.change_type === 'removed') {
continue
}
@@ -55,56 +47,3 @@ export async function getDeniedLicenseChanges(
return [disallowed, unknown]
}
const fetchGHLicense = async (
owner: string,
repo: string
): Promise<string | null> => {
const octokit = new Octokit({
auth: core.getInput('repo-token', {required: true})
})
try {
const response = await octokit.rest.licenses.getForRepo({owner, repo})
return response.data.license?.spdx_id ?? null
} catch (_) {
return null
}
}
const parseGitHubURL = (url: string): {owner: string; repo: string} | null => {
try {
const parsed = new URL(url)
if (parsed.host !== 'github.com') {
return null
}
const components = parsed.pathname.split('/')
if (components.length < 3) {
return null
}
return {owner: components[1], repo: components[2]}
} catch (_) {
return null
}
}
const setGHLicenses = async (changes: Change[]): Promise<Change[]> => {
const updatedChanges = changes.map(async change => {
if (change.license !== null || change.source_repository_url === null) {
return change
}
const githubUrl = parseGitHubURL(change.source_repository_url)
if (githubUrl === null) {
return change
}
return {
...change,
license: await fetchGHLicense(githubUrl.owner, githubUrl.repo)
}
})
return Promise.all(updatedChanges)
}
+4 -4
View File
@@ -8,7 +8,7 @@ import {readConfig} from '../src/config'
import {
filterChangesBySeverity,
filterChangesByScopes,
filterAllowedAdvisories
filterOutAllowedAdvisories
} from '../src/filter'
import {getDeniedLicenseChanges} from './licenses'
import * as summary from './summary'
@@ -30,7 +30,7 @@ async function run(): Promise<void> {
const minSeverity = config.fail_on_severity as Severity
const scopedChanges = filterChangesByScopes(config.fail_on_scopes, changes)
const filteredChanges = filterAllowedAdvisories(
const filteredChanges = filterOutAllowedAdvisories(
config.allow_ghsas,
scopedChanges
)
@@ -45,7 +45,7 @@ async function run(): Promise<void> {
change.vulnerabilities.length > 0
)
const [licenseErrors, unknownLicenses] = await getDeniedLicenseChanges(
const [licenseErrors, unknownLicenses] = getDeniedLicenseChanges(
filteredChanges,
{
allow: config.allow_licenses,
@@ -192,7 +192,7 @@ function renderScannedDependency(change: Change): string {
} as const
)[changeType]
return `${styles.color[color].open}${icon} ${change.name}@${change.version}${styles.color[color].close}`
return `${styles.color[color].open}${icon} ${change.manifest}@${change.version}${styles.color[color].close}`
}
function printScannedDependencies(changes: Changes): void {