Skip to content

Commit a945d8b

Browse files
authored
chore(workers/repository): split malicious dependency checks (renovatebot#42508)
* chore(workers/repository): report malicious packages before `packageFiles with updates` As part of future changes, we may modify the `skipReason`s before the update phase, which means that we should ensure these are correct before logging out `packageFiles with updates`. * chore(workers/repository): split malicious dependency checks We will likely run into two specific cases of malicious dependencies - you're currently using one, or you're receiving an update to one. We should make these separate, explicit, cases so users are aware what `malicious` actually means, and what the path forward is.
1 parent c8e14ba commit a945d8b

File tree

3 files changed

+160
-11
lines changed

3 files changed

+160
-11
lines changed

lib/types/skip-reason.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,17 @@ export type SkipReason =
4747
| 'github-token-required'
4848
| 'inherited-dependency'
4949
/**
50-
* The dependency has been detected as explicitly malicious, and should not be updated.
50+
* The dependency has been detected as explicitly malicious.
51+
*
52+
* This reason should be removed before the update phase, so updates can be determined.
5153
*/
52-
| 'malicious';
54+
| 'malicious-version-in-use'
55+
/**
56+
* The dependency has a new dependency version available which has been marked as malicious.
57+
*
58+
* Renovate will not propose any updates, and leave you on the version you are currently on, which is currently known as safe.
59+
*/
60+
| 'malicious-update-proposed';
5361

5462
export type StageName =
5563
| 'current-timestamp'

lib/workers/repository/process/extract-update.spec.ts

Lines changed: 127 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,23 +159,143 @@ describe('workers/repository/process/extract-update', () => {
159159
expect(createVulnerabilitiesMock).toHaveBeenCalledExactlyOnceWith();
160160
});
161161

162-
it('logs a warning for each skipReason=malicious', async () => {
162+
describe('when skipReason=malicious-version-in-use', () => {
163+
it('logs a warning', async () => {
164+
const packageFiles: Record<string, PackageFile[]> = {
165+
npm: [
166+
{
167+
deps: [
168+
{
169+
depType: 'devDependencies',
170+
depName: 'axios',
171+
currentValue: '1.14.1',
172+
datasource: 'npm',
173+
prettyDepType: 'devDependency',
174+
lockedVersion: '1.14.1',
175+
updates: [],
176+
packageName: 'axios',
177+
178+
// most importantly
179+
skipReason: 'malicious-version-in-use',
180+
},
181+
// not malicious
182+
{
183+
depType: 'devDependencies',
184+
depName: 'axios',
185+
currentValue: '1.14.0',
186+
datasource: 'npm',
187+
prettyDepType: 'devDependency',
188+
lockedVersion: '1.14.0',
189+
updates: [],
190+
packageName: 'axios',
191+
},
192+
],
193+
packageFile: 'package.json',
194+
},
195+
],
196+
};
197+
198+
const config = {
199+
repoIsOnboarded: true,
200+
baseBranch: 'main',
201+
};
202+
203+
await lookup(config, packageFiles);
204+
205+
expect(logger.logger.warn).toHaveBeenCalledWith(
206+
{
207+
packageFile: 'package.json',
208+
depName: 'axios',
209+
packageName: 'axios',
210+
manager: 'npm',
211+
datasource: 'npm',
212+
},
213+
'Dependency axios is currently using a malicious version',
214+
);
215+
});
216+
217+
it('deletes the skipReason and skipStage, to allow the update phase to continue updating', async () => {
218+
const packageFiles: Record<string, PackageFile[]> = {
219+
npm: [
220+
{
221+
deps: [
222+
{
223+
depType: 'devDependencies',
224+
depName: 'axios',
225+
currentValue: '1.14.1',
226+
datasource: 'npm',
227+
prettyDepType: 'devDependency',
228+
lockedVersion: '1.14.1',
229+
updates: [],
230+
packageName: 'axios',
231+
232+
// most importantly
233+
skipReason: 'malicious-version-in-use',
234+
skipStage: 'lookup',
235+
},
236+
// not malicious
237+
{
238+
depType: 'devDependencies',
239+
depName: 'axios',
240+
currentValue: '1.14.0',
241+
datasource: 'npm',
242+
prettyDepType: 'devDependency',
243+
lockedVersion: '1.14.0',
244+
updates: [],
245+
packageName: 'axios',
246+
},
247+
],
248+
packageFile: 'package.json',
249+
},
250+
],
251+
};
252+
253+
const config = {
254+
repoIsOnboarded: true,
255+
baseBranch: 'main',
256+
};
257+
258+
await lookup(config, packageFiles);
259+
260+
expect(packageFiles.npm[0].deps[0].skipReason).toBeUndefined();
261+
expect(packageFiles.npm[0].deps[0].skipStage).toBeUndefined();
262+
});
263+
});
264+
265+
it('when skipReason=malicious-version-in-use, it logs a warning for each skipReason', async () => {
163266
const packageFiles: Record<string, PackageFile[]> = {
164267
npm: [
165268
{
166269
deps: [
167270
{
168271
depType: 'devDependencies',
169272
depName: 'axios',
170-
currentValue: '1.14.1',
273+
currentValue: '1.14.0',
171274
datasource: 'npm',
172275
prettyDepType: 'devDependency',
173-
lockedVersion: '1.14.1',
174-
updates: [],
276+
lockedVersion: '1.14.0',
277+
updates: [
278+
{
279+
newVersion: '1.14.1',
280+
},
281+
{
282+
// unrelated, using newValue
283+
newValue: '1.14.2',
284+
},
285+
{
286+
// unrelated
287+
newVersion: '2.0.0',
288+
},
289+
{
290+
// doesn't have a newVersion or newValue
291+
updateType: 'digest',
292+
newDigest: '1234',
293+
},
294+
],
175295
packageName: 'axios',
176296

177297
// most importantly
178-
skipReason: 'malicious',
298+
skipReason: 'malicious-update-proposed',
179299
},
180300
// not malicious
181301
{
@@ -208,8 +328,9 @@ describe('workers/repository/process/extract-update', () => {
208328
packageName: 'axios',
209329
manager: 'npm',
210330
datasource: 'npm',
331+
newVersions: ['1.14.1', '1.14.2', '2.0.0'],
211332
},
212-
'Dependency axios will not be updated as it malicious',
333+
'Dependency axios has update(s) proposed which would update you to a malicious version - skipping',
213334
);
214335
});
215336
});

lib/workers/repository/process/extract-update.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { logger } from '../../../logger/index.ts';
55
import { hashMap } from '../../../modules/manager/index.ts';
66
import type { PackageFile } from '../../../modules/manager/types.ts';
77
import { scm } from '../../../modules/platform/scm.ts';
8+
import { isNotNullOrUndefined } from '../../../util/array.ts';
89
import * as memCache from '../../../util/cache/memory/index.ts';
910
import { getCache } from '../../../util/cache/repository/index.ts';
1011
import type { BaseBranchCache } from '../../../util/cache/repository/types.ts';
@@ -236,12 +237,12 @@ export async function lookup(
236237
config,
237238
packageFiles,
238239
);
240+
reportMaliciousSkippedDependencies(packageFiles);
239241
logger.debug(
240242
{ baseBranch: config.baseBranch, config: packageFiles },
241243
'packageFiles with updates',
242244
);
243245
sortBranches(branches);
244-
reportMaliciousSkippedDependencies(packageFiles);
245246
return { branches, branchList, packageFiles };
246247
}
247248

@@ -255,16 +256,35 @@ export function reportMaliciousSkippedDependencies(
255256
for (const [manager, packageFiles] of Object.entries(allPackageFiles)) {
256257
for (const packageFile of packageFiles) {
257258
for (const dep of packageFile.deps) {
258-
if (dep.skipReason === 'malicious') {
259+
if (dep.skipReason === 'malicious-version-in-use') {
260+
logger.warn(
261+
{
262+
packageFile: packageFile.packageFile,
263+
depName: dep.depName,
264+
packageName: dep.packageName,
265+
manager: manager,
266+
datasource: dep.datasource,
267+
},
268+
`Dependency ${dep.depName} is currently using a malicious version`,
269+
);
270+
271+
// and make sure that it then gets updates proposed in the update phase
272+
delete dep.skipReason;
273+
delete dep.skipStage;
274+
} else if (dep.skipReason === 'malicious-update-proposed') {
275+
const newVersions = dep.updates
276+
?.map((u) => u.newVersion ?? u.newValue)
277+
.filter(isNotNullOrUndefined);
259278
logger.warn(
260279
{
261280
packageFile: packageFile.packageFile,
262281
depName: dep.depName,
263282
packageName: dep.packageName,
264283
manager: manager,
265284
datasource: dep.datasource,
285+
newVersions,
266286
},
267-
`Dependency ${dep.depName} will not be updated as it malicious`,
287+
`Dependency ${dep.depName} has update(s) proposed which would update you to a malicious version - skipping`,
268288
);
269289
}
270290
}

0 commit comments

Comments
 (0)