Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions patches/@npmcli+arborist#9.1.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Index: /@npmcli/arborist/lib/node.js
===================================================================
--- /@npmcli/arborist/lib/node.js
+++ /@npmcli/arborist/lib/node.js
@@ -1156,9 +1156,15 @@
}

// if they're links, they match if the targets match
if (this.isLink) {
- return node.isLink && this.target.matches(node.target)
+ if (node.isLink) {
+ if (this.target && node.target) {
+ return this.target.matches(node.target)
+ }
+ } else {
+ return false
+ }
}

// if they're two project root nodes, they're different if the paths differ
if (this.isProjectRoot && node.isProjectRoot) {
14 changes: 8 additions & 6 deletions src/commands/fix/npm-fix.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ import constants from '../../constants.mts'
import {
Arborist,
SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES,
SafeArborist,
} from '../../shadow/npm/arborist/lib/arborist/index.mts'
} from '../../shadow/npm/arborist/index.mts'
import {
findBestPatchVersion,
findPackageNode,
Expand All @@ -51,7 +50,10 @@ import { getCveInfoFromAlertsMap } from '../../utils/socket-package-alert.mts'
import { idToPurl } from '../../utils/spec.mts'

import type { NormalizedFixOptions } from './types.mts'
import type { SafeNode } from '../../shadow/npm/arborist/lib/node.mts'
import type {
ArboristInstance,
NodeClass,
} from '../../shadow/npm/arborist/types.mts'
import type { EnvDetails } from '../../utils/package-environment.mts'
import type { PackageJson } from '@socketsecurity/registry/lib/packages'

Expand All @@ -62,9 +64,9 @@ type InstallOptions = {
}

async function install(
arb: SafeArborist,
arb: ArboristInstance,
options: InstallOptions,
): Promise<SafeNode | null> {
): Promise<NodeClass | null> {
const { cwd = process.cwd() } = {
__proto__: null,
...options,
Expand Down Expand Up @@ -103,7 +105,7 @@ export async function npmFix(

const { pkgPath: rootPath } = pkgEnvDetails

const arb = new SafeArborist({
const arb = new Arborist({
path: rootPath,
...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES,
})
Expand Down
14 changes: 7 additions & 7 deletions src/commands/fix/pnpm-fix.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import {
import { getAlertsMapOptions } from './shared.mts'
import constants from '../../constants.mts'
import {
Arborist,
SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES,
SafeArborist,
} from '../../shadow/npm/arborist/lib/arborist/index.mts'
} from '../../shadow/npm/arborist/index.mts'
import {
findBestPatchVersion,
findPackageNode,
Expand All @@ -59,20 +59,20 @@ import { getCveInfoFromAlertsMap } from '../../utils/socket-package-alert.mts'
import { idToPurl } from '../../utils/spec.mts'

import type { NormalizedFixOptions } from './types.mts'
import type { SafeNode } from '../../shadow/npm/arborist/lib/node.mts'
import type { NodeClass } from '../../shadow/npm/arborist/types.mts'
import type { StringKeyValueObject } from '../../types.mts'
import type { EnvDetails } from '../../utils/package-environment.mts'
import type { PackageJson } from '@socketsecurity/registry/lib/packages'
import type { Spinner } from '@socketsecurity/registry/lib/spinner'

const { DRY_RUN_NOT_SAVING, NPM, OVERRIDES, PNPM } = constants

async function getActualTree(cwd: string = process.cwd()): Promise<SafeNode> {
async function getActualTree(cwd: string = process.cwd()): Promise<NodeClass> {
// @npmcli/arborist DOES have partial support for pnpm structured node_modules
// folders. However, support is iffy resulting in unhappy path errors and hangs.
// So, to avoid the unhappy path, we restrict our usage to --dry-run loading
// of the node_modules folder.
const arb = new SafeArborist({
const arb = new Arborist({
path: cwd,
...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES,
})
Expand All @@ -88,7 +88,7 @@ type InstallOptions = {
async function install(
pkgEnvDetails: EnvDetails,
options: InstallOptions,
): Promise<SafeNode | null> {
): Promise<NodeClass | null> {
const { args, cwd, spinner } = {
__proto__: null,
...options,
Expand Down Expand Up @@ -135,7 +135,7 @@ export async function pnpmFix(

spinner?.start()

let actualTree: SafeNode | undefined
let actualTree: NodeClass | undefined
const lockfilePath = path.join(rootPath, 'pnpm-lock.yaml')
let lockfileContent = await readPnpmLockfile(lockfilePath)

Expand Down
16 changes: 10 additions & 6 deletions src/commands/optimize/add-overrides.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from 'node:path'

import npa from 'npm-package-arg'
import semver from 'semver'

import { getManifestData } from '@socketsecurity/registry'
Expand All @@ -19,9 +18,12 @@ import { updateManifestByAgent } from './update-manifest-by-agent.mts'
import constants from '../../constants.mts'
import { cmdPrefixMessage } from '../../utils/cmd.mts'
import { globWorkspace } from '../../utils/glob.mts'
import { npa } from '../../utils/npm-package-arg.mts'
import { getMajor } from '../../utils/semver.mts'

import type { GetOverridesResult } from './get-overrides-by-agent.mts'
import type { AgentLockIncludesFn } from './lockfile-includes-by-agent.mts'
import type { AliasResult } from '../../utils/npm-package-arg.mts'
import type { EnvDetails } from '../../utils/package-environment.mts'
import type { Logger } from '@socketsecurity/registry/lib/logger'
import type { PackageJson } from '@socketsecurity/registry/lib/packages'
Expand Down Expand Up @@ -120,7 +122,7 @@ export async function addOverrides(
// Chunk package names to process them in parallel 3 at a time.
await pEach(manifestEntries, 3, async ({ 1: data }) => {
const { name: sockRegPkgName, package: origPkgName, version } = data
const major = semver.major(version)
const major = getMajor(version)!
const sockOverridePrefix = `${NPM}:${sockRegPkgName}@`
const sockOverrideSpec = `${sockOverridePrefix}${pin ? version : `^${major}`}`
for (const { 1: depObj } of depEntries) {
Expand All @@ -144,7 +146,8 @@ export async function addOverrides(
thisSpec.startsWith(sockOverridePrefix) &&
// Check the validity of the spec by passing it through npa and
// seeing if it will coerce to a version.
semver.coerce(npa(thisSpec).rawSpec)?.version
semver.coerce((npa(thisSpec) as AliasResult).subSpec.rawSpec)
?.version
)
) {
thisSpec = sockOverrideSpec
Expand Down Expand Up @@ -198,19 +201,20 @@ export async function addOverrides(
if (thisSpec.startsWith(sockOverridePrefix)) {
if (
pin &&
semver.major(
getMajor(
// Check the validity of the spec by passing it through npa
// and seeing if it will coerce to a version. semver.coerce
// will strip leading v's, carets (^), comparators (<,<=,>,>=,=),
// and tildes (~). If not coerced to a valid version then
// default to the manifest entry version.
semver.coerce(npa(thisSpec).rawSpec)?.version ?? version,
semver.coerce((npa(thisSpec) as AliasResult).subSpec.rawSpec)
?.version ?? version,
) !== major
) {
const otherVersion = (await fetchPackageManifest(thisSpec))
?.version
if (otherVersion && otherVersion !== version) {
newSpec = `${sockOverridePrefix}${pin ? otherVersion : `^${semver.major(otherVersion)}`}`
newSpec = `${sockOverridePrefix}${pin ? otherVersion : `^${getMajor(otherVersion)!}`}`
}
}
} else {
Expand Down
65 changes: 34 additions & 31 deletions src/shadow/npm/arborist-helpers.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import { PackageURL } from '@socketregistry/packageurl-js'
import { getManifestData } from '@socketsecurity/registry'
import { hasOwn } from '@socketsecurity/registry/lib/objects'
import { fetchPackagePackument } from '@socketsecurity/registry/lib/packages'
import { isNonEmptyString } from '@socketsecurity/registry/lib/strings'

import constants from '../../constants.mts'
import { applyRange, getMajor } from '../../utils/semver.mts'
import { idToPurl } from '../../utils/spec.mts'
import { DiffAction } from './arborist/lib/arborist/types.mts'
import { Edge } from './arborist/lib/edge.mts'
import { Edge } from './arborist/index.mts'
import { DiffAction } from './arborist/types.mts'
import { getAlertsMapFromPurls } from '../../utils/alerts-map.mts'
import { type AliasResult, npa } from '../../utils/npm-package-arg.mts'
import { applyRange, getMajor, getMinVersion } from '../../utils/semver.mts'
import { idToPurl } from '../../utils/spec.mts'

import type {
ArboristInstance,
Diff,
EdgeClass,
LinkClass,
NodeClass,
} from './arborist/types.mts'
import type { RangeStyle } from '../../utils/semver.mts'
import type { SafeArborist } from './arborist/lib/arborist/index.mts'
import type { Diff } from './arborist/lib/arborist/types.mts'
import type { SafeEdge } from './arborist/lib/edge.mts'
import type { LinkClass, SafeNode } from './arborist/lib/node.mts'
import type {
AlertIncludeFilter,
AlertsByPkgId,
Expand All @@ -38,7 +41,7 @@ function getUrlOrigin(input: string): string {
}

export function findBestPatchVersion(
node: SafeNode,
node: NodeClass,
availableVersions: string[],
vulnerableVersionRange?: string,
_firstPatchedVersionIdentifier?: string | undefined,
Expand Down Expand Up @@ -69,12 +72,12 @@ export function findBestPatchVersion(
}

export function findPackageNode(
tree: SafeNode,
tree: NodeClass,
name: string,
version?: string | undefined,
): SafeNode | undefined {
const queue: Array<SafeNode | LinkClass> = [tree]
const visited = new Set<SafeNode>()
): NodeClass | undefined {
const queue: Array<NodeClass | LinkClass> = [tree]
const visited = new Set<NodeClass>()
let sentinel = 0
while (queue.length) {
if (sentinel++ === LOOP_SENTINEL) {
Expand Down Expand Up @@ -106,13 +109,13 @@ export function findPackageNode(
}

export function findPackageNodes(
tree: SafeNode,
tree: NodeClass,
name: string,
version?: string | undefined,
): SafeNode[] {
const matches: SafeNode[] = []
const queue: Array<SafeNode | LinkClass> = [tree]
const visited = new Set<SafeNode>()
): NodeClass[] {
const matches: NodeClass[] = []
const queue: Array<NodeClass | LinkClass> = [tree]
const visited = new Set<NodeClass>()
let sentinel = 0
while (queue.length) {
if (sentinel++ === LOOP_SENTINEL) {
Expand Down Expand Up @@ -151,7 +154,7 @@ export type GetAlertsMapFromArboristOptions = {
}

export async function getAlertsMapFromArborist(
arb: SafeArborist,
arb: ArboristInstance,
options_?: GetAlertsMapFromArboristOptions | undefined,
): Promise<AlertsByPkgId> {
const options = {
Expand Down Expand Up @@ -215,8 +218,8 @@ export type DiffQueryOptions = {
}

export type PackageDetail = {
node: SafeNode
existing?: SafeNode | undefined
node: NodeClass
existing?: NodeClass | undefined
}

export function getDetailsFromDiff(
Expand Down Expand Up @@ -251,7 +254,7 @@ export function getDetailsFromDiff(
// The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff
// action is 'ADD'.
const { actual: oldNode, ideal: pkgNode } = diff
let existing: SafeNode | undefined
let existing: NodeClass | undefined
let keep = false
if (action === DiffAction.change) {
if (pkgNode?.package.version !== oldNode?.package.version) {
Expand Down Expand Up @@ -303,13 +306,13 @@ export function getDetailsFromDiff(
return details
}

export function getTargetNode(nodeOrLink: SafeNode | LinkClass): SafeNode
export function getTargetNode<T>(nodeOrLink: T): SafeNode | null
export function getTargetNode(nodeOrLink: any): SafeNode | null {
export function getTargetNode(nodeOrLink: NodeClass | LinkClass): NodeClass
export function getTargetNode<T>(nodeOrLink: T): NodeClass | null
export function getTargetNode(nodeOrLink: any): NodeClass | null {
return nodeOrLink?.isLink ? nodeOrLink.target : (nodeOrLink ?? null)
}

export function isTopLevel(tree: SafeNode, node: SafeNode): boolean {
export function isTopLevel(tree: NodeClass, node: NodeClass): boolean {
return getTargetNode(tree.children.get(node.name)) === node
}

Expand All @@ -319,7 +322,7 @@ export type Packument = Exclude<
>

export function updateNode(
node: SafeNode,
node: NodeClass,
newVersion: string,
newVersionPackument: Packument['versions'][number],
): void {
Expand Down Expand Up @@ -372,16 +375,16 @@ export function updateNode(
name: newDepName,
spec: newDeps[newDepName],
type: 'prod',
}) as unknown as SafeEdge,
}) as unknown as EdgeClass,
)
}
}
}

export function updatePackageJsonFromNode(
editablePkgJson: EditablePackageJson,
tree: SafeNode,
node: SafeNode,
tree: NodeClass,
node: NodeClass,
newVersion: string,
rangeStyle?: RangeStyle | undefined,
): boolean {
Expand Down
40 changes: 33 additions & 7 deletions src/shadow/npm/arborist/index.mts
Original file line number Diff line number Diff line change
@@ -1,24 +1,50 @@
import { createRequire } from 'node:module'

// @ts-ignore
import UntypedEdge from '@npmcli/arborist/lib/edge.js'
// @ts-ignore
import UntypedNode from '@npmcli/arborist/lib/node.js'
// @ts-ignore
import UntypedOverrideSet from '@npmcli/arborist/lib/override-set.js'

import {
getArboristClassPath,
getArboristEdgeClassPath,
getArboristNodeClassPath,
getArboristOverrideSetClassPath,
} from '../paths.mts'
import { SafeArborist } from './lib/arborist/index.mts'
import { SafeEdge } from './lib/edge.mts'
import { SafeNode } from './lib/node.mts'
import { SafeOverrideSet } from './lib/override-set.mts'
import { Arborist, SafeArborist } from './lib/arborist/index.mts'

import type { EdgeClass, NodeClass, OverrideSetClass } from './types.mts'

const require = createRequire(import.meta.url)

export const SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = {
__proto__: null,
audit: false,
dryRun: true,
fund: false,
ignoreScripts: true,
progress: false,
save: false,
saveBundle: false,
silent: true,
}

export { Arborist, SafeArborist }

export const Edge: EdgeClass = UntypedEdge

export const Node: NodeClass = UntypedNode

export const OverrideSet: OverrideSetClass = UntypedOverrideSet

export function installSafeArborist() {
// Override '@npmcli/arborist' module exports with patched variants based on
// https://github.com/npm/cli/pull/8089.
const cache: { [key: string]: any } = require.cache
cache[getArboristClassPath()] = { exports: SafeArborist }
cache[getArboristEdgeClassPath()] = { exports: SafeEdge }
cache[getArboristNodeClassPath()] = { exports: SafeNode }
cache[getArboristOverrideSetClassPath()] = { exports: SafeOverrideSet }
cache[getArboristEdgeClassPath()] = { exports: Edge }
cache[getArboristNodeClassPath()] = { exports: Node }
cache[getArboristOverrideSetClassPath()] = { exports: OverrideSet }
}
Loading