Merge pull request #423 from theztefan/allow-list-dependencies
Exclude dependencies from license checks
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.
|
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` |
|
||||||
| `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 |
|
| `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 |
|
||||||
| `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` |
|
| `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
|
†will be supported with GitHub Enterprise Server 3.8
|
||||||
|
|
||||||
@@ -139,6 +140,8 @@ allow_licenses:
|
|||||||
- 'MIT'
|
- 'MIT'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For more examples of how to use this action and its configuration options, see the [examples](docs/examples.md) page.
|
||||||
|
|
||||||
### Considerations
|
### Considerations
|
||||||
|
|
||||||
- Checking for licenses is not supported on Enterprise Server.
|
- Checking for licenses is not supported on Enterprise Server.
|
||||||
|
|||||||
@@ -49,6 +49,32 @@ const rubyChange: Change = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pipChange: Change = {
|
||||||
|
change_type: 'added',
|
||||||
|
manifest: 'requirements.txt',
|
||||||
|
ecosystem: 'pip',
|
||||||
|
name: 'package-1',
|
||||||
|
version: '1.1.1',
|
||||||
|
package_url: 'pkg:pip/[email protected]',
|
||||||
|
license: 'MIT',
|
||||||
|
source_repository_url: 'github.com/some-repo',
|
||||||
|
scope: 'runtime',
|
||||||
|
vulnerabilities: [
|
||||||
|
{
|
||||||
|
severity: 'moderate',
|
||||||
|
advisory_ghsa_id: 'second-random_string',
|
||||||
|
advisory_summary: 'not so dangerous',
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
jest.mock('@actions/core')
|
jest.mock('@actions/core')
|
||||||
|
|
||||||
const mockOctokit = {
|
const mockOctokit = {
|
||||||
@@ -153,6 +179,34 @@ test('it adds all licenses to unresolved if it is unable to determine the validi
|
|||||||
expect(invalidLicenses.unresolved.length).toEqual(2)
|
expect(invalidLicenses.unresolved.length).toEqual(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('it does not filter out changes that are on the exclusions list', async () => {
|
||||||
|
const changes: Changes = [pipChange, npmChange, rubyChange]
|
||||||
|
const licensesConfig = {
|
||||||
|
allow: ['BSD'],
|
||||||
|
licenseExclusions: ['pkg:pip/[email protected]', 'pkg:npm/[email protected]']
|
||||||
|
}
|
||||||
|
const invalidLicenses = await getInvalidLicenseChanges(
|
||||||
|
changes,
|
||||||
|
licensesConfig
|
||||||
|
)
|
||||||
|
expect(invalidLicenses.forbidden.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('it does filters out changes if they are not on the exclusions list', async () => {
|
||||||
|
const changes: Changes = [pipChange, npmChange, rubyChange]
|
||||||
|
const licensesConfig = {
|
||||||
|
allow: ['BSD'],
|
||||||
|
licenseExclusions: ['pkg:pip/[email protected]', 'pkg:npm/[email protected]']
|
||||||
|
}
|
||||||
|
const invalidLicenses = await getInvalidLicenseChanges(
|
||||||
|
changes,
|
||||||
|
licensesConfig
|
||||||
|
)
|
||||||
|
expect(invalidLicenses.forbidden.length).toEqual(2)
|
||||||
|
expect(invalidLicenses.forbidden[0]).toBe(pipChange)
|
||||||
|
expect(invalidLicenses.forbidden[1]).toBe(npmChange)
|
||||||
|
})
|
||||||
|
|
||||||
describe('GH License API fallback', () => {
|
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 () => {
|
test('it calls licenses endpoint if atleast one of the changes has null license and valid source_repository_url', async () => {
|
||||||
const nullLicenseChange = {
|
const nullLicenseChange = {
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ inputs:
|
|||||||
deny-licenses:
|
deny-licenses:
|
||||||
description: Comma-separated list of forbidden licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
|
description: Comma-separated list of forbidden licenses (e.g. "MIT, GPL 3.0, BSD 2 Clause")
|
||||||
required: false
|
required: false
|
||||||
|
allow-dependencies-licenses:
|
||||||
|
description: Comma-separated list of dependencies in purl format (e.g. "pkg:npm/express, pkg:pip/pycrypto"). These dependencies will be permitted to use any license, no matter what license policy is enforced otherwise.
|
||||||
|
required: false
|
||||||
allow-ghsas:
|
allow-ghsas:
|
||||||
description: Comma-separated list of allowed GitHub Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
|
description: Comma-separated list of allowed GitHub Advisory IDs (e.g. "GHSA-abcd-1234-5679, GHSA-efgh-1234-5679")
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
+293
-2
@@ -272,10 +272,30 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|||||||
exports.getInvalidLicenseChanges = void 0;
|
exports.getInvalidLicenseChanges = void 0;
|
||||||
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
|
const spdx_satisfies_1 = __importDefault(__nccwpck_require__(4424));
|
||||||
const utils_1 = __nccwpck_require__(918);
|
const utils_1 = __nccwpck_require__(918);
|
||||||
|
const packageurl_js_1 = __nccwpck_require__(8915);
|
||||||
function getInvalidLicenseChanges(changes, licenses) {
|
function getInvalidLicenseChanges(changes, licenses) {
|
||||||
|
var _a;
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const { allow, deny } = licenses;
|
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);
|
const groupedChanges = yield groupChanges(changes);
|
||||||
|
// Takes the changes from the groupedChanges object and filters out the ones that are part of the exclusions list
|
||||||
|
// It does by creating a new PackageURL object from the change and comparing it to the exclusions list
|
||||||
|
groupedChanges.licensed = groupedChanges.licensed.filter(change => {
|
||||||
|
const changeAsPackageURL = packageurl_js_1.PackageURL.fromString(change.package_url);
|
||||||
|
// We want to find if the licenseExclussion list contains the PackageURL of the Change
|
||||||
|
// If it does, we want to filter it out and therefore return false
|
||||||
|
// If it doesn't, we want to keep it and therefore return true
|
||||||
|
if (licenseExclusions !== null &&
|
||||||
|
licenseExclusions !== undefined &&
|
||||||
|
licenseExclusions.findIndex(exclusion => exclusion.type === changeAsPackageURL.type &&
|
||||||
|
exclusion.name === changeAsPackageURL.name) !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
const licensedChanges = groupedChanges.licensed;
|
const licensedChanges = groupedChanges.licensed;
|
||||||
const invalidLicenseChanges = {
|
const invalidLicenseChanges = {
|
||||||
unlicensed: groupedChanges.unlicensed,
|
unlicensed: groupedChanges.unlicensed,
|
||||||
@@ -485,7 +505,8 @@ function run() {
|
|||||||
change.vulnerabilities.length > 0);
|
change.vulnerabilities.length > 0);
|
||||||
const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, {
|
const invalidLicenseChanges = yield (0, licenses_1.getInvalidLicenseChanges)(filteredChanges, {
|
||||||
allow: config.allow_licenses,
|
allow: config.allow_licenses,
|
||||||
deny: config.deny_licenses
|
deny: config.deny_licenses,
|
||||||
|
licenseExclusions: config.allow_dependencies_licenses
|
||||||
});
|
});
|
||||||
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config);
|
summary.addSummaryToSummary(vulnerableChanges, invalidLicenseChanges, config);
|
||||||
if (snapshot_warnings) {
|
if (snapshot_warnings) {
|
||||||
@@ -684,6 +705,7 @@ exports.ConfigurationOptionsSchema = z
|
|||||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||||
allow_licenses: z.array(z.string()).optional(),
|
allow_licenses: z.array(z.string()).optional(),
|
||||||
deny_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([]),
|
allow_ghsas: 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),
|
||||||
@@ -853,6 +875,9 @@ function addLicensesToSummary(invalidLicenseChanges, config) {
|
|||||||
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
if (config.deny_licenses && config.deny_licenses.length > 0) {
|
||||||
core.summary.addQuote(`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`);
|
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(`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`);
|
||||||
core.debug(`${invalidLicenseChanges.unresolved.length} licenses could not be validated`);
|
core.debug(`${invalidLicenseChanges.unresolved.length} licenses could not be validated`);
|
||||||
}
|
}
|
||||||
@@ -37498,6 +37523,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:
|
/***/ 1867:
|
||||||
@@ -45900,6 +46170,7 @@ const core = __importStar(__nccwpck_require__(2186));
|
|||||||
const z = __importStar(__nccwpck_require__(3301));
|
const z = __importStar(__nccwpck_require__(3301));
|
||||||
const schemas_1 = __nccwpck_require__(1129);
|
const schemas_1 = __nccwpck_require__(1129);
|
||||||
const utils_1 = __nccwpck_require__(1314);
|
const utils_1 = __nccwpck_require__(1314);
|
||||||
|
const packageurl_js_1 = __nccwpck_require__(8915);
|
||||||
function readConfig() {
|
function readConfig() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const inlineConfig = readInlineConfig();
|
const inlineConfig = readInlineConfig();
|
||||||
@@ -45917,12 +46188,14 @@ function readInlineConfig() {
|
|||||||
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'));
|
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'));
|
||||||
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_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');
|
||||||
const base_ref = getOptionalInput('base-ref');
|
const base_ref = getOptionalInput('base-ref');
|
||||||
const head_ref = getOptionalInput('head-ref');
|
const head_ref = getOptionalInput('head-ref');
|
||||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr');
|
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr');
|
||||||
|
validatePURL(allow_dependencies_licenses);
|
||||||
validateLicenses('allow-licenses', allow_licenses);
|
validateLicenses('allow-licenses', allow_licenses);
|
||||||
validateLicenses('deny-licenses', deny_licenses);
|
validateLicenses('deny-licenses', deny_licenses);
|
||||||
const keys = {
|
const keys = {
|
||||||
@@ -45930,6 +46203,7 @@ function readInlineConfig() {
|
|||||||
fail_on_scopes,
|
fail_on_scopes,
|
||||||
allow_licenses,
|
allow_licenses,
|
||||||
deny_licenses,
|
deny_licenses,
|
||||||
|
allow_dependencies_licenses,
|
||||||
allow_ghsas,
|
allow_ghsas,
|
||||||
license_check,
|
license_check,
|
||||||
vulnerability_check,
|
vulnerability_check,
|
||||||
@@ -45998,7 +46272,8 @@ function parseConfigFile(configData) {
|
|||||||
'allow-licenses',
|
'allow-licenses',
|
||||||
'deny-licenses',
|
'deny-licenses',
|
||||||
'fail-on-scopes',
|
'fail-on-scopes',
|
||||||
'allow-ghsas'
|
'allow-ghsas',
|
||||||
|
'allow-dependencies-licenses'
|
||||||
];
|
];
|
||||||
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
|
||||||
@@ -46013,6 +46288,10 @@ function parseConfigFile(configData) {
|
|||||||
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
if (key === 'allow-licenses' || key === 'deny-licenses') {
|
||||||
validateLicenses(key, data[key]);
|
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
|
// get rid of the ugly dashes from the actions conventions
|
||||||
if (key.includes('-')) {
|
if (key.includes('-')) {
|
||||||
data[key.replace(/-/g, '_')] = data[key];
|
data[key.replace(/-/g, '_')] = data[key];
|
||||||
@@ -46048,6 +46327,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -46195,6 +46485,7 @@ exports.ConfigurationOptionsSchema = z
|
|||||||
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
fail_on_scopes: z.array(z.enum(exports.SCOPES)).default(['runtime']),
|
||||||
allow_licenses: z.array(z.string()).optional(),
|
allow_licenses: z.array(z.string()).optional(),
|
||||||
deny_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([]),
|
allow_ghsas: 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),
|
||||||
|
|||||||
+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.
|
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
|
safe-buffer
|
||||||
MIT
|
MIT
|
||||||
The MIT License (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 action 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. The values should be provided in [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
+9
-6839
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dependency-review-action",
|
"name": "dependency-review-action",
|
||||||
"version": "3.0.4",
|
"version": "3.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A GitHub Action for Dependency Review",
|
"description": "A GitHub Action for Dependency Review",
|
||||||
"main": "lib/main.js",
|
"main": "lib/main.js",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"got": "^12.6.0",
|
"got": "^12.6.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"octokit": "^2.0.16",
|
"octokit": "^2.0.16",
|
||||||
|
"packageurl-js": "^1.0.2",
|
||||||
"spdx-expression-parse": "^3.0.1",
|
"spdx-expression-parse": "^3.0.1",
|
||||||
"spdx-satisfies": "^5.0.1",
|
"spdx-satisfies": "^5.0.1",
|
||||||
"yaml": "^2.3.1",
|
"yaml": "^2.3.1",
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ const defaultConfig: ConfigurationOptions = {
|
|||||||
allow_ghsas: [],
|
allow_ghsas: [],
|
||||||
allow_licenses: ['MIT'],
|
allow_licenses: ['MIT'],
|
||||||
deny_licenses: [],
|
deny_licenses: [],
|
||||||
|
allow_dependencies_licenses: [
|
||||||
|
'pkg:npm/[email protected]',
|
||||||
|
'pkg:pip/requests',
|
||||||
|
'pkg:pip/certifi',
|
||||||
|
'pkg:pip/[email protected]'
|
||||||
|
],
|
||||||
comment_summary_in_pr: true
|
comment_summary_in_pr: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-1
@@ -5,6 +5,7 @@ import * as core from '@actions/core'
|
|||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
import {ConfigurationOptions, ConfigurationOptionsSchema} from './schemas'
|
||||||
import {isSPDXValid, octokitClient} from './utils'
|
import {isSPDXValid, octokitClient} from './utils'
|
||||||
|
import {PackageURL} from 'packageurl-js'
|
||||||
|
|
||||||
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
type ConfigurationOptionsPartial = Partial<ConfigurationOptions>
|
||||||
|
|
||||||
@@ -29,6 +30,9 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
|||||||
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'))
|
const fail_on_scopes = parseList(getOptionalInput('fail-on-scopes'))
|
||||||
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_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')
|
||||||
@@ -36,6 +40,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
|||||||
const head_ref = getOptionalInput('head-ref')
|
const head_ref = getOptionalInput('head-ref')
|
||||||
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
|
const comment_summary_in_pr = getOptionalBoolean('comment-summary-in-pr')
|
||||||
|
|
||||||
|
validatePURL(allow_dependencies_licenses)
|
||||||
validateLicenses('allow-licenses', allow_licenses)
|
validateLicenses('allow-licenses', allow_licenses)
|
||||||
validateLicenses('deny-licenses', deny_licenses)
|
validateLicenses('deny-licenses', deny_licenses)
|
||||||
|
|
||||||
@@ -44,6 +49,7 @@ function readInlineConfig(): ConfigurationOptionsPartial {
|
|||||||
fail_on_scopes,
|
fail_on_scopes,
|
||||||
allow_licenses,
|
allow_licenses,
|
||||||
deny_licenses,
|
deny_licenses,
|
||||||
|
allow_dependencies_licenses,
|
||||||
allow_ghsas,
|
allow_ghsas,
|
||||||
license_check,
|
license_check,
|
||||||
vulnerability_check,
|
vulnerability_check,
|
||||||
@@ -130,7 +136,8 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
|
|||||||
'allow-licenses',
|
'allow-licenses',
|
||||||
'deny-licenses',
|
'deny-licenses',
|
||||||
'fail-on-scopes',
|
'fail-on-scopes',
|
||||||
'allow-ghsas'
|
'allow-ghsas',
|
||||||
|
'allow-dependencies-licenses'
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const key of Object.keys(data)) {
|
for (const key of Object.keys(data)) {
|
||||||
@@ -149,6 +156,11 @@ function parseConfigFile(configData: string): ConfigurationOptionsPartial {
|
|||||||
validateLicenses(key, data[key])
|
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
|
// get rid of the ugly dashes from the actions conventions
|
||||||
if (key.includes('-')) {
|
if (key.includes('-')) {
|
||||||
data[key.replace(/-/g, '_')] = data[key]
|
data[key.replace(/-/g, '_')] = data[key]
|
||||||
@@ -187,3 +199,19 @@ async function getRemoteConfig(configOpts: {
|
|||||||
throw new Error('Error fetching remote config file')
|
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,17 +1,19 @@
|
|||||||
import spdxSatisfies from 'spdx-satisfies'
|
import spdxSatisfies from 'spdx-satisfies'
|
||||||
import {Change, Changes} from './schemas'
|
import {Change, Changes} from './schemas'
|
||||||
import {isSPDXValid, octokitClient} from './utils'
|
import {isSPDXValid, octokitClient} from './utils'
|
||||||
|
import {PackageURL} from 'packageurl-js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loops through a list of changes, filtering and returning the
|
* Loops through a list of changes, filtering and returning the
|
||||||
* ones that don't conform to the licenses allow/deny lists.
|
* ones that don't conform to the licenses allow/deny lists.
|
||||||
|
* It will also filter out the changes which are defined in the licenseExclusions list.
|
||||||
*
|
*
|
||||||
* Keep in mind that we don't let users specify both an allow and a deny
|
* Keep in mind that we don't let users specify both an allow and a deny
|
||||||
* list in their config files, so this code works under the assumption that
|
* list in their config files, so this code works under the assumption that
|
||||||
* one of the two list parameters will be empty. If both lists are provided,
|
* one of the two list parameters will be empty. If both lists are provided,
|
||||||
* we will ignore the deny list.
|
* we will ignore the deny list.
|
||||||
* @param {Change[]} changes The list of changes to filter.
|
* @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.
|
* @param { { allow?: string[], deny?: string[], licenseExclusions?: string[]}} licenses An object with `allow`/`deny`/`licenseExclusions` keys, each containing a list of licenses.
|
||||||
* @returns {Promise<{Object.<string, Array.<Change>>}} A promise to a Record Object. The keys are strings, unlicensed, unresolved and forbidden. The values are a list of changes
|
* @returns {Promise<{Object.<string, Array.<Change>>}} A promise to a Record Object. The keys are strings, unlicensed, unresolved and forbidden. The values are a list of changes
|
||||||
*/
|
*/
|
||||||
export type InvalidLicenseChangeTypes =
|
export type InvalidLicenseChangeTypes =
|
||||||
@@ -24,11 +26,39 @@ export async function getInvalidLicenseChanges(
|
|||||||
licenses: {
|
licenses: {
|
||||||
allow?: string[]
|
allow?: string[]
|
||||||
deny?: string[]
|
deny?: string[]
|
||||||
|
licenseExclusions?: string[]
|
||||||
}
|
}
|
||||||
): Promise<InvalidLicenseChanges> {
|
): Promise<InvalidLicenseChanges> {
|
||||||
const {allow, deny} = licenses
|
const {allow, deny} = licenses
|
||||||
|
const licenseExclusions = licenses.licenseExclusions?.map(
|
||||||
|
(pkgUrl: string) => {
|
||||||
|
return PackageURL.fromString(pkgUrl)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const groupedChanges = await groupChanges(changes)
|
const groupedChanges = await groupChanges(changes)
|
||||||
|
|
||||||
|
// Takes the changes from the groupedChanges object and filters out the ones that are part of the exclusions list
|
||||||
|
// It does by creating a new PackageURL object from the change and comparing it to the exclusions list
|
||||||
|
groupedChanges.licensed = groupedChanges.licensed.filter(change => {
|
||||||
|
const changeAsPackageURL = PackageURL.fromString(change.package_url)
|
||||||
|
|
||||||
|
// We want to find if the licenseExclussion list contains the PackageURL of the Change
|
||||||
|
// If it does, we want to filter it out and therefore return false
|
||||||
|
// If it doesn't, we want to keep it and therefore return true
|
||||||
|
if (
|
||||||
|
licenseExclusions !== null &&
|
||||||
|
licenseExclusions !== undefined &&
|
||||||
|
licenseExclusions.findIndex(
|
||||||
|
exclusion =>
|
||||||
|
exclusion.type === changeAsPackageURL.type &&
|
||||||
|
exclusion.name === changeAsPackageURL.name
|
||||||
|
) !== -1
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
const licensedChanges: Changes = groupedChanges.licensed
|
const licensedChanges: Changes = groupedChanges.licensed
|
||||||
|
|
||||||
const invalidLicenseChanges: InvalidLicenseChanges = {
|
const invalidLicenseChanges: InvalidLicenseChanges = {
|
||||||
|
|||||||
+3
-1
@@ -20,6 +20,7 @@ import {commentPr} from './comment-pr'
|
|||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const config = await readConfig()
|
const config = await readConfig()
|
||||||
|
|
||||||
const refs = getRefs(config, github.context)
|
const refs = getRefs(config, github.context)
|
||||||
|
|
||||||
const comparison = await dependencyGraph.compare({
|
const comparison = await dependencyGraph.compare({
|
||||||
@@ -57,7 +58,8 @@ async function run(): Promise<void> {
|
|||||||
filteredChanges,
|
filteredChanges,
|
||||||
{
|
{
|
||||||
allow: config.allow_licenses,
|
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']),
|
fail_on_scopes: z.array(z.enum(SCOPES)).default(['runtime']),
|
||||||
allow_licenses: z.array(z.string()).optional(),
|
allow_licenses: z.array(z.string()).optional(),
|
||||||
deny_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([]),
|
allow_ghsas: 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),
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ export function addLicensesToSummary(
|
|||||||
`<strong>Denied Licenses</strong>: ${config.deny_licenses.join(', ')}`
|
`<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(
|
core.debug(
|
||||||
`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`
|
`found ${invalidLicenseChanges.unlicensed.length} unknown licenses`
|
||||||
|
|||||||
Reference in New Issue
Block a user