fix: validate readPackage dependency maps (#11230)

This commit is contained in:
Dami Oyeniyi
2026-05-05 13:15:21 +01:00
committed by Zoltan Kochan
parent 5f34a8d028
commit 8131d7c53e
6 changed files with 56 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/hooks.pnpmfile": patch
"pnpm": patch
---
Validate all `readPackage` dependency map fields, including `devDependencies`, and reject falsy non-object invalid values instead of silently accepting them.

View File

@@ -75,9 +75,9 @@ export async function requirePnpmfile (pnpmFilePath: string, prefix: string): Pr
if (!newPkg) {
throw new BadReadPackageHookError(pnpmFilePath, 'readPackage hook did not return a package manifest object.')
}
const dependencies = ['dependencies', 'optionalDependencies', 'peerDependencies']
const dependencies = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] as const
for (const dep of dependencies) {
if (newPkg[dep] && typeof newPkg[dep] !== 'object') {
if (newPkg[dep] != null && (typeof newPkg[dep] !== 'object' || Array.isArray(newPkg[dep]))) {
throw new BadReadPackageHookError(pnpmFilePath, `readPackage hook returned package manifest object's property '${dep}' must be an object.`)
}
}

View File

@@ -0,0 +1,8 @@
module.exports = {
hooks: { readPackage },
}
function readPackage (pkg) {
pkg.peerDependencies = []
return pkg
}

View File

@@ -0,0 +1,8 @@
module.exports = {
hooks: { readPackage },
}
function readPackage (pkg) {
pkg.optionalDependencies = false
return pkg
}

View File

@@ -0,0 +1,8 @@
module.exports = {
hooks: { readPackage },
}
function readPackage (pkg) {
pkg.devDependencies = '@oclif/errors'
return pkg
}

View File

@@ -32,6 +32,30 @@ test('readPackage hook run fails when returned dependencies is not an object', a
).rejects.toEqual(new BadReadPackageHookError(pnpmfilePath, 'readPackage hook returned package manifest object\'s property \'dependencies\' must be an object.'))
})
test('readPackage hook run fails when returned devDependencies is not an object', async () => {
const pnpmfilePath = path.join(import.meta.dirname, '__fixtures__/readPackageNoObjectDevDependencies.js')
const { pnpmfileModule: pnpmfile } = (await requirePnpmfile(pnpmfilePath, import.meta.dirname))!
return expect(
pnpmfile!.hooks!.readPackage!({}, defaultHookContext)
).rejects.toEqual(new BadReadPackageHookError(pnpmfilePath, 'readPackage hook returned package manifest object\'s property \'devDependencies\' must be an object.'))
})
test('readPackage hook run fails when returned optionalDependencies is a falsy non-object value', async () => {
const pnpmfilePath = path.join(import.meta.dirname, '__fixtures__/readPackageFalsyOptionalDependencies.js')
const { pnpmfileModule: pnpmfile } = (await requirePnpmfile(pnpmfilePath, import.meta.dirname))!
return expect(
pnpmfile!.hooks!.readPackage!({}, defaultHookContext)
).rejects.toEqual(new BadReadPackageHookError(pnpmfilePath, 'readPackage hook returned package manifest object\'s property \'optionalDependencies\' must be an object.'))
})
test('readPackage hook run fails when returned peerDependencies is an array', async () => {
const pnpmfilePath = path.join(import.meta.dirname, '__fixtures__/readPackageArrayPeerDependencies.js')
const { pnpmfileModule: pnpmfile } = (await requirePnpmfile(pnpmfilePath, import.meta.dirname))!
return expect(
pnpmfile!.hooks!.readPackage!({}, defaultHookContext)
).rejects.toEqual(new BadReadPackageHookError(pnpmfilePath, 'readPackage hook returned package manifest object\'s property \'peerDependencies\' must be an object.'))
})
test('filterLog hook combines with the global hook', async () => {
const globalPnpmfile = path.join(import.meta.dirname, '__fixtures__/globalFilterLog.js')
const pnpmfile = path.join(import.meta.dirname, '__fixtures__/filterLog.js')