Skip to content

Commit a35d528

Browse files
committed
fix(arborist): sanitize packageName in path construction for linked strategy
Node.packageName returns raw package.json name without validation (syncNormalize doesn't include fixName). This allows path traversal via malicious package names from file: deps or private registries. Changes: - Add packageName getter to IsolatedNode deriving safe names via nameFromFolder(path) - Wrap node.packageName through nameFromFolder in #assignCommonProperties so proxy objects get path-safe names - Fix hasShrinkwrap branch to use sanitized result.packageName instead of raw node.packageName in mkdirSync path construction
1 parent 21ea382 commit a35d528

File tree

2 files changed

+8
-2
lines changed

2 files changed

+8
-2
lines changed

workspaces/arborist/lib/arborist/isolated-reifier.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { join } = require('node:path')
44
const { depth } = require('treeverse')
55
const crypto = require('node:crypto')
66
const { IsolatedNode, IsolatedLink } = require('../isolated-classes.js')
7+
const nameFromFolder = require('@npmcli/name-from-folder')
78

89
// generate short hash key based on the dependency tree starting at this node
910
const getKey = (startNode) => {
@@ -149,7 +150,7 @@ module.exports = cls => class IsolatedReifier extends cls {
149150
node.root.path,
150151
'node_modules',
151152
'.store',
152-
`${node.packageName}@${node.version}`
153+
`${result.packageName}@${node.version}`
153154
)
154155
mkdirSync(dir, { recursive: true })
155156
// TODO this approach feels wrong and shouldn't be necessary for shrinkwraps
@@ -191,7 +192,7 @@ module.exports = cls => class IsolatedReifier extends cls {
191192
result.id = this.counter++
192193
/* istanbul ignore next - packageName is always set for real packages */
193194
result.name = result.isWorkspace ? (node.packageName || node.name) : node.name
194-
result.packageName = node.packageName || node.name
195+
result.packageName = nameFromFolder(node.packageName || node.path)
195196
result.package = { ...node.package }
196197
result.package.bundleDependencies = undefined
197198

workspaces/arborist/lib/isolated-classes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Alternate versions of different classes that we use for isolated mode
22
const CaseInsensitiveMap = require('./case-insensitive-map.js')
33
const { resolve } = require('node:path')
4+
const nameFromFolder = require('@npmcli/name-from-folder')
45

56
// fake lib/inventory.js
67
class IsolatedInventory extends Map {
@@ -104,6 +105,10 @@ class IsolatedNode {
104105
return !!(hasInstallScript || install || preinstall || postinstall)
105106
}
106107

108+
get packageName () {
109+
return nameFromFolder(this.path)
110+
}
111+
107112
get version () {
108113
return this.package.version
109114
}

0 commit comments

Comments
 (0)