Merge branch 'allow-list-dependencies' of github.com:theztefan/dependency-review-action into allow-list-dependencies
This commit is contained in:
@@ -66,19 +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.
|
||||
|
||||
| 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` |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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` |
|
||||
| `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` |
|
||||
| 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` |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `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` |
|
||||
| `allow-dependencies-licenses`\* | Containts excluded packages per ecosystem for the licenses 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` |
|
||||
|
||||
*not supported for use with GitHub Enterprise Server
|
||||
\*not supported for use with GitHub Enterprise Server
|
||||
|
||||
†will be supported with GitHub Enterprise Server 3.8
|
||||
|
||||
@@ -138,6 +139,8 @@ allow-licenses:
|
||||
- 'MIT'
|
||||
```
|
||||
|
||||
For more examples of how to use this action and its configuration options, see the [examples](docs/examples.md) page.
|
||||
|
||||
### Considerations
|
||||
|
||||
- Checking for licenses is not supported on Enterprise Server.
|
||||
|
||||
@@ -29,6 +29,9 @@ inputs:
|
||||
deny-licenses:
|
||||
description: Comma-separated list of forbidden licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
|
||||
required: false
|
||||
allow-dependencies-licenses:
|
||||
description: Comma-separated list of allowed licenses for specific dependencies in purl format (e.g. "pkg:npm/express, pkg:pip/pycrypto")
|
||||
required: false
|
||||
allow-ghsas:
|
||||
description: Comma-separated list of allowed GitHub Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
|
||||
required: false
|
||||
|
||||
+300
-4
@@ -259,10 +259,25 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getInvalidLicenseChanges = void 0;
|
||||
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
|
||||
const utils_1 = __nccwpck_require__(918);
|
||||
const packageurl_js_1 = __nccwpck_require__(8915);
|
||||
function getInvalidLicenseChanges(changes, licenses) {
|
||||
var _a;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { allow, deny } = licenses;
|
||||
const licenseExclusions = (_a = licenses.licenseExclusions) === null || _a === void 0 ? void 0 : _a.map((pkgUrl) => {
|
||||
return packageurl_js_1.PackageURL.fromString(pkgUrl);
|
||||
});
|
||||
const groupedChanges = yield groupChanges(changes);
|
||||
// filter out changes that are part of exclusions list - config.allow_dependencies_licenses
|
||||
groupedChanges.licensed = groupedChanges.licensed.filter(change => {
|
||||
const changeAsPackageURL = new packageurl_js_1.PackageURL(change.ecosystem, undefined, change.name, change.version, undefined, undefined);
|
||||
if ((0, utils_1.isDefined)(licenseExclusions) &&
|
||||
licenseExclusions.findIndex(exclusion => exclusion.type === changeAsPackageURL.type &&
|
||||
exclusion.name === changeAsPackageURL.name) !== -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const licensedChanges = groupedChanges.licensed;
|
||||
const invalidLicenseChanges = {
|
||||
unlicensed: groupedChanges.unlicensed,
|
||||
@@ -470,7 +485,8 @@ function run() {
|
||||
change.vulnerabilities.length > 0);
|
||||
const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, {
|
||||
allow: config.allow_licenses,
|
||||
deny: config.deny_licenses
|
||||
deny: config.deny_licenses,
|
||||
licenseExclusions: config.allow_dependencies_licenses
|
||||
});
|
||||
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config);
|
||||
if (config.vulnerability_check) {
|
||||
@@ -666,6 +682,7 @@ exports.ConfigurationOptionsSchema = z
|
||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||
allow_licenses: z.array(z.string()).optional(),
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_dependencies_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
@@ -831,6 +848,9 @@ function addLicensesToSummary(invalidLicenseChanges, config) {
|
||||
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
||||
core.summary.addQuote(`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`);
|
||||
}
|
||||
if (config.allow_dependencies_licenses) {
|
||||
core.summary.addQuote(`<strong>Excluded from license check</strong>: ${config.allow_dependencies_licenses.join(', ')}`);
|
||||
}
|
||||
core.debug(`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`);
|
||||
core.debug(`${invalidLicenseChanges.unresolved.length} licenses could not be validated`);
|
||||
}
|
||||
@@ -932,7 +952,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.octokitClient = exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
||||
exports.octokitClient = exports.isDefined = exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const octokit_1 = __nccwpck_require__(7467);
|
||||
const spdx_expression_parse_1 = __importDefault(__nccwpck_require__(1620));
|
||||
@@ -972,6 +992,11 @@ function isSPDXValid(license) {
|
||||
}
|
||||
}
|
||||
exports.isSPDXValid = isSPDXValid;
|
||||
// function to check if a value is not null or undefined
|
||||
function isDefined(value) {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
exports.isDefined = isDefined;
|
||||
function isEnterprise() {
|
||||
var _a;
|
||||
const serverUrl = new URL((_a = process.env['GITHUB_SERVER_URL']) !== null && _a !== void 0 ? _a : 'https://github.com');
|
||||
@@ -36422,6 +36447,251 @@ function onceStrict (fn) {
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8915:
|
||||
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
||||
|
||||
/*!
|
||||
Copyright (c) the purl authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const PackageURL = __nccwpck_require__(8749);
|
||||
|
||||
module.exports = {
|
||||
PackageURL
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8749:
|
||||
/***/ ((module) => {
|
||||
|
||||
/*!
|
||||
Copyright (c) the purl authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const KnownQualifierNames = Object.freeze({
|
||||
// known qualifiers as defined here:
|
||||
// https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs
|
||||
RepositoryUrl: 'repository_url',
|
||||
DownloadUrl: 'download_url',
|
||||
VcsUrl: 'vcs_url',
|
||||
FileName: 'file_name',
|
||||
Checksum: 'checksum'
|
||||
});
|
||||
|
||||
class PackageURL {
|
||||
|
||||
static get KnownQualifierNames() {
|
||||
return KnownQualifierNames;
|
||||
}
|
||||
|
||||
constructor(type, namespace, name, version, qualifiers, subpath) {
|
||||
let required = { 'type': type, 'name': name };
|
||||
Object.keys(required).forEach(key => {
|
||||
if (!required[key]) {
|
||||
throw new Error('Invalid purl: "' + key + '" is a required field.');
|
||||
}
|
||||
});
|
||||
|
||||
let strings = { 'type': type, 'namespace': namespace, 'name': name, 'versions': version, 'subpath': subpath };
|
||||
Object.keys(strings).forEach(key => {
|
||||
if (strings[key] && typeof strings[key] === 'string' || !strings[key]) {
|
||||
return;
|
||||
}
|
||||
throw new Error('Invalid purl: "' + key + '" argument must be a string.');
|
||||
});
|
||||
|
||||
if (qualifiers) {
|
||||
if (typeof qualifiers !== 'object') {
|
||||
throw new Error('Invalid purl: "qualifiers" argument must be a dictionary.');
|
||||
}
|
||||
Object.keys(qualifiers).forEach(key => {
|
||||
if (!/^[a-z]+$/i.test(key) && !/[\.-_]/.test(key)) {
|
||||
throw new Error('Invalid purl: qualifier "' + key + '" contains an illegal character.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.namespace = namespace;
|
||||
this.version = version;
|
||||
this.qualifiers = qualifiers;
|
||||
this.subpath = subpath;
|
||||
}
|
||||
|
||||
_handlePyPi() {
|
||||
this.name = this.name.toLowerCase().replace(/_/g, '-');
|
||||
}
|
||||
|
||||
toString() {
|
||||
var purl = ['pkg:', encodeURIComponent(this.type), '/'];
|
||||
|
||||
if (this.type === 'pypi') {
|
||||
this._handlePyPi();
|
||||
}
|
||||
|
||||
if (this.namespace) {
|
||||
purl.push(
|
||||
encodeURIComponent(this.namespace)
|
||||
.replace(/%3A/g, ':')
|
||||
.replace(/%2F/g, '/')
|
||||
);
|
||||
purl.push('/');
|
||||
}
|
||||
|
||||
purl.push(encodeURIComponent(this.name).replace(/%3A/g, ':'));
|
||||
|
||||
if (this.version) {
|
||||
purl.push('@');
|
||||
purl.push(encodeURIComponent(this.version).replace(/%3A/g, ':'));
|
||||
}
|
||||
|
||||
if (this.qualifiers) {
|
||||
purl.push('?');
|
||||
|
||||
let qualifiers = this.qualifiers;
|
||||
let qualifierString = [];
|
||||
Object.keys(qualifiers).sort().forEach(key => {
|
||||
qualifierString.push(
|
||||
encodeURIComponent(key).replace(/%3A/g, ':')
|
||||
+ '='
|
||||
+ encodeURIComponent(qualifiers[key]).replace(/%2F/g, '/')
|
||||
);
|
||||
});
|
||||
|
||||
purl.push(qualifierString.join('&'));
|
||||
}
|
||||
|
||||
if (this.subpath) {
|
||||
purl.push('#');
|
||||
purl.push(encodeURIComponent(this.subpath)
|
||||
.replace(/%3A/g, ':')
|
||||
.replace(/%2F/g, '/'));
|
||||
}
|
||||
|
||||
return purl.join('');
|
||||
}
|
||||
|
||||
static fromString(purl) {
|
||||
if (!purl || typeof purl !== 'string' || !purl.trim()) {
|
||||
throw new Error('A purl string argument is required.');
|
||||
}
|
||||
|
||||
let [scheme, remainder] = purl.split(':', 2);
|
||||
if (scheme !== 'pkg') {
|
||||
throw new Error('purl is missing the required "pkg" scheme component.');
|
||||
}
|
||||
// this strip '/, // and /// as possible in :// or :///
|
||||
// from https://gist.github.com/refo/47632c8a547f2d9b6517#file-remove-leading-slash
|
||||
remainder = remainder.trim().replace(/^\/+/g, '');
|
||||
|
||||
let type
|
||||
[type, remainder] = remainder.split('/', 2);
|
||||
if (!type || !remainder) {
|
||||
throw new Error('purl is missing the required "type" component.');
|
||||
}
|
||||
type = decodeURIComponent(type)
|
||||
|
||||
let url = new URL(purl);
|
||||
|
||||
let qualifiers = null;
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (!qualifiers) {
|
||||
qualifiers = {};
|
||||
}
|
||||
qualifiers[key] = value;
|
||||
});
|
||||
let subpath = url.hash;
|
||||
if (subpath.indexOf('#') === 0) {
|
||||
subpath = subpath.substring(1);
|
||||
}
|
||||
subpath = subpath.length === 0
|
||||
? null
|
||||
: decodeURIComponent(subpath)
|
||||
|
||||
if (url.username !== '' || url.password !== '') {
|
||||
throw new Error('Invalid purl: cannot contain a "user:pass@host:port"');
|
||||
}
|
||||
|
||||
// this strip '/, // and /// as possible in :// or :///
|
||||
// from https://gist.github.com/refo/47632c8a547f2d9b6517#file-remove-leading-slash
|
||||
let path = url.pathname.trim().replace(/^\/+/g, '');
|
||||
|
||||
// version is optional - check for existence
|
||||
let version = null;
|
||||
if (path.includes('@')) {
|
||||
let index = path.indexOf('@');
|
||||
version = decodeURIComponent(path.substring(index + 1));
|
||||
remainder = path.substring(0, index);
|
||||
} else {
|
||||
remainder = path;
|
||||
}
|
||||
|
||||
// The 'remainder' should now consist of an optional namespace and the name
|
||||
let remaining = remainder.split('/').slice(1);
|
||||
let name = null;
|
||||
let namespace = null;
|
||||
if (remaining.length > 1) {
|
||||
let nameIndex = remaining.length - 1;
|
||||
let namespaceComponents = remaining.slice(0, nameIndex);
|
||||
name = decodeURIComponent(remaining[nameIndex]);
|
||||
namespace = decodeURIComponent(namespaceComponents.join('/'));
|
||||
} else if (remaining.length === 1) {
|
||||
name = decodeURIComponent(remaining[0]);
|
||||
}
|
||||
|
||||
if (name === '') {
|
||||
throw new Error('purl is missing the required "name" component.');
|
||||
}
|
||||
|
||||
return new PackageURL(type, namespace, name, version, qualifiers, subpath);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = PackageURL;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 1867:
|
||||
@@ -44824,6 +45094,7 @@ const core = __importStar(__nccwpck_require__(2186));
|
||||
const z = __importStar(__nccwpck_require__(3301));
|
||||
const schemas_1 = __nccwpck_require__(1129);
|
||||
const utils_1 = __nccwpck_require__(1314);
|
||||
const packageurl_js_1 = __nccwpck_require__(8915);
|
||||
function readConfig() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const inlineConfig = readInlineConfig();
|
||||
@@ -44841,12 +45112,14 @@ function readInlineConfig() {
|
||||
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'));
|
||||
const allow_licenses = parseList(getOptionalInput('allow-licenses'));
|
||||
const deny_licenses = parseList(getOptionalInput('deny-licenses'));
|
||||
const allow_dependencies_licenses = parseList(getOptionalInput('allow-dependencies-licenses'));
|
||||
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'));
|
||||
const license_check = getOptionalBoolean('license-check');
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check');
|
||||
const base_ref = getOptionalInput('base-ref');
|
||||
const head_ref = getOptionalInput('head-ref');
|
||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr');
|
||||
validatepurl(allow_dependencies_licenses);
|
||||
validateLicenses('allow-licenses', allow_licenses);
|
||||
validateLicenses('deny-licenses', deny_licenses);
|
||||
const keys = {
|
||||
@@ -44854,6 +45127,7 @@ function readInlineConfig() {
|
||||
fail_on_scopes,
|
||||
allow_licenses,
|
||||
deny_licenses,
|
||||
allow_dependencies_licenses,
|
||||
allow_ghsas,
|
||||
license_check,
|
||||
vulnerability_check,
|
||||
@@ -44922,7 +45196,8 @@ function parseConfigFile(configData) {
|
||||
'allow-licenses',
|
||||
'deny-licenses',
|
||||
'fail-on-scopes',
|
||||
'allow-ghsas'
|
||||
'allow-ghsas',
|
||||
'allow-dependencies-licenses'
|
||||
];
|
||||
for (const key of Object.keys(data)) {
|
||||
// strings can contain list values (e.g. 'MIT, Apache-2.0'). In this
|
||||
@@ -44937,6 +45212,10 @@ function parseConfigFile(configData) {
|
||||
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
||||
validateLicenses(key, data[key]);
|
||||
}
|
||||
// validate purls from the allow-dependencies-licenses
|
||||
if (key === 'allow-dependencies-licenses') {
|
||||
validatepurl(data[key]);
|
||||
}
|
||||
// get rid of the ugly dashes from the actions conventions
|
||||
if (key.includes('-')) {
|
||||
data[key.replace(/-/g, '_')] = data[key];
|
||||
@@ -44972,6 +45251,17 @@ function getRemoteConfig(configOpts) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function validatepurl(allow_dependencies_licenses) {
|
||||
//validate that the provided elements of the string are in valid purl format
|
||||
if (allow_dependencies_licenses === undefined) {
|
||||
return;
|
||||
}
|
||||
const invalid_purls = allow_dependencies_licenses.filter(purl => !packageurl_js_1.PackageURL.fromString(purl));
|
||||
if (invalid_purls.length > 0) {
|
||||
throw new Error(`Invalid purl(s) in allow-dependencies-licenses: ${invalid_purls}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -45119,6 +45409,7 @@ exports.ConfigurationOptionsSchema = z
|
||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||
allow_licenses: z.array(z.string()).optional(),
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_dependencies_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
@@ -45185,7 +45476,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.octokitClient = exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
||||
exports.octokitClient = exports.isDefined = exports.isSPDXValid = exports.renderUrl = exports.getManifestsSet = exports.groupDependenciesByManifest = void 0;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
const octokit_1 = __nccwpck_require__(7467);
|
||||
const spdx_expression_parse_1 = __importDefault(__nccwpck_require__(1620));
|
||||
@@ -45225,6 +45516,11 @@ function isSPDXValid(license) {
|
||||
}
|
||||
}
|
||||
exports.isSPDXValid = isSPDXValid;
|
||||
// function to check if a value is not null or undefined
|
||||
function isDefined(value) {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
exports.isDefined = isDefined;
|
||||
function isEnterprise() {
|
||||
var _a;
|
||||
const serverUrl = new URL((_a = process.env['GITHUB_SERVER_URL']) !== null && _a !== void 0 ? _a : 'https://github.com');
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+22
@@ -1340,6 +1340,28 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
packageurl-js
|
||||
MIT
|
||||
Copyright (c) the purl authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
safe-buffer
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
# Examples on how to use the Dependancy Review Action
|
||||
|
||||
## Basic Usage
|
||||
|
||||
A very basic example of how to use the action. This will run the aciton with the default configuration.
|
||||
|
||||
The full list of configuration options can be found [here](../README.md#configuration-options).
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
```
|
||||
|
||||
## Using an inline configuration
|
||||
|
||||
The following example will fail the action if any vulnerabilities are found with a severity of medium or higher; and if any packages are found with an incompatible license - in this case, the LGPL-2.0 and BSD-2-Clause licenses.
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
```
|
||||
|
||||
## Using a configuration file
|
||||
|
||||
The following example will use a configuration file to configure the action. This is useful if you want to keep your configuration in a single place and makes it easier to manage as the configuration grows.
|
||||
|
||||
The configuration file can be located in the same repository or in a separate repository. Having it in a separate repository might be useful if you plan to use the same configuration across multiple repositories and control it centrally.
|
||||
|
||||
In this example, the configuration file is located in the same repository under `.github/dependency-review-config.yml`. The following configuration will fail the action if any vulnerabilities are found with a severity of critical; and if any packages are found with an incompatible license - in this case, the LGPL-2.0 and BSD-2-Clause licenses.
|
||||
|
||||
```yaml
|
||||
fail-on-severity: 'critical'
|
||||
allow-licenses:
|
||||
- 'LGPL-2.0'
|
||||
- 'BSD-2-Clause'
|
||||
```
|
||||
|
||||
The Dependancy Review Action workflow file will then look like this:
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
config-file: './.github/dependency-review-config.yml'
|
||||
```
|
||||
|
||||
## Using a configuration file from a external repository
|
||||
|
||||
The following example will use a configuration file from an external public GitHub repository to configure the action.
|
||||
|
||||
Let's say that the configuration file is located in `github/octorepo/dependency-review-config.yml@main`
|
||||
|
||||
The Dependancy Review Action workflow file will then look like this:
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
config-file: 'github/octorepo/dependency-review-config.yml@main'
|
||||
```
|
||||
|
||||
## Using a configuration file from a external repository with a personal access token
|
||||
|
||||
The following example will use a configuration file from an external private GtiHub repository to configure the action.
|
||||
|
||||
Let's say that the configuration file is located in `github/octorepo-private/dependency-review-config.yml@main`
|
||||
|
||||
The Dependancy Review Action workflow file will then look like this:
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
config-file: 'github/octorepo-private/dependency-review-config.yml@main'
|
||||
config-file-token: ${{ secrets.GITHUB_TOKEN }} # or a personal access token
|
||||
```
|
||||
|
||||
## Getting the results of the action in the PR as a comment
|
||||
|
||||
Using the `comment-summary-in-pr` you can get the results of the action in the PR as a comment. In order for this to work, the action needs to be able to create a comment in the PR. This requires additional `pull-requests: write` permission.
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
comment-summary-in-pr: true
|
||||
```
|
||||
|
||||
## Exclude dependencies from the license check
|
||||
|
||||
Using the `allow-dependencies-licenses` you can exclude dependencies from the license check. You can exclude dependencies per ecosystem and the values should be provided in a valid [purl](https://github.com/package-url/purl-spec) format.
|
||||
|
||||
In this example, we are excluding `lodash` from `npm` and `requests` from `pip` dependencies from the license check
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
deny-licenses: LGPL-2.0, BSD-2-Clause
|
||||
comment-summary-in-pr: true
|
||||
allow-dependencies-licenses: 'pkg:npm/loadash, pkg:pip/requests'
|
||||
```
|
||||
|
||||
If we were to use configuration file, the configuration would look like this:
|
||||
|
||||
```yaml
|
||||
fail-on-severity: 'critical'
|
||||
allow-licenses:
|
||||
- 'LGPL-2.0'
|
||||
- 'BSD-2-Clause'
|
||||
allow-dependencies-licenses:
|
||||
- 'pkg:npm/loadash'
|
||||
- 'pkg:pip/requests'
|
||||
```
|
||||
|
||||
## Only check for vulnerabilities
|
||||
|
||||
To only do the vulnerability check you can use the `license-check` to disable the license compatibility check (which is done by default).
|
||||
|
||||
```yaml
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
fail-on-severity: critical
|
||||
comment-summary-in-pr: true
|
||||
license-check: false
|
||||
```
|
||||
Generated
+47
-6714
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -33,6 +33,7 @@
|
||||
"got": "^12.6.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"octokit": "^2.0.14",
|
||||
"packageurl-js": "^1.0.2",
|
||||
"spdx-expression-parse": "^3.0.1",
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"yaml": "^2.2.1",
|
||||
@@ -41,8 +42,6 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.23",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@types/spdx-expression-parse": "^3.0.2",
|
||||
"@types/spdx-satisfies": "^0.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
|
||||
@@ -22,6 +22,12 @@ const defaultConfig: ConfigurationOptions = {
|
||||
allow_ghsas: [],
|
||||
allow_licenses: ['MIT'],
|
||||
deny_licenses: [],
|
||||
allow_dependencies_licenses: [
|
||||
'pkg:npm/[email protected]',
|
||||
'pkg:pip/requests',
|
||||
'pkg:pip/certifi',
|
||||
'pkg:pip/[email protected]'
|
||||
],
|
||||
comment_summary_in_pr: true
|
||||
}
|
||||
|
||||
|
||||
+29
-1
@@ -5,6 +5,7 @@ import * as core from '@actions/core'
|
||||
import * as z from 'zod'
|
||||
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
||||
import {isSPDXValid, octokitClient} from './utils'
|
||||
import {PackageURL} from 'packageurl-js'
|
||||
|
||||
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
||||
|
||||
@@ -29,6 +30,9 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'))
|
||||
const allow_licenses = parseList(getOptionalInput('allow-licenses'))
|
||||
const deny_licenses = parseList(getOptionalInput('deny-licenses'))
|
||||
const allow_dependencies_licenses = parseList(
|
||||
getOptionalInput('allow-dependencies-licenses')
|
||||
)
|
||||
const allow_ghsas = parseList(getOptionalInput('allow-ghsas'))
|
||||
const license_check = getOptionalBoolean('license-check')
|
||||
const vulnerability_check = getOptionalBoolean('vulnerability-check')
|
||||
@@ -36,6 +40,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
const head_ref = getOptionalInput('head-ref')
|
||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
|
||||
|
||||
validatepurl(allow_dependencies_licenses)
|
||||
validateLicenses('allow-licenses', allow_licenses)
|
||||
validateLicenses('deny-licenses', deny_licenses)
|
||||
|
||||
@@ -44,6 +49,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
||||
fail_on_scopes,
|
||||
allow_licenses,
|
||||
deny_licenses,
|
||||
allow_dependencies_licenses,
|
||||
allow_ghsas,
|
||||
license_check,
|
||||
vulnerability_check,
|
||||
@@ -130,7 +136,8 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
|
||||
'allow-licenses',
|
||||
'deny-licenses',
|
||||
'fail-on-scopes',
|
||||
'allow-ghsas'
|
||||
'allow-ghsas',
|
||||
'allow-dependencies-licenses'
|
||||
]
|
||||
|
||||
for (const key of Object.keys(data)) {
|
||||
@@ -149,6 +156,11 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
|
||||
validateLicenses(key, data[key])
|
||||
}
|
||||
|
||||
// validate purls from the allow-dependencies-licenses
|
||||
if (key === 'allow-dependencies-licenses') {
|
||||
validatepurl(data[key])
|
||||
}
|
||||
|
||||
// get rid of the ugly dashes from the actions conventions
|
||||
if (key.includes('-')) {
|
||||
data[key.replace(/-/g, '_')] = data[key]
|
||||
@@ -187,3 +199,19 @@ async function getRemoteConfig(configOpts: {
|
||||
throw new Error('Error fetching remote config file')
|
||||
}
|
||||
}
|
||||
function validatepurl(allow_dependencies_licenses: string[] | undefined): void {
|
||||
//validate that the provided elements of the string are in valid purl format
|
||||
if (allow_dependencies_licenses === undefined) {
|
||||
return
|
||||
}
|
||||
const invalid_purls = allow_dependencies_licenses.filter(
|
||||
purl => !PackageURL.fromString(purl)
|
||||
)
|
||||
|
||||
if (invalid_purls.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid purl(s) in allow-dependencies-licenses: ${invalid_purls}`
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+31
-1
@@ -1,6 +1,7 @@
|
||||
import spdxSatisfies from 'spdx-satisfies'
|
||||
import {Change, Changes} from './schemas'
|
||||
import {isSPDXValid, octokitClient} from './utils'
|
||||
import {isSPDXValid, octokitClient, isDefined} from './utils'
|
||||
import {PackageURL} from 'packageurl-js'
|
||||
|
||||
/**
|
||||
* Loops through a list of changes, filtering and returning the
|
||||
@@ -24,11 +25,40 @@ export async function getInvalidLicenseChanges(
|
||||
licenses: {
|
||||
allow?: string[]
|
||||
deny?: string[]
|
||||
licenseExclusions?: string[]
|
||||
}
|
||||
): Promise<InvalidLicenseChanges> {
|
||||
const {allow, deny} = licenses
|
||||
const licenseExclusions = licenses.licenseExclusions?.map(
|
||||
(pkgUrl: string) => {
|
||||
return PackageURL.fromString(pkgUrl)
|
||||
}
|
||||
)
|
||||
|
||||
const groupedChanges = await groupChanges(changes)
|
||||
|
||||
// filter out changes that are part of exclusions list - config.allow_dependencies_licenses
|
||||
groupedChanges.licensed = groupedChanges.licensed.filter(change => {
|
||||
const changeAsPackageURL = new PackageURL(
|
||||
change.ecosystem,
|
||||
undefined,
|
||||
change.name,
|
||||
change.version,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
if (
|
||||
isDefined(licenseExclusions) &&
|
||||
licenseExclusions.findIndex(
|
||||
exclusion =>
|
||||
exclusion.type === changeAsPackageURL.type &&
|
||||
exclusion.name === changeAsPackageURL.name
|
||||
) !== -1
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
const licensedChanges: Changes = groupedChanges.licensed
|
||||
|
||||
const invalidLicenseChanges: InvalidLicenseChanges = {
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ import {commentPr} from './comment-pr'
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const config = await readConfig()
|
||||
|
||||
const refs = getRefs(config, github.context)
|
||||
|
||||
const changes = await dependencyGraph.compare({
|
||||
@@ -55,7 +56,8 @@ async function run(): Promise<void> {
|
||||
filteredChanges,
|
||||
{
|
||||
allow: config.allow_licenses,
|
||||
deny: config.deny_licenses
|
||||
deny: config.deny_licenses,
|
||||
licenseExclusions: config.allow_dependencies_licenses
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ export const ConfigurationOptionsSchema = z
|
||||
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
|
||||
allow_licenses: z.array(z.string()).optional(),
|
||||
deny_licenses: z.array(z.string()).optional(),
|
||||
allow_dependencies_licenses: z.array(z.string()).optional(),
|
||||
allow_ghsas: z.array(z.string()).default([]),
|
||||
license_check: z.boolean().default(true),
|
||||
vulnerability_check: z.boolean().default(true),
|
||||
|
||||
@@ -143,6 +143,13 @@ export function addLicensesToSummary(
|
||||
`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`
|
||||
)
|
||||
}
|
||||
if (config.allow_dependencies_licenses) {
|
||||
core.summary.addQuote(
|
||||
`<strong>Excluded from license check</strong>: ${config.allow_dependencies_licenses.join(
|
||||
', '
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
core.debug(
|
||||
`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`
|
||||
|
||||
@@ -41,6 +41,11 @@ export function isSPDXValid(license: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
// function to check if a value is not null or undefined
|
||||
export function isDefined<T>(value: T | null | undefined): value is T {
|
||||
return value !== null && value !== undefined
|
||||
}
|
||||
|
||||
function isEnterprise(): boolean {
|
||||
const serverUrl = new URL(
|
||||
process.env['GITHUB_SERVER_URL'] ?? 'https://github.com'
|
||||
|
||||
Reference in New Issue
Block a user