Skip to content

Commit 8aa25ac

Browse files
committed
Abstract out updating arborist nodes
1 parent 2f868fc commit 8aa25ac

File tree

1 file changed

+112
-67
lines changed
  • src/shadow/arborist/lib/arborist

1 file changed

+112
-67
lines changed

src/shadow/arborist/lib/arborist/reify.ts

+112-67
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import process from 'node:process'
33

44
import semver from 'semver'
55

6+
import { PackageURL } from '@socketregistry/packageurl-js'
67
import { getManifestData } from '@socketsecurity/registry'
78
import { arrayUnique } from '@socketsecurity/registry/lib/arrays'
89
import { hasOwn } from '@socketsecurity/registry/lib/objects'
@@ -32,6 +33,8 @@ import type { SocketArtifact } from '../../../../utils/alert/artifact'
3233
import type { SafeNode } from '../node'
3334
import type { Writable } from 'node:stream'
3435

36+
type Packument = Awaited<ReturnType<typeof fetchPackagePackument>>
37+
3538
type SocketPackageAlert = {
3639
key: string
3740
type: string
@@ -59,19 +62,20 @@ function findBestPatchVersion(
5962
name: string,
6063
availableVersions: string[],
6164
currentMajorVersion: number,
62-
vulnerableVersionRange: string,
63-
_firstPatchedVersionIdentifier: string
65+
vulnerableVersionRange?: string,
66+
_firstPatchedVersionIdentifier?: string
6467
): string | null {
65-
const manifestVersion = getManifestData(NPM, name)?.version
68+
const manifestData = getManifestData(NPM, name)
6669
// 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+
)
7579
if (eligibleVersions.length === 0) {
7680
return null
7781
}
@@ -279,79 +283,119 @@ async function updateAdvisoryDependencies(
279283
const tree = arb.idealTree!
280284
for (const name of Object.keys(patchDataByPkg)) {
281285
const nodes = findPackageNodes(tree, name)
282-
if (!nodes.length) {
286+
const patchData = patchDataByPkg[name]!
287+
if (!nodes.length || !patchData.length) {
283288
continue
284289
}
285-
// Fetch packument to get available versions.
286290
// eslint-disable-next-line no-await-in-loop
287291
const packument = await fetchPackagePackument(name)
292+
if (!packument) {
293+
continue
294+
}
288295
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]!
293296
for (const {
294297
firstPatchedVersionIdentifier,
295298
vulnerableVersionRange
296299
} 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,
302303
vulnerableVersionRange,
303304
firstPatchedVersionIdentifier
304305
)
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-
}
350306
}
351307
}
352308
}
353309
}
354310

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+
355399
export const kRiskyReify = Symbol('riskyReify')
356400

357401
type SafeArborist = ArboristClass & {
@@ -363,6 +407,7 @@ export async function reify(
363407
...args: Parameters<InstanceType<ArboristClass>['reify']>
364408
): Promise<SafeNode> {
365409
const IPC = await getIPC()
410+
await updateSocketRegistryDependencies(this)
366411
const runningFixCommand = !!IPC[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]
367412
// We are assuming `this[_diffTrees]()` has been called by `super.reify(...)`:
368413
// https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/reify.js#L141

0 commit comments

Comments
 (0)