@@ -3,6 +3,7 @@ import process from 'node:process'
3
3
4
4
import semver from 'semver'
5
5
6
+ import { PackageURL } from '@socketregistry/packageurl-js'
6
7
import { getManifestData } from '@socketsecurity/registry'
7
8
import { arrayUnique } from '@socketsecurity/registry/lib/arrays'
8
9
import { hasOwn } from '@socketsecurity/registry/lib/objects'
@@ -32,6 +33,8 @@ import type { SocketArtifact } from '../../../../utils/alert/artifact'
32
33
import type { SafeNode } from '../node'
33
34
import type { Writable } from 'node:stream'
34
35
36
+ type Packument = Awaited < ReturnType < typeof fetchPackagePackument > >
37
+
35
38
type SocketPackageAlert = {
36
39
key : string
37
40
type : string
@@ -59,19 +62,20 @@ function findBestPatchVersion(
59
62
name : string ,
60
63
availableVersions : string [ ] ,
61
64
currentMajorVersion : number ,
62
- vulnerableVersionRange : string ,
63
- _firstPatchedVersionIdentifier : string
65
+ vulnerableVersionRange ? : string ,
66
+ _firstPatchedVersionIdentifier ? : string
64
67
) : string | null {
65
- const manifestVersion = getManifestData ( NPM , name ) ?. version
68
+ const manifestData = getManifestData ( NPM , name )
66
69
// Filter versions that are within the current major version and are not in the vulnerable range
67
- const eligibleVersions = availableVersions . filter ( version => {
68
- const isSameMajor = semver . major ( version ) === currentMajorVersion
69
- const isNotVulnerable = ! semver . satisfies ( version , vulnerableVersionRange )
70
- if ( isSameMajor && isNotVulnerable ) {
71
- return true
72
- }
73
- return ! ! manifestVersion
74
- } )
70
+ const eligibleVersions =
71
+ manifestData && manifestData . name === manifestData . package
72
+ ? availableVersions
73
+ : availableVersions . filter (
74
+ version =>
75
+ semver . major ( version ) === currentMajorVersion &&
76
+ ( ! vulnerableVersionRange ||
77
+ ! semver . satisfies ( version , vulnerableVersionRange ) )
78
+ )
75
79
if ( eligibleVersions . length === 0 ) {
76
80
return null
77
81
}
@@ -279,79 +283,119 @@ async function updateAdvisoryDependencies(
279
283
const tree = arb . idealTree !
280
284
for ( const name of Object . keys ( patchDataByPkg ) ) {
281
285
const nodes = findPackageNodes ( tree , name )
282
- if ( ! nodes . length ) {
286
+ const patchData = patchDataByPkg [ name ] !
287
+ if ( ! nodes . length || ! patchData . length ) {
283
288
continue
284
289
}
285
- // Fetch packument to get available versions.
286
290
// eslint-disable-next-line no-await-in-loop
287
291
const packument = await fetchPackagePackument ( name )
292
+ if ( ! packument ) {
293
+ continue
294
+ }
288
295
for ( const node of nodes ) {
289
- const { version } = node
290
- const majorVerNum = semver . major ( version )
291
- const availableVersions = packument ? Object . keys ( packument . versions ) : [ ]
292
- const patchData = patchDataByPkg [ name ] !
293
296
for ( const {
294
297
firstPatchedVersionIdentifier,
295
298
vulnerableVersionRange
296
299
} of patchData ) {
297
- // Find the highest non-vulnerable version within the same major range
298
- const targetVersion = findBestPatchVersion (
299
- name ,
300
- availableVersions ,
301
- majorVerNum ,
300
+ updateNode (
301
+ node ,
302
+ packument ,
302
303
vulnerableVersionRange ,
303
304
firstPatchedVersionIdentifier
304
305
)
305
- const targetPackument = targetVersion
306
- ? packument . versions [ targetVersion ]
307
- : undefined
308
- // Check !targetVersion to make TypeScript happy.
309
- if ( ! targetVersion || ! targetPackument ) {
310
- // No suitable patch version found.
311
- continue
312
- }
313
- // Use Object.defineProperty to override the version.
314
- Object . defineProperty ( node , 'version' , {
315
- configurable : true ,
316
- enumerable : true ,
317
- get : ( ) => targetVersion
318
- } )
319
- node . package . version = targetVersion
320
- // Update resolved and clear integrity for the new version.
321
- node . resolved = `${ NPM_REGISTRY_URL } /${ name } /-/${ name } -${ targetVersion } .tgz`
322
- if ( node . integrity ) {
323
- delete node . integrity
324
- }
325
- if ( 'deprecated' in targetPackument ) {
326
- node . package [ 'deprecated' ] = < string > targetPackument . deprecated
327
- } else {
328
- delete node . package [ 'deprecated' ]
329
- }
330
- const newDeps = { ...targetPackument . dependencies }
331
- const { dependencies : oldDeps } = node . package
332
- node . package . dependencies = newDeps
333
- if ( oldDeps ) {
334
- for ( const oldDepName of Object . keys ( oldDeps ) ) {
335
- if ( ! hasOwn ( newDeps , oldDepName ) ) {
336
- node . edgesOut . get ( oldDepName ) ?. detach ( )
337
- }
338
- }
339
- }
340
- for ( const newDepName of Object . keys ( newDeps ) ) {
341
- if ( ! hasOwn ( oldDeps , newDepName ) ) {
342
- node . addEdgeOut ( ( < unknown > new Edge ( {
343
- from : node ,
344
- name : newDepName ,
345
- spec : newDeps [ newDepName ] ,
346
- type : 'prod'
347
- } ) ) as SafeEdge )
348
- }
349
- }
350
306
}
351
307
}
352
308
}
353
309
}
354
310
311
+ async function updateSocketRegistryDependencies ( arb : SafeArborist ) {
312
+ await arb . buildIdealTree ( )
313
+ const manifest = getManifestData ( NPM ) !
314
+ const tree = arb . idealTree !
315
+ for ( const { 1 : data } of manifest ) {
316
+ const nodes = findPackageNodes ( tree , data . name )
317
+ if ( ! nodes . length ) {
318
+ continue
319
+ }
320
+ // eslint-disable-next-line no-await-in-loop
321
+ const packument = await fetchPackagePackument ( data . name )
322
+ if ( ! packument ) {
323
+ continue
324
+ }
325
+ for ( const node of nodes ) {
326
+ updateNode ( node , packument )
327
+ }
328
+ }
329
+ }
330
+
331
+ function updateNode (
332
+ node : SafeNode ,
333
+ packument : Packument ,
334
+ vulnerableVersionRange ?: string ,
335
+ firstPatchedVersionIdentifier ?: string
336
+ ) {
337
+ const { version } = node
338
+ const majorVerNum = semver . major ( version )
339
+ const availableVersions = packument ? Object . keys ( packument . versions ) : [ ]
340
+ // Find the highest non-vulnerable version within the same major range
341
+ const targetVersion = findBestPatchVersion (
342
+ node . name ,
343
+ availableVersions ,
344
+ majorVerNum ,
345
+ vulnerableVersionRange ,
346
+ firstPatchedVersionIdentifier
347
+ )
348
+ const targetPackument = targetVersion
349
+ ? packument . versions [ targetVersion ]
350
+ : undefined
351
+ // Check !targetVersion to make TypeScript happy.
352
+ if ( ! targetVersion || ! targetPackument ) {
353
+ // No suitable patch version found.
354
+ return node
355
+ }
356
+ // Use Object.defineProperty to override the version.
357
+ Object . defineProperty ( node , 'version' , {
358
+ configurable : true ,
359
+ enumerable : true ,
360
+ get : ( ) => targetVersion
361
+ } )
362
+ node . package . version = targetVersion
363
+ // Update resolved and clear integrity for the new version.
364
+ const purlObj = PackageURL . fromString ( `pkg:npm/${ node . name } ` )
365
+ node . resolved = `${ NPM_REGISTRY_URL } /${ node . name } /-/${ purlObj . name } -${ targetVersion } .tgz`
366
+ const { integrity } = targetPackument . dist
367
+ if ( integrity ) {
368
+ node . integrity = integrity
369
+ } else {
370
+ delete node . integrity
371
+ }
372
+ if ( 'deprecated' in targetPackument ) {
373
+ node . package [ 'deprecated' ] = < string > targetPackument . deprecated
374
+ } else {
375
+ delete node . package [ 'deprecated' ]
376
+ }
377
+ const newDeps = { ...targetPackument . dependencies }
378
+ const { dependencies : oldDeps } = node . package
379
+ node . package . dependencies = newDeps
380
+ if ( oldDeps ) {
381
+ for ( const oldDepName of Object . keys ( oldDeps ) ) {
382
+ if ( ! hasOwn ( newDeps , oldDepName ) ) {
383
+ node . edgesOut . get ( oldDepName ) ?. detach ( )
384
+ }
385
+ }
386
+ }
387
+ for ( const newDepName of Object . keys ( newDeps ) ) {
388
+ if ( ! hasOwn ( oldDeps , newDepName ) ) {
389
+ node . addEdgeOut ( ( < unknown > new Edge ( {
390
+ from : node ,
391
+ name : newDepName ,
392
+ spec : newDeps [ newDepName ] ,
393
+ type : 'prod'
394
+ } ) ) as SafeEdge )
395
+ }
396
+ }
397
+ }
398
+
355
399
export const kRiskyReify = Symbol ( 'riskyReify' )
356
400
357
401
type SafeArborist = ArboristClass & {
@@ -363,6 +407,7 @@ export async function reify(
363
407
...args : Parameters < InstanceType < ArboristClass > [ 'reify' ] >
364
408
) : Promise < SafeNode > {
365
409
const IPC = await getIPC ( )
410
+ await updateSocketRegistryDependencies ( this )
366
411
const runningFixCommand = ! ! IPC [ SOCKET_CLI_FIX_PACKAGE_LOCK_FILE ]
367
412
// We are assuming `this[_diffTrees]()` has been called by `super.reify(...)`:
368
413
// https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/reify.js#L141
0 commit comments