diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 5f0b8719bac1154f4867c5e78dfd940fb11305d3..5c177150a3d826ffff9df876097f0fa6b87260c1 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -686,6 +686,19 @@ const options = [ cli: false, env: false, }, + { + name: 'vulnerabilityAlerts', + description: + 'Config to apply when Renovate detects a PR is necessary due to vulnerability of existing package version.', + type: 'object', + default: { + groupName: null, + schedule: [], + commitMessageSuffix: '[SECURITY]', + }, + cli: false, + env: false, + }, // Default templates { name: 'branchName', diff --git a/lib/workers/repository/init/index.js b/lib/workers/repository/init/index.js index 80036f44d8171a82ed13b8edc19aea3cf7d245db..8b5b6460af26549ded472d4b0267cae040634adb 100644 --- a/lib/workers/repository/init/index.js +++ b/lib/workers/repository/init/index.js @@ -4,6 +4,7 @@ const { initApis } = require('../init/apis'); const { checkBaseBranch } = require('./base'); const { mergeRenovateConfig } = require('./config'); const { detectSemanticCommits } = require('./semantic'); +const { detectVulnerabilityAlerts } = require('./vulnerability'); async function initRepo(input) { let config = { @@ -19,7 +20,7 @@ async function initRepo(input) { checkIfConfigured(config); config = await checkBaseBranch(config); config.semanticCommits = await detectSemanticCommits(config); - await platform.getVulnerabilityAlerts(); + config = await detectVulnerabilityAlerts(config); return config; } diff --git a/lib/workers/repository/init/vulnerability.js b/lib/workers/repository/init/vulnerability.js new file mode 100644 index 0000000000000000000000000000000000000000..1b68304741703c41c2f6e5e743fdc8732b428b01 --- /dev/null +++ b/lib/workers/repository/init/vulnerability.js @@ -0,0 +1,38 @@ +module.exports = { + detectVulnerabilityAlerts, +}; + +async function detectVulnerabilityAlerts(input) { + if (!(input && input.vulnerabilityAlerts)) { + return input; + } + if (input.vulnerabilityAlerts.enabled === false) { + return input; + } + const alerts = await platform.getVulnerabilityAlerts(); + if (!alerts.length) { + return input; + } + const config = { ...input }; + const alertPackageRules = alerts + .map(alert => { + if (!alert.fixedIn) { + logger.warn({ alert }, 'Vulnerability alert has no fixedIn version'); + return null; + } + const rule = {}; + rule.packageNames = [alert.packageName]; + // Raise only for where the currentVersion is vulnerable + rule.matchCurrentVersion = `< ${alert.fixedIn}`; + // Don't propose upgrades to any versions that are still vulnerable + rule.allowedVersions = `>= ${alert.fixedIn}`; + rule.force = { + ...config.vulnerabilityAlerts, + vulnerabilityAlert: true, + }; + return rule; + }) + .filter(Boolean); + config.packageRules = (config.packageRules || []).concat(alertPackageRules); + return config; +} diff --git a/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap b/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..eeaafc8e18894f1a131f59d9ebe2a58929956c17 --- /dev/null +++ b/test/workers/repository/init/__snapshots__/vulnerability.spec.js.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`workers/repository/init/vulnerability detectVulnerabilityAlerts() returns alerts 1`] = ` +Array [ + Object { + "allowedVersions": ">= 1.1.0", + "force": Object { + "commitMessageSuffix": "[SECURITY]", + "groupName": null, + "schedule": Array [], + "vulnerabilityAlert": true, + }, + "matchCurrentVersion": "< 1.1.0", + "packageNames": Array [ + "some-package", + ], + }, +] +`; diff --git a/test/workers/repository/init/vulnerability.spec.js b/test/workers/repository/init/vulnerability.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f43c4551a0e7f28fbc6822a771e005ec2b68d77b --- /dev/null +++ b/test/workers/repository/init/vulnerability.spec.js @@ -0,0 +1,37 @@ +let config; +beforeEach(() => { + jest.resetAllMocks(); + config = require('../../../_fixtures/config'); +}); + +const { + detectVulnerabilityAlerts, +} = require('../../../../lib/workers/repository/init/vulnerability'); + +describe('workers/repository/init/vulnerability', () => { + describe('detectVulnerabilityAlerts()', () => { + it('returns if alerts are disabled', async () => { + config.vulnerabilityAlerts.enabled = false; + expect(await detectVulnerabilityAlerts(config)).toEqual(config); + }); + it('returns if no alerts', async () => { + delete config.vulnerabilityAlerts.enabled; + platform.getVulnerabilityAlerts.mockReturnValue([]); + expect(await detectVulnerabilityAlerts(config)).toEqual(config); + }); + it('returns alerts', async () => { + delete config.vulnerabilityAlerts.enabled; + platform.getVulnerabilityAlerts.mockReturnValue([ + {}, + { + packageName: 'some-package', + fixedIn: '1.1.0', + }, + {}, + ]); + const res = await detectVulnerabilityAlerts(config); + expect(res.packageRules).toMatchSnapshot(); + expect(res.packageRules).toHaveLength(1); + }); + }); +}); diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap index 3ad256d0c8dfe139c7e0a49d58e29887a03ef9da..52de9513e889a98c3f0b7f5ca5564626479abd2b 100644 --- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap +++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap @@ -61,6 +61,11 @@ Array [ "unpublishSafe": false, "updateLockFiles": true, "updateNotScheduled": true, + "vulnerabilityAlerts": Object { + "commitMessageSuffix": "[SECURITY]", + "groupName": null, + "schedule": Array [], + }, "warnings": Array [], "yarnrc": null, }, @@ -122,6 +127,11 @@ Array [ "unpublishSafe": false, "updateLockFiles": true, "updateNotScheduled": true, + "vulnerabilityAlerts": Object { + "commitMessageSuffix": "[SECURITY]", + "groupName": null, + "schedule": Array [], + }, "warnings": Array [], "yarnrc": null, }, @@ -197,6 +207,11 @@ Array [ "updateLockFiles": true, "updateNotScheduled": true, "updateType": "lockFileMaintenance", + "vulnerabilityAlerts": Object { + "commitMessageSuffix": "[SECURITY]", + "groupName": null, + "schedule": Array [], + }, "warnings": Array [], "yarnrc": null, }, @@ -259,6 +274,11 @@ Array [ "unpublishSafe": false, "updateLockFiles": true, "updateNotScheduled": true, + "vulnerabilityAlerts": Object { + "commitMessageSuffix": "[SECURITY]", + "groupName": null, + "schedule": Array [], + }, "warnings": Array [], "yarnrc": null, }, diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index 986c61f4819a0e05417ca58f16de8ebbf1655335..75ba7aadda398b46a0a3be11b0cfe936ce54dbf0 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -702,4 +702,27 @@ When schedules are in use, it generally means "no updates". However there are ca This is default true, meaning that Renovate will perform certain "desirable" updates to _existing_ PRs even when outside of schedule. If you wish to disable all updates outside of scheduled hours then set this field to false. +## vulnerabilityAlerts + +Use this object to customise PRs that are raised when vulnerability alerts are detected (GitHub-only). For example, to set custom labels and assignees: + +```json +{ + "vulnerabilityAlerts": { + "labels": ["security"], + "assignees": ["@rarkins"] + } +} +``` + +To disable vulnerability alerts completely, set like this: + +```json +{ + "vulnerabilityAlerts": { + "enabled": false + } +} +``` + ## yarnrc