mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
fix: audit fix should update overrides in pnpm-workspace.yaml (#9371)
This commit is contained in:
7
.changeset/plenty-waves-play.md
Normal file
7
.changeset/plenty-waves-play.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-audit": patch
|
||||
"@pnpm/config": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
`pnpm audit --fix` should update the overrides in `pnpm-workspace.yaml`.
|
||||
@@ -28,7 +28,7 @@ export type OptionsFromRootManifest = {
|
||||
patchedDependencies?: Record<string, string>
|
||||
peerDependencyRules?: PeerDependencyRules
|
||||
supportedArchitectures?: SupportedArchitectures
|
||||
} & Pick<PnpmSettings, 'configDependencies'>
|
||||
} & Pick<PnpmSettings, 'configDependencies' | 'auditConfig'>
|
||||
|
||||
export function getOptionsFromRootManifest (manifestDir: string, manifest: ProjectManifest): OptionsFromRootManifest {
|
||||
const settings: OptionsFromRootManifest = getOptionsFromPnpmSettings(manifestDir, {
|
||||
@@ -36,6 +36,7 @@ export function getOptionsFromRootManifest (manifestDir: string, manifest: Proje
|
||||
'allowedDeprecatedVersions',
|
||||
'allowNonAppliedPatches',
|
||||
'allowUnusedPatches',
|
||||
'auditConfig',
|
||||
'configDependencies',
|
||||
'ignoredBuiltDependencies',
|
||||
'ignoredOptionalDependencies',
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@pnpm/audit": "workspace:*",
|
||||
"@pnpm/cli-utils": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/config.config-writer": "workspace:*",
|
||||
"@pnpm/constants": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/lockfile.fs": "workspace:*",
|
||||
@@ -55,6 +56,7 @@
|
||||
"@types/zkochan__table": "catalog:",
|
||||
"load-json-file": "catalog:",
|
||||
"nock": "catalog:",
|
||||
"read-yaml-file": "catalog:",
|
||||
"tempy": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -113,37 +113,41 @@ export function help (): string {
|
||||
})
|
||||
}
|
||||
|
||||
export async function handler (
|
||||
opts: Pick<UniversalOptions, 'dir'> & {
|
||||
auditLevel?: 'low' | 'moderate' | 'high' | 'critical'
|
||||
fix?: boolean
|
||||
ignoreRegistryErrors?: boolean
|
||||
json?: boolean
|
||||
lockfileDir?: string
|
||||
registries: Registries
|
||||
} & Pick<Config, 'ca'
|
||||
| 'cert'
|
||||
| 'httpProxy'
|
||||
| 'httpsProxy'
|
||||
| 'key'
|
||||
| 'localAddress'
|
||||
| 'maxSockets'
|
||||
| 'noProxy'
|
||||
| 'strictSsl'
|
||||
| 'fetchRetries'
|
||||
| 'fetchRetryMaxtimeout'
|
||||
| 'fetchRetryMintimeout'
|
||||
| 'fetchRetryFactor'
|
||||
| 'fetchTimeout'
|
||||
| 'production'
|
||||
| 'dev'
|
||||
| 'optional'
|
||||
| 'userConfig'
|
||||
| 'rawConfig'
|
||||
| 'rootProjectManifest'
|
||||
| 'virtualStoreDirMaxLength'
|
||||
>
|
||||
): Promise<{ exitCode: number, output: string }> {
|
||||
export type AuditOptions = Pick<UniversalOptions, 'dir'> & {
|
||||
auditLevel?: 'low' | 'moderate' | 'high' | 'critical'
|
||||
fix?: boolean
|
||||
ignoreRegistryErrors?: boolean
|
||||
json?: boolean
|
||||
lockfileDir?: string
|
||||
registries: Registries
|
||||
} & Pick<Config, 'auditConfig'
|
||||
| 'ca'
|
||||
| 'cert'
|
||||
| 'httpProxy'
|
||||
| 'httpsProxy'
|
||||
| 'key'
|
||||
| 'localAddress'
|
||||
| 'maxSockets'
|
||||
| 'noProxy'
|
||||
| 'strictSsl'
|
||||
| 'fetchRetries'
|
||||
| 'fetchRetryMaxtimeout'
|
||||
| 'fetchRetryMintimeout'
|
||||
| 'fetchRetryFactor'
|
||||
| 'fetchTimeout'
|
||||
| 'production'
|
||||
| 'dev'
|
||||
| 'overrides'
|
||||
| 'optional'
|
||||
| 'userConfig'
|
||||
| 'rawConfig'
|
||||
| 'rootProjectManifest'
|
||||
| 'rootProjectManifestDir'
|
||||
| 'virtualStoreDirMaxLength'
|
||||
| 'workspaceDir'
|
||||
>
|
||||
|
||||
export async function handler (opts: AuditOptions): Promise<{ exitCode: number, output: string }> {
|
||||
const lockfileDir = opts.lockfileDir ?? opts.dir
|
||||
const lockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: true })
|
||||
if (lockfile == null) {
|
||||
@@ -193,7 +197,7 @@ export async function handler (
|
||||
throw err
|
||||
}
|
||||
if (opts.fix) {
|
||||
const newOverrides = await fix(opts.dir, auditReport)
|
||||
const newOverrides = await fix(auditReport, opts)
|
||||
if (Object.values(newOverrides).length === 0) {
|
||||
return {
|
||||
exitCode: 0,
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { type AuditReport, type AuditAdvisory } from '@pnpm/audit'
|
||||
import { readProjectManifest } from '@pnpm/read-project-manifest'
|
||||
import { writeSettings } from '@pnpm/config.config-writer'
|
||||
import difference from 'ramda/src/difference'
|
||||
import { type AuditOptions } from './audit'
|
||||
|
||||
export async function fix (dir: string, auditReport: AuditReport): Promise<Record<string, string>> {
|
||||
const { manifest, writeProjectManifest } = await readProjectManifest(dir)
|
||||
const vulnOverrides = createOverrides(Object.values(auditReport.advisories), manifest.pnpm?.auditConfig?.ignoreCves)
|
||||
export async function fix (auditReport: AuditReport, opts: AuditOptions): Promise<Record<string, string>> {
|
||||
const vulnOverrides = createOverrides(Object.values(auditReport.advisories), opts.auditConfig?.ignoreCves)
|
||||
if (Object.values(vulnOverrides).length === 0) return vulnOverrides
|
||||
await writeProjectManifest({
|
||||
...manifest,
|
||||
pnpm: {
|
||||
...manifest.pnpm,
|
||||
await writeSettings({
|
||||
updatedSettings: {
|
||||
overrides: {
|
||||
...manifest.pnpm?.overrides,
|
||||
...opts.overrides,
|
||||
...vulnOverrides,
|
||||
},
|
||||
},
|
||||
rootProjectManifest: opts.rootProjectManifest,
|
||||
rootProjectManifestDir: opts.rootProjectManifestDir,
|
||||
workspaceDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
|
||||
})
|
||||
return vulnOverrides
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import path from 'path'
|
||||
import { fixtures } from '@pnpm/test-fixtures'
|
||||
import { type ProjectManifest } from '@pnpm/types'
|
||||
import { audit } from '@pnpm/plugin-commands-audit'
|
||||
import { readProjectManifest } from '@pnpm/read-project-manifest'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import { sync as readYamlFile } from 'read-yaml-file'
|
||||
import nock from 'nock'
|
||||
import * as responses from './utils/responses'
|
||||
|
||||
@@ -25,6 +23,7 @@ test('overrides are added for vulnerable dependencies', async () => {
|
||||
const { exitCode, output } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
dir: tmp,
|
||||
rootProjectManifestDir: tmp,
|
||||
fix: true,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
@@ -35,9 +34,9 @@ test('overrides are added for vulnerable dependencies', async () => {
|
||||
expect(exitCode).toBe(0)
|
||||
expect(output).toMatch(/Run "pnpm install"/)
|
||||
|
||||
const manifest = loadJsonFile.sync<ProjectManifest>(path.join(tmp, 'package.json'))
|
||||
expect(manifest.pnpm?.overrides?.['axios@<=0.18.0']).toBe('>=0.18.1')
|
||||
expect(manifest.pnpm?.overrides?.['sync-exec@>=0.0.0']).toBeFalsy()
|
||||
const manifest = readYamlFile<{ overrides?: Record<string, string> }>(path.join(tmp, 'pnpm-workspace.yaml'))
|
||||
expect(manifest.overrides?.['axios@<=0.18.0']).toBe('>=0.18.1')
|
||||
expect(manifest.overrides?.['sync-exec@>=0.0.0']).toBeFalsy()
|
||||
})
|
||||
|
||||
test('no overrides are added if no vulnerabilities are found', async () => {
|
||||
@@ -50,6 +49,7 @@ test('no overrides are added if no vulnerabilities are found', async () => {
|
||||
const { exitCode, output } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
dir: tmp,
|
||||
rootProjectManifestDir: tmp,
|
||||
fix: true,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
@@ -63,21 +63,6 @@ test('no overrides are added if no vulnerabilities are found', async () => {
|
||||
|
||||
test('CVEs found in the allow list are not added as overrides', async () => {
|
||||
const tmp = f.prepare('has-vulnerabilities')
|
||||
{
|
||||
const { manifest, writeProjectManifest } = await readProjectManifest(tmp)
|
||||
manifest.pnpm = {
|
||||
...manifest.pnpm,
|
||||
auditConfig: {
|
||||
ignoreCves: [
|
||||
'CVE-2019-10742',
|
||||
'CVE-2020-28168',
|
||||
'CVE-2021-3749',
|
||||
'CVE-2020-7598',
|
||||
],
|
||||
},
|
||||
}
|
||||
await writeProjectManifest(manifest)
|
||||
}
|
||||
|
||||
nock(registries.default)
|
||||
.post('/-/npm/v1/security/audits')
|
||||
@@ -85,7 +70,16 @@ test('CVEs found in the allow list are not added as overrides', async () => {
|
||||
|
||||
const { exitCode, output } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
auditConfig: {
|
||||
ignoreCves: [
|
||||
'CVE-2019-10742',
|
||||
'CVE-2020-28168',
|
||||
'CVE-2021-3749',
|
||||
'CVE-2020-7598',
|
||||
],
|
||||
},
|
||||
dir: tmp,
|
||||
rootProjectManifestDir: tmp,
|
||||
fix: true,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
@@ -95,9 +89,9 @@ test('CVEs found in the allow list are not added as overrides', async () => {
|
||||
expect(exitCode).toBe(0)
|
||||
expect(output).toMatch(/Run "pnpm install"/)
|
||||
|
||||
const manifest = loadJsonFile.sync<ProjectManifest>(path.join(tmp, 'package.json'))
|
||||
expect(manifest.pnpm?.overrides?.['axios@<=0.18.0']).toBeFalsy()
|
||||
expect(manifest.pnpm?.overrides?.['axios@<0.21.1']).toBeFalsy()
|
||||
expect(manifest.pnpm?.overrides?.['minimist@<0.2.1']).toBeFalsy()
|
||||
expect(manifest.pnpm?.overrides?.['url-parse@<1.5.6']).toBeTruthy()
|
||||
const manifest = readYamlFile<{ overrides?: Record<string, string> }>(path.join(tmp, 'pnpm-workspace.yaml'))
|
||||
expect(manifest.overrides?.['axios@<=0.18.0']).toBeFalsy()
|
||||
expect(manifest.overrides?.['axios@<0.21.1']).toBeFalsy()
|
||||
expect(manifest.overrides?.['minimist@<0.2.1']).toBeFalsy()
|
||||
expect(manifest.overrides?.['url-parse@<1.5.6']).toBeTruthy()
|
||||
})
|
||||
|
||||
@@ -67,11 +67,12 @@ export const DEFAULT_OPTS = {
|
||||
}
|
||||
|
||||
describe('plugin-commands-audit', () => {
|
||||
const hasVulnerabilitiesDir = f.find('has-vulnerabilities')
|
||||
beforeAll(async () => {
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
frozenLockfile: true,
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
})
|
||||
})
|
||||
test('audit', async () => {
|
||||
@@ -80,7 +81,8 @@ describe('plugin-commands-audit', () => {
|
||||
.reply(200, responses.ALL_VULN_RESP)
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
registries,
|
||||
@@ -96,7 +98,8 @@ describe('plugin-commands-audit', () => {
|
||||
.reply(200, responses.DEV_VULN_ONLY_RESP)
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
dev: true,
|
||||
production: false,
|
||||
userConfig: {},
|
||||
@@ -116,7 +119,8 @@ describe('plugin-commands-audit', () => {
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
registries,
|
||||
@@ -133,7 +137,8 @@ describe('plugin-commands-audit', () => {
|
||||
.reply(200, responses.NO_VULN_RESP)
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
dir: f.find('has-outdated-deps'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
registries,
|
||||
@@ -150,7 +155,8 @@ describe('plugin-commands-audit', () => {
|
||||
.reply(200, responses.ALL_VULN_RESP)
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
json: true,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
@@ -170,7 +176,8 @@ describe('plugin-commands-audit', () => {
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
auditLevel: 'high',
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
dev: true,
|
||||
@@ -188,7 +195,8 @@ describe('plugin-commands-audit', () => {
|
||||
.post('/-/npm/v1/security/audits')
|
||||
.reply(500, { message: 'Something bad happened' })
|
||||
const { output, exitCode } = await audit.handler({
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
dev: true,
|
||||
fetchRetries: 0,
|
||||
ignoreRegistryErrors: true,
|
||||
@@ -211,7 +219,8 @@ describe('plugin-commands-audit', () => {
|
||||
.reply(200, responses.NO_VULN_RESP)
|
||||
|
||||
const { output, exitCode } = await audit.handler({
|
||||
dir: f.find('has-outdated-deps'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
userConfig: {},
|
||||
rawConfig: {
|
||||
registry: registries.default,
|
||||
@@ -231,7 +240,8 @@ describe('plugin-commands-audit', () => {
|
||||
.reply(404, {})
|
||||
|
||||
await expect(audit.handler({
|
||||
dir: f.find('has-vulnerabilities'),
|
||||
dir: hasVulnerabilitiesDir,
|
||||
rootProjectManifestDir: hasVulnerabilitiesDir,
|
||||
dev: true,
|
||||
fetchRetries: 0,
|
||||
ignoreRegistryErrors: false,
|
||||
@@ -253,6 +263,7 @@ describe('plugin-commands-audit', () => {
|
||||
const { exitCode, output } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
dir: tmp,
|
||||
rootProjectManifestDir: tmp,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
registries,
|
||||
@@ -285,6 +296,7 @@ describe('plugin-commands-audit', () => {
|
||||
const { exitCode, output } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
dir: tmp,
|
||||
rootProjectManifestDir: tmp,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
registries,
|
||||
@@ -317,6 +329,7 @@ describe('plugin-commands-audit', () => {
|
||||
const { exitCode, output } = await audit.handler({
|
||||
auditLevel: 'moderate',
|
||||
dir: tmp,
|
||||
rootProjectManifestDir: tmp,
|
||||
json: true,
|
||||
userConfig: {},
|
||||
rawConfig,
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
{
|
||||
"path": "../../config/config"
|
||||
},
|
||||
{
|
||||
"path": "../../config/config-writer"
|
||||
},
|
||||
{
|
||||
"path": "../../network/auth-header"
|
||||
},
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -3518,6 +3518,9 @@ importers:
|
||||
'@pnpm/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config
|
||||
'@pnpm/config.config-writer':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config-writer
|
||||
'@pnpm/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/constants
|
||||
@@ -3573,6 +3576,9 @@ importers:
|
||||
nock:
|
||||
specifier: 'catalog:'
|
||||
version: 13.3.4
|
||||
read-yaml-file:
|
||||
specifier: 'catalog:'
|
||||
version: 2.1.0
|
||||
tempy:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.1
|
||||
|
||||
Reference in New Issue
Block a user