Skip to content

Commit

Permalink
feat: allow audit --fix to skip installing overrides based on allowLi…
Browse files Browse the repository at this point in the history
…st array in the manifest (#5592)

Co-authored-by: Zoltan Kochan <z@kochan.io>
  • Loading branch information
CobyPear and zkochan committed Nov 8, 2022
1 parent 6710d9d commit 702e847
Show file tree
Hide file tree
Showing 7 changed files with 481 additions and 3 deletions.
22 changes: 22 additions & 0 deletions .changeset/thick-cows-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@pnpm/plugin-commands-audit": minor
"@pnpm/types": minor
"pnpm": minor
---

A new setting supported for ignoring vulnerabilities by their CVEs. The ignored CVEs may be listed in the `pnpm.auditConfig.ignoreCves` field of `package.json`. For instance:

```
{
"pnpm": {
"auditConfig": {
"ignoreCves": [
"CVE-2019-10742",
"CVE-2020-28168",
"CVE-2021-3749",
"CVE-2020-7598"
]
}
}
}
```
9 changes: 8 additions & 1 deletion packages/plugin-commands-audit/src/audit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Registries } from '@pnpm/types'
import { table } from '@zkochan/table'
import chalk from 'chalk'
import pick from 'ramda/src/pick'
import { difference } from 'ramda'
import renderHelp from 'render-help'
import { fix } from './fix'

Expand Down Expand Up @@ -126,6 +127,7 @@ export async function handler (
| 'optional'
| 'userConfig'
| 'rawConfig'
| 'rootProjectManifest'
>
) {
const lockfile = await readWantedLockfile(opts.lockfileDir ?? opts.dir, { ignoreIncompatible: true })
Expand Down Expand Up @@ -202,7 +204,12 @@ ${JSON.stringify(newOverrides, null, 2)}`,

let output = ''
const auditLevel = AUDIT_LEVEL_NUMBER[opts.auditLevel ?? 'low']
const advisories = Object.values(auditReport.advisories)
let advisories = Object.values(auditReport.advisories)
const ignoreCves = opts.rootProjectManifest?.pnpm?.auditConfig?.ignoreCves
if (ignoreCves) {
advisories = advisories.filter(({ cves }) => difference(cves, ignoreCves).length > 0)
}
advisories = advisories
.filter(({ severity }) => AUDIT_LEVEL_NUMBER[severity] >= auditLevel)
.sort((a1, a2) => AUDIT_LEVEL_NUMBER[a2.severity] - AUDIT_LEVEL_NUMBER[a1.severity])
for (const advisory of advisories) {
Expand Down
8 changes: 6 additions & 2 deletions packages/plugin-commands-audit/src/fix.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AuditReport, AuditAdvisory } from '@pnpm/audit'
import { readProjectManifest } from '@pnpm/read-project-manifest'
import { difference } from 'ramda'
import fromPairs from 'ramda/src/fromPairs'

export async function fix (dir: string, auditReport: AuditReport) {
const { manifest, writeProjectManifest } = await readProjectManifest(dir)
const vulnOverrides = createOverrides(Object.values(auditReport.advisories))
const vulnOverrides = createOverrides(Object.values(auditReport.advisories), manifest.pnpm?.auditConfig?.ignoreCves)
if (Object.values(vulnOverrides).length === 0) return vulnOverrides
await writeProjectManifest({
...manifest,
Expand All @@ -19,7 +20,10 @@ export async function fix (dir: string, auditReport: AuditReport) {
return vulnOverrides
}

function createOverrides (advisories: AuditAdvisory[]) {
function createOverrides (advisories: AuditAdvisory[], ignoreCves?: string[]) {
if (ignoreCves) {
advisories = advisories.filter(({ cves }) => difference(cves, ignoreCves).length > 0)
}
return fromPairs(
advisories
.filter(({ vulnerable_versions, patched_versions }) => vulnerable_versions !== '>=0.0.0' && patched_versions !== '<0.0.0') // eslint-disable-line
Expand Down
368 changes: 368 additions & 0 deletions packages/plugin-commands-audit/test/__snapshots__/index.ts.snap

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions packages/plugin-commands-audit/test/fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path'
import { fixtures } from '@pnpm/test-fixtures'
import { 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 nock from 'nock'
import * as responses from './utils/responses'
Expand Down Expand Up @@ -57,3 +58,43 @@ test('no overrides are added if no vulnerabilities are found', async () => {
expect(exitCode).toBe(0)
expect(output).toBe('No fixes were made')
})

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')
.reply(200, responses.ALL_VULN_RESP)

const { exitCode, output } = await audit.handler({
auditLevel: 'moderate',
dir: tmp,
fix: true,
userConfig: {},
rawConfig,
registries,
})
expect(exitCode).toBe(0)
expect(output).toMatch(/Run "pnpm install"/)

const manifest = await loadJsonFile<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()
})
33 changes: 33 additions & 0 deletions packages/plugin-commands-audit/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import path from 'path'
import { fixtures } from '@pnpm/test-fixtures'
import { audit } from '@pnpm/plugin-commands-audit'
import { AuditEndpointNotExistsError } from '@pnpm/audit'
import nock from 'nock'
import stripAnsi from 'strip-ansi'
import * as responses from './utils/responses'

const f = fixtures(path.join(__dirname, 'fixtures'))
const registries = {
default: 'https://registry.npmjs.org/',
}
Expand Down Expand Up @@ -171,3 +173,34 @@ test('audit endpoint does not exist', async () => {
registries,
})).rejects.toThrow(AuditEndpointNotExistsError)
})

test('audit: CVEs in ignoreCves do not show up', async () => {
const tmp = f.prepare('has-vulnerabilities')

nock(registries.default)
.post('/-/npm/v1/security/audits')
.reply(200, responses.ALL_VULN_RESP)

const { exitCode, output } = await audit.handler({
auditLevel: 'moderate',
dir: tmp,
userConfig: {},
rawConfig,
registries,
rootProjectManifest: {
pnpm: {
auditConfig: {
ignoreCves: [
'CVE-2019-10742',
'CVE-2020-28168',
'CVE-2021-3749',
'CVE-2020-7598',
],
},
},
},
})

expect(exitCode).toBe(1)
expect(stripAnsi(output)).toMatchSnapshot()
})
3 changes: 3 additions & 0 deletions packages/types/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ export type ProjectManifest = BaseManifest & {
updateConfig?: {
ignoreDependencies?: string[]
}
auditConfig?: {
ignoreCves?: string[]
}
}
private?: boolean
resolutions?: Record<string, string>
Expand Down

0 comments on commit 702e847

Please sign in to comment.