From 5f34a8d0280b0bfa9741bbff64a6e48a3dac9fe1 Mon Sep 17 00:00:00 2001 From: Dami Oyeniyi Date: Tue, 5 May 2026 00:06:33 +0100 Subject: [PATCH] fix: reject invalid overrides values (#11380) * fix: reject invalid overrides values * fix: improve overrides validation error messages --- .changeset/fix-overrides-value-validation.md | 6 ++++ .../reader/src/getOptionsFromRootManifest.ts | 18 +++++++++++ .../test/getOptionsFromRootManifest.test.ts | 31 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 .changeset/fix-overrides-value-validation.md diff --git a/.changeset/fix-overrides-value-validation.md b/.changeset/fix-overrides-value-validation.md new file mode 100644 index 0000000000..2d44a7e8c0 --- /dev/null +++ b/.changeset/fix-overrides-value-validation.md @@ -0,0 +1,6 @@ +--- +"@pnpm/config.reader": patch +"pnpm": patch +--- + +Throw a pnpm error when `overrides` has an invalid shape or contains a non-string value. diff --git a/config/reader/src/getOptionsFromRootManifest.ts b/config/reader/src/getOptionsFromRootManifest.ts index efdef766ab..ff3a45cbbf 100644 --- a/config/reader/src/getOptionsFromRootManifest.ts +++ b/config/reader/src/getOptionsFromRootManifest.ts @@ -28,6 +28,7 @@ export type OptionsFromRootManifest = { export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnpmSettings: PnpmSettings, manifest?: ProjectManifest): OptionsFromRootManifest { const settings: OptionsFromRootManifest = replaceEnvInSettings(pnpmSettings) if (settings.overrides) { + assertValidOverrides(settings.overrides) if (Object.keys(settings.overrides).length === 0) { delete settings.overrides } else if (manifest) { @@ -45,6 +46,23 @@ export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnp return settings } +function assertValidOverrides (overrides: unknown): asserts overrides is Record { + if (overrides == null || typeof overrides !== 'object' || Array.isArray(overrides)) { + throw new PnpmError('INVALID_OVERRIDES', `The overrides field should be an object, but got ${renderReceivedType(overrides)}`) + } + for (const [selector, spec] of Object.entries(overrides)) { + if (typeof spec !== 'string') { + throw new PnpmError('INVALID_OVERRIDES', `The value of overrides.${selector} should be a string, but got ${renderReceivedType(spec)}`) + } + } +} + +function renderReceivedType (value: unknown): string { + if (value === null) return 'null' + if (Array.isArray(value)) return 'array' + return typeof value +} + function replaceEnvInSettings (settings: PnpmSettings): PnpmSettings { const newSettings: PnpmSettings = {} for (const [key, value] of Object.entries(settings)) { diff --git a/config/reader/test/getOptionsFromRootManifest.test.ts b/config/reader/test/getOptionsFromRootManifest.test.ts index 43f63ff6ea..10cf161662 100644 --- a/config/reader/test/getOptionsFromRootManifest.test.ts +++ b/config/reader/test/getOptionsFromRootManifest.test.ts @@ -33,3 +33,34 @@ test('getOptionsFromPnpmSettings() converts allowBuilds', () => { }, }) }) + +test('getOptionsFromPnpmSettings() rejects non-string overrides values', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + overrides: { + foo: null, + } as unknown as Record, + })).toThrow(expect.objectContaining({ + code: 'ERR_PNPM_INVALID_OVERRIDES', + message: 'The value of overrides.foo should be a string, but got null', + })) +}) + +test('getOptionsFromPnpmSettings() rejects array overrides values', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + overrides: { + foo: [], + } as unknown as Record, + })).toThrow(expect.objectContaining({ + code: 'ERR_PNPM_INVALID_OVERRIDES', + message: 'The value of overrides.foo should be a string, but got array', + })) +}) + +test('getOptionsFromPnpmSettings() rejects non-object overrides values', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + overrides: [] as unknown as Record, + })).toThrow(expect.objectContaining({ + code: 'ERR_PNPM_INVALID_OVERRIDES', + message: 'The overrides field should be an object, but got array', + })) +})