add tests and docs

This commit is contained in:
Adrien Pessu
2023-08-07 14:07:46 +02:00
parent 6862f6f65f
commit 00f1f5b642
11 changed files with 164 additions and 54 deletions
+13 -11
View File
@@ -66,18 +66,20 @@ 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`. | `true`, `false` | `false` | | `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` |
| `deny-packages` | Contains a list of denied package's name. | Any packages complete names | empty |
| `deny-groups` | Contains a list of denied groups package's name. | Any packages's group names | empty |
\*not supported for use with GitHub Enterprise Server \*not supported for use with GitHub Enterprise Server
@@ -75,6 +75,27 @@ const pipChange: Change = {
] ]
} }
const mvnChange: Change = {
change_type: 'added',
manifest: 'pom.xml',
ecosystem: 'maven',
name: 'org.apache.logging.log4j:log4j-core',
version: '2.15.0',
package_url: 'pkg:org.apache.logging.log4j:[email protected]',
license: 'Apache-2.0',
source_repository_url:
'https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core',
scope: 'unknown',
vulnerabilities: [
{
severity: 'critical',
advisory_ghsa_id: 'second-random_string',
advisory_summary: 'not so dangerous',
advisory_url: 'github.com/future-funk'
}
]
}
jest.mock('@actions/core') jest.mock('@actions/core')
const mockOctokit = { const mockOctokit = {
@@ -106,13 +127,36 @@ beforeEach(async () => {
return jest.fn((license: string, _: string): boolean => license === 'BSD') return jest.fn((license: string, _: string): boolean => license === 'BSD')
}) })
// eslint-disable-next-line @typescript-eslint/no-require-imports // eslint-disable-next-line @typescript-eslint/no-require-imports
;({getDeniedChanges} = require('../src/denylist')) ;({getDeniedChanges} = require('../src/deny'))
}) })
test('it adds license outside the allow list to forbidden changes', async () => { test('it adds packages in the deny packages list', async () => {
const changes: Changes = [npmChange, rubyChange] const changes: Changes = [npmChange, rubyChange]
const deniedChanges = await getDeniedChanges(changes, ['actionsomething']) const deniedChanges = await getDeniedChanges(changes, ['actionsomething'], [])
expect(deniedChanges[0]).toBe(rubyChange) expect(deniedChanges[0]).toBe(rubyChange)
expect(deniedChanges.length).toEqual(1) expect(deniedChanges.length).toEqual(1)
}) })
test('it adds packages in the deny group list', async () => {
const changes: Changes = [mvnChange, rubyChange]
const deniedChanges = await getDeniedChanges(
changes,
[],
['org.apache.logging.log4j']
)
expect(deniedChanges[0]).toBe(mvnChange)
expect(deniedChanges.length).toEqual(1)
})
test('it adds packages outside of the deny lists', async () => {
const changes: Changes = [npmChange, pipChange]
const deniedChanges = await getDeniedChanges(
changes,
['actionsomething'],
['org.apache.logging.log4j']
)
expect(deniedChanges.length).toEqual(0)
})
+2 -1
View File
@@ -24,7 +24,8 @@ const defaultConfig: ConfigurationOptions = {
allow_ghsas: [], allow_ghsas: [],
allow_licenses: [], allow_licenses: [],
deny_licenses: [], deny_licenses: [],
deny_list: [], deny_packages: [],
deny_groups: [],
comment_summary_in_pr: true comment_summary_in_pr: true
} }
+5 -2
View File
@@ -47,8 +47,11 @@ inputs:
comment-summary-in-pr: 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 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 required: false
deny-list: deny-packages:
description: A comma-separated list of dependencies to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto") description: A comma-separated list of packages to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto")
required: false
deny-groups:
description: A comma-separated list of groups of packages to deny (e.g. "pkg:npm/express, pkg:pip/pycrypto")
required: false required: false
runs: runs:
using: 'node16' using: 'node16'
Generated Vendored
+63 -18
View File
@@ -137,11 +137,34 @@ function findCommentByMarker(commentBodyIncludes) {
/***/ }), /***/ }),
/***/ 3491: /***/ 2134:
/***/ (function(__unused_webpack_module, exports) { /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
@@ -153,17 +176,36 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getDeniedChanges = void 0; exports.getDeniedChanges = void 0;
function getDeniedChanges(changes, deniedList) { const core = __importStar(__nccwpck_require__(2186));
function getDeniedChanges(changes, deniedPackages, deniedGroups) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const changesDenied = []; const changesDenied = [];
let failed = false;
for (const change of changes) { for (const change of changes) {
change.name = change.name.toLowerCase(); change.name = change.name.toLowerCase();
change.package_url = change.package_url.toLowerCase(); change.package_url = change.package_url.toLowerCase();
for (const denied of deniedList) { if (deniedPackages) {
if (change.name.includes(denied)) { for (const denied of deniedPackages) {
changesDenied.push(change); if (change.name === denied.toLowerCase()) {
changesDenied.push(change);
failed = true;
}
} }
} }
if (deniedGroups) {
for (const denied of deniedGroups) {
if (change.name.startsWith(denied.toLowerCase())) {
changesDenied.push(change);
failed = true;
}
}
}
}
if (failed) {
core.setFailed('Dependency review detected denied packages.');
}
else {
core.info('Dependency review did not detect any denied packages');
} }
return changesDenied; return changesDenied;
}); });
@@ -521,7 +563,7 @@ const summary = __importStar(__nccwpck_require__(8608));
const git_refs_1 = __nccwpck_require__(1086); const git_refs_1 = __nccwpck_require__(1086);
const utils_1 = __nccwpck_require__(918); const utils_1 = __nccwpck_require__(918);
const comment_pr_1 = __nccwpck_require__(5842); const comment_pr_1 = __nccwpck_require__(5842);
const denylist_1 = __nccwpck_require__(3491); const deny_1 = __nccwpck_require__(2134);
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
@@ -550,10 +592,7 @@ function run() {
deny: config.deny_licenses, deny: config.deny_licenses,
licenseExclusions: config.allow_dependencies_licenses licenseExclusions: config.allow_dependencies_licenses
}); });
const deniedChanges = yield (0, denylist_1.getDeniedChanges)(filteredChanges, config.deny_list); const deniedChanges = yield (0, deny_1.getDeniedChanges)(filteredChanges, config.deny_packages, config.deny_groups);
core.debug(`config: ${JSON.stringify(config)}`);
core.debug(`filteredChanges: ${JSON.stringify(filteredChanges)}`);
core.debug(`deniedChanges: ${JSON.stringify(deniedChanges)}`);
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, deniedChanges, config); summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, deniedChanges, config);
if (snapshot_warnings) { if (snapshot_warnings) {
summary.addSnapshotWarnings(snapshot_warnings); summary.addSnapshotWarnings(snapshot_warnings);
@@ -566,7 +605,7 @@ function run() {
summary.addLicensesToSummary(invalidLicenseChanges, config); summary.addLicensesToSummary(invalidLicenseChanges, config);
printLicensesBlock(invalidLicenseChanges); printLicensesBlock(invalidLicenseChanges);
} }
if (config.deny_list) { if (config.deny_packages || config.deny_groups) {
summary.addDeniedToSummary(deniedChanges); summary.addDeniedToSummary(deniedChanges);
printDeniedDependencies(deniedChanges, config); printDeniedDependencies(deniedChanges, config);
} }
@@ -687,7 +726,7 @@ function printScannedDependencies(changes) {
} }
function printDeniedDependencies(changes, config) { function printDeniedDependencies(changes, config) {
core.group('Denied', () => __awaiter(this, void 0, void 0, function* () { core.group('Denied', () => __awaiter(this, void 0, void 0, function* () {
for (const denied of config.deny_list) { for (const denied of config.deny_packages) {
core.info(`Config: ${denied}`); core.info(`Config: ${denied}`);
} }
for (const change of changes) { for (const change of changes) {
@@ -768,7 +807,8 @@ exports.ConfigurationOptionsSchema = z
deny_licenses: z.array(z.string()).optional(), deny_licenses: z.array(z.string()).optional(),
allow_dependencies_licenses: z.array(z.string()).optional(), allow_dependencies_licenses: z.array(z.string()).optional(),
allow_ghsas: z.array(z.string()).default([]), allow_ghsas: z.array(z.string()).default([]),
deny_list: z.array(z.string()).default([]), deny_packages: z.array(z.string()).default([]),
deny_groups: z.array(z.string()).default([]),
license_check: z.boolean().default(true), license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),
@@ -47902,7 +47942,8 @@ function readInlineConfig() {
const allow_licenses = parseList(getOptionalInput('allow-licenses')); const allow_licenses = parseList(getOptionalInput('allow-licenses'));
const deny_licenses = parseList(getOptionalInput('deny-licenses')); const deny_licenses = parseList(getOptionalInput('deny-licenses'));
const allow_dependencies_licenses = parseList(getOptionalInput('allow-dependencies-licenses')); const allow_dependencies_licenses = parseList(getOptionalInput('allow-dependencies-licenses'));
const deny_list = parseList(getOptionalInput('deny-list')); const deny_packages = parseList(getOptionalInput('deny-packages'));
const deny_groups = parseList(getOptionalInput('deny-groups'));
const allow_ghsas = parseList(getOptionalInput('allow-ghsas')); const allow_ghsas = parseList(getOptionalInput('allow-ghsas'));
const license_check = getOptionalBoolean('license-check'); const license_check = getOptionalBoolean('license-check');
const vulnerability_check = getOptionalBoolean('vulnerability-check'); const vulnerability_check = getOptionalBoolean('vulnerability-check');
@@ -47917,7 +47958,8 @@ function readInlineConfig() {
fail_on_scopes, fail_on_scopes,
allow_licenses, allow_licenses,
deny_licenses, deny_licenses,
deny_list, deny_packages,
deny_groups,
allow_dependencies_licenses, allow_dependencies_licenses,
allow_ghsas, allow_ghsas,
license_check, license_check,
@@ -47988,7 +48030,9 @@ function parseConfigFile(configData) {
'deny-licenses', 'deny-licenses',
'fail-on-scopes', 'fail-on-scopes',
'allow-ghsas', 'allow-ghsas',
'allow-dependencies-licenses' 'allow-dependencies-licenses',
'deny-packages',
'deny-groups'
]; ];
for (const key of Object.keys(data)) { for (const key of Object.keys(data)) {
// strings can contain list values (e.g. 'MIT, Apache-2.0'). In this // strings can contain list values (e.g. 'MIT, Apache-2.0'). In this
@@ -48202,7 +48246,8 @@ exports.ConfigurationOptionsSchema = z
deny_licenses: z.array(z.string()).optional(), deny_licenses: z.array(z.string()).optional(),
allow_dependencies_licenses: z.array(z.string()).optional(), allow_dependencies_licenses: z.array(z.string()).optional(),
allow_ghsas: z.array(z.string()).default([]), allow_ghsas: z.array(z.string()).default([]),
deny_list: z.array(z.string()).default([]), deny_packages: z.array(z.string()).default([]),
deny_groups: z.array(z.string()).default([]),
license_check: z.boolean().default(true), license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -22,7 +22,8 @@ const defaultConfig: ConfigurationOptions = {
allow_ghsas: [], allow_ghsas: [],
allow_licenses: ['MIT'], allow_licenses: ['MIT'],
deny_licenses: [], deny_licenses: [],
deny_list: [], deny_packages: [],
deny_groups: [],
allow_dependencies_licenses: [ allow_dependencies_licenses: [
'pkg:npm/[email protected]', 'pkg:npm/[email protected]',
'pkg:pip/requests', 'pkg:pip/requests',
+7 -3
View File
@@ -33,7 +33,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
const allow_dependencies_licenses = parseList( const allow_dependencies_licenses = parseList(
getOptionalInput('allow-dependencies-licenses') getOptionalInput('allow-dependencies-licenses')
) )
const deny_list = parseList(getOptionalInput('deny-list')) const deny_packages = parseList(getOptionalInput('deny-packages'))
const deny_groups = parseList(getOptionalInput('deny-groups'))
const allow_ghsas = parseList(getOptionalInput('allow-ghsas')) const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
const license_check = getOptionalBoolean('license-check') const license_check = getOptionalBoolean('license-check')
const vulnerability_check = getOptionalBoolean('vulnerability-check') const vulnerability_check = getOptionalBoolean('vulnerability-check')
@@ -50,7 +51,8 @@ function readInlineConfig(): ConfigurationOptionsPartial {
fail_on_scopes, fail_on_scopes,
allow_licenses, allow_licenses,
deny_licenses, deny_licenses,
deny_list, deny_packages,
deny_groups,
allow_dependencies_licenses, allow_dependencies_licenses,
allow_ghsas, allow_ghsas,
license_check, license_check,
@@ -139,7 +141,9 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
'deny-licenses', 'deny-licenses',
'fail-on-scopes', 'fail-on-scopes',
'allow-ghsas', 'allow-ghsas',
'allow-dependencies-licenses' 'allow-dependencies-licenses',
'deny-packages',
'deny-groups'
] ]
for (const key of Object.keys(data)) { for (const key of Object.keys(data)) {
+17 -5
View File
@@ -3,7 +3,8 @@ import * as core from '@actions/core'
export async function getDeniedChanges( export async function getDeniedChanges(
changes: Change[], changes: Change[],
deniedList: string[] deniedPackages: string[],
deniedGroups: string[]
): Promise<Change[]> { ): Promise<Change[]> {
const changesDenied: Change[] = [] const changesDenied: Change[] = []
@@ -12,10 +13,21 @@ export async function getDeniedChanges(
change.name = change.name.toLowerCase() change.name = change.name.toLowerCase()
change.package_url = change.package_url.toLowerCase() change.package_url = change.package_url.toLowerCase()
for (const denied of deniedList) { if (deniedPackages) {
if (change.name.includes(denied)) { for (const denied of deniedPackages) {
changesDenied.push(change) if (change.name === denied.toLowerCase()) {
failed = true changesDenied.push(change)
failed = true
}
}
}
if (deniedGroups) {
for (const denied of deniedGroups) {
if (change.name.startsWith(denied.toLowerCase())) {
changesDenied.push(change)
failed = true
}
} }
} }
} }
+5 -8
View File
@@ -16,7 +16,7 @@ import {getRefs} from './git-refs'
import {groupDependenciesByManifest} from './utils' import {groupDependenciesByManifest} from './utils'
import {commentPr} from './comment-pr' import {commentPr} from './comment-pr'
import {getDeniedChanges} from './denylist' import {getDeniedChanges} from './deny'
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
@@ -66,13 +66,10 @@ async function run(): Promise<void> {
const deniedChanges = await getDeniedChanges( const deniedChanges = await getDeniedChanges(
filteredChanges, filteredChanges,
config.deny_list config.deny_packages,
config.deny_groups
) )
core.debug(`config: ${JSON.stringify(config)}`)
core.debug(`filteredChanges: ${JSON.stringify(filteredChanges)}`)
core.debug(`deniedChanges: ${JSON.stringify(deniedChanges)}`)
summary.addSummaryToSummary( summary.addSummaryToSummary(
vulnerableChanges, vulnerableChanges,
invalidLicenseChanges, invalidLicenseChanges,
@@ -92,7 +89,7 @@ async function run(): Promise<void> {
summary.addLicensesToSummary(invalidLicenseChanges, config) summary.addLicensesToSummary(invalidLicenseChanges, config)
printLicensesBlock(invalidLicenseChanges) printLicensesBlock(invalidLicenseChanges)
} }
if (config.deny_list) { if (config.deny_packages || config.deny_groups) {
summary.addDeniedToSummary(deniedChanges) summary.addDeniedToSummary(deniedChanges)
printDeniedDependencies(deniedChanges, config) printDeniedDependencies(deniedChanges, config)
} }
@@ -259,7 +256,7 @@ function printDeniedDependencies(
config: ConfigurationOptions config: ConfigurationOptions
): void { ): void {
core.group('Denied', async () => { core.group('Denied', async () => {
for (const denied of config.deny_list) { for (const denied of config.deny_packages) {
core.info(`Config: ${denied}`) core.info(`Config: ${denied}`)
} }
+2 -1
View File
@@ -42,7 +42,8 @@ export const ConfigurationOptionsSchema = z
deny_licenses: z.array(z.string()).optional(), deny_licenses: z.array(z.string()).optional(),
allow_dependencies_licenses: z.array(z.string()).optional(), allow_dependencies_licenses: z.array(z.string()).optional(),
allow_ghsas: z.array(z.string()).default([]), allow_ghsas: z.array(z.string()).default([]),
deny_list: z.array(z.string()).default([]), deny_packages: z.array(z.string()).default([]),
deny_groups: z.array(z.string()).default([]),
license_check: z.boolean().default(true), license_check: z.boolean().default(true),
vulnerability_check: z.boolean().default(true), vulnerability_check: z.boolean().default(true),
config_file: z.string().optional(), config_file: z.string().optional(),