@pnpm/deps.compliance.commands 1100.0.0 → 1101.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,27 @@
1
1
  import { docsUrl, TABLE_OPTIONS } from '@pnpm/cli.utils';
2
2
  import { types as allTypes } from '@pnpm/config.reader';
3
3
  import { WANTED_LOCKFILE } from '@pnpm/constants';
4
- import { audit } from '@pnpm/deps.compliance.audit';
4
+ import { audit, normalizeGhsaId } from '@pnpm/deps.compliance.audit';
5
5
  import { PnpmError } from '@pnpm/error';
6
6
  import { update } from '@pnpm/installing.commands';
7
7
  import { readEnvLockfile, readWantedLockfile } from '@pnpm/lockfile.fs';
8
8
  import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header';
9
9
  import { table } from '@zkochan/table';
10
10
  import chalk, {} from 'chalk';
11
- import { difference, pick, pickBy } from 'ramda';
11
+ import { pick, pickBy } from 'ramda';
12
12
  import { renderHelp } from 'render-help';
13
13
  import { fix } from './fix.js';
14
14
  import { fixWithUpdate } from './fixWithUpdate.js';
15
15
  import { ignore } from './ignore.js';
16
16
  const AUDIT_LEVEL_NUMBER = {
17
- low: 0,
18
- moderate: 1,
19
- high: 2,
20
- critical: 3,
17
+ info: 0,
18
+ low: 1,
19
+ moderate: 2,
20
+ high: 3,
21
+ critical: 4,
21
22
  };
22
23
  const AUDIT_COLOR = {
24
+ info: chalk.dim,
23
25
  low: chalk.bold,
24
26
  moderate: chalk.bold.yellow,
25
27
  high: chalk.bold.red,
@@ -46,7 +48,7 @@ export function rcOptionsTypes() {
46
48
  'production',
47
49
  'registry',
48
50
  ], allTypes),
49
- 'audit-level': ['low', 'moderate', 'high', 'critical'],
51
+ 'audit-level': ['info', 'low', 'moderate', 'high', 'critical'],
50
52
  // For fix, use String instead of a list of allowed string values.
51
53
  // Otherwise, an unexpected value will get coerced to true because of the Boolean type.
52
54
  fix: [String, Boolean],
@@ -86,7 +88,7 @@ export function help() {
86
88
  name: '--json',
87
89
  },
88
90
  {
89
- description: 'Only print advisories with severity greater than or equal to one of the following: low|moderate|high|critical. Default: low',
91
+ description: 'Only print advisories with severity greater than or equal to one of the following: info|low|moderate|high|critical. Default: low',
90
92
  name: '--audit-level <severity>',
91
93
  },
92
94
  {
@@ -108,11 +110,11 @@ export function help() {
108
110
  name: '--ignore-registry-errors',
109
111
  },
110
112
  {
111
- description: 'Ignore a vulnerability by CVE',
113
+ description: 'Ignore a vulnerability by its GitHub advisory ID (e.g. GHSA-xxxx-xxxx-xxxx)',
112
114
  name: '--ignore <vulnerability>',
113
115
  },
114
116
  {
115
- description: 'Ignore all CVEs with no resolution',
117
+ description: 'Ignore all vulnerabilities for which no fix exists',
116
118
  name: '--ignore-unfixable',
117
119
  },
118
120
  ],
@@ -153,7 +155,6 @@ export async function handler(opts) {
153
155
  },
154
156
  envLockfile,
155
157
  include,
156
- lockfileDir,
157
158
  registry: opts.registries.default,
158
159
  retry: {
159
160
  factor: opts.fetchRetryFactor,
@@ -162,7 +163,6 @@ export async function handler(opts) {
162
163
  retries: opts.fetchRetries,
163
164
  },
164
165
  timeout: opts.fetchTimeout,
165
- virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
166
166
  });
167
167
  }
168
168
  catch (err) { // eslint-disable-line
@@ -244,6 +244,7 @@ ${newIgnores.join('\n')}`,
244
244
  }
245
245
  const vulnerabilities = auditReport.metadata.vulnerabilities;
246
246
  const ignoredVulnerabilities = {
247
+ info: 0,
247
248
  low: 0,
248
249
  moderate: 0,
249
250
  high: 0,
@@ -252,19 +253,12 @@ ${newIgnores.join('\n')}`,
252
253
  const totalVulnerabilityCount = Object.values(vulnerabilities)
253
254
  .reduce((sum, vulnerabilitiesCount) => sum + vulnerabilitiesCount, 0);
254
255
  const ignoreGhsas = opts.auditConfig?.ignoreGhsas;
255
- if (ignoreGhsas) {
256
+ if (ignoreGhsas?.length) {
257
+ // Compare GHSA ids after normalizing so stored entries with varying
258
+ // casing still match the canonical form on the advisory.
259
+ const ignoreSet = new Set(ignoreGhsas.map(normalizeGhsaId));
256
260
  auditReport.advisories = pickBy(({ github_advisory_id: githubAdvisoryId, severity }) => {
257
- if (!ignoreGhsas.includes(githubAdvisoryId)) {
258
- return true;
259
- }
260
- ignoredVulnerabilities[severity] += 1;
261
- return false;
262
- }, auditReport.advisories);
263
- }
264
- const ignoreCves = opts.auditConfig?.ignoreCves;
265
- if (ignoreCves) {
266
- auditReport.advisories = pickBy(({ cves, severity }) => {
267
- if (cves.length === 0 || difference(cves, ignoreCves).length > 0) {
261
+ if (!ignoreSet.has(normalizeGhsaId(githubAdvisoryId))) {
268
262
  return true;
269
263
  }
270
264
  ignoredVulnerabilities[severity] += 1;
@@ -289,7 +283,7 @@ ${newIgnores.join('\n')}`,
289
283
  [AUDIT_COLOR[advisory.severity](advisory.severity), chalk.bold(advisory.title)],
290
284
  ['Package', advisory.module_name],
291
285
  ['Vulnerable versions', advisory.vulnerable_versions],
292
- ['Patched versions', advisory.patched_versions],
286
+ ['Patched versions', advisory.patched_versions ?? '(unknown)'],
293
287
  [
294
288
  'Paths',
295
289
  (paths.length > MAX_PATHS_COUNT
@@ -1,4 +1,4 @@
1
- import type { AuditAdvisory, AuditReport } from '@pnpm/deps.compliance.audit';
1
+ import { type AuditAdvisory, type AuditReport } from '@pnpm/deps.compliance.audit';
2
2
  import type { AuditOptions } from './audit.js';
3
3
  export interface FixResult {
4
4
  vulnOverrides: Record<string, string>;
package/lib/audit/fix.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { writeSettings } from '@pnpm/config.writer';
2
- import { difference } from 'ramda';
2
+ import { normalizeGhsaId } from '@pnpm/deps.compliance.audit';
3
3
  import semver from 'semver';
4
4
  export async function fix(auditReport, opts) {
5
- const fixableAdvisories = getFixableAdvisories(Object.values(auditReport.advisories), opts.auditConfig?.ignoreCves);
5
+ const fixableAdvisories = getFixableAdvisories(Object.values(auditReport.advisories), opts.auditConfig?.ignoreGhsas);
6
6
  const vulnOverrides = createOverrides(fixableAdvisories);
7
7
  if (Object.values(vulnOverrides).length === 0)
8
8
  return { vulnOverrides, addedAgeExcludes: [] };
@@ -16,27 +16,33 @@ export async function fix(auditReport, opts) {
16
16
  });
17
17
  return { vulnOverrides, addedAgeExcludes };
18
18
  }
19
- function getFixableAdvisories(advisories, ignoreCves) {
20
- if (ignoreCves) {
21
- advisories = advisories.filter(({ cves }) => difference(cves, ignoreCves).length > 0);
19
+ function getFixableAdvisories(advisories, ignoreGhsas) {
20
+ if (ignoreGhsas) {
21
+ // Normalize on both sides so ignore entries match regardless of casing.
22
+ const ignored = new Set(ignoreGhsas.map(normalizeGhsaId));
23
+ advisories = advisories.filter(({ github_advisory_id: ghsaId }) => !ghsaId || !ignored.has(normalizeGhsaId(ghsaId)));
22
24
  }
23
- return advisories
24
- .filter(({ vulnerable_versions: vulnerableVersions, patched_versions: patchedVersions }) => vulnerableVersions !== '>=0.0.0' && patchedVersions !== '<0.0.0');
25
+ // Only advisories with a known patched range can produce an override.
26
+ // patched_versions is undefined when the range couldn't be inferred from
27
+ // vulnerable_versions — no override is possible in that case.
28
+ return advisories.filter(({ patched_versions: patchedVersions }) => patchedVersions != null);
25
29
  }
26
30
  function createOverrides(advisories) {
27
- return Object.fromEntries(advisories.map((advisory) => [
28
- `${advisory.module_name}@${advisory.vulnerable_versions}`,
29
- advisory.patched_versions,
30
- ]));
31
+ const entries = [];
32
+ for (const advisory of advisories) {
33
+ if (!advisory.patched_versions)
34
+ continue;
35
+ entries.push([`${advisory.module_name}@${advisory.vulnerable_versions}`, advisory.patched_versions]);
36
+ }
37
+ return Object.fromEntries(entries);
31
38
  }
32
39
  export function createMinimumReleaseAgeExcludes(advisories) {
33
40
  const excludes = new Set();
34
41
  for (const advisory of advisories) {
35
- if (advisory.patched_versions === '<0.0.0')
36
- continue;
37
- if (advisory.vulnerable_versions === '>=0.0.0' || advisory.vulnerable_versions === '*')
42
+ const patchedVersions = advisory.patched_versions;
43
+ if (!patchedVersions)
38
44
  continue;
39
- const minVersion = semver.minVersion(advisory.patched_versions);
45
+ const minVersion = semver.minVersion(patchedVersions);
40
46
  if (minVersion) {
41
47
  excludes.add(`${advisory.module_name}@${minVersion.version}`);
42
48
  }
@@ -1,4 +1,4 @@
1
- import type { AuditReport } from '@pnpm/deps.compliance.audit';
1
+ import { type AuditReport } from '@pnpm/deps.compliance.audit';
2
2
  import type { AuditConfig, ProjectManifest } from '@pnpm/types';
3
3
  export interface IgnoreVulnerabilitiesOptions {
4
4
  dir: string;
@@ -1,31 +1,44 @@
1
1
  import { writeSettings } from '@pnpm/config.writer';
2
+ import { normalizeGhsaId } from '@pnpm/deps.compliance.audit';
3
+ import { PnpmError } from '@pnpm/error';
2
4
  import { difference } from 'ramda';
3
5
  export async function ignore(opts) {
4
- const currentCves = opts?.auditConfig?.ignoreCves ?? [];
5
- const currentUniqueCves = new Set(currentCves);
6
- const advisoryWthNoResolutions = filterAdvisoriesWithNoResolutions(Object.values(opts.auditReport.advisories));
6
+ // GHSA IDs are canonically uppercase; normalize on read/write so a stored
7
+ // "ghsa-..." or uppercase user input both match the derived id at filter
8
+ // time.
9
+ const currentGhsas = (opts?.auditConfig?.ignoreGhsas ?? []).map(normalizeGhsaId);
10
+ const currentUniqueGhsas = new Set(currentGhsas);
11
+ const advisoriesWithNoResolutions = filterAdvisoriesWithNoResolutions(Object.values(opts.auditReport.advisories));
7
12
  if (opts.ignoreUnfixable) {
8
- Object.values(advisoryWthNoResolutions).forEach((advisory) => {
9
- advisory.cves.forEach((cve) => currentUniqueCves.add(cve));
10
- });
13
+ for (const advisory of advisoriesWithNoResolutions) {
14
+ if (!advisory.github_advisory_id) {
15
+ throw new PnpmError('AUDIT_MISSING_GHSA', `Cannot ignore advisory ${advisory.id} (${advisory.module_name}): the registry did not provide a GHSA id or a resolvable url.`);
16
+ }
17
+ currentUniqueGhsas.add(normalizeGhsaId(advisory.github_advisory_id));
18
+ }
11
19
  }
12
- else {
13
- opts.ignore?.forEach((cve) => currentUniqueCves.add(cve));
20
+ else if (opts.ignore) {
21
+ for (const ghsa of opts.ignore) {
22
+ currentUniqueGhsas.add(normalizeGhsaId(ghsa));
23
+ }
14
24
  }
15
- const newIgnoreCves = currentUniqueCves.size > 0 ? Array.from(currentUniqueCves) : undefined;
16
- const diffCve = difference(newIgnoreCves ?? [], currentCves);
25
+ const newIgnoreGhsas = currentUniqueGhsas.size > 0 ? Array.from(currentUniqueGhsas) : undefined;
26
+ const diffGhsas = difference(newIgnoreGhsas ?? [], currentGhsas);
17
27
  await writeSettings({
18
28
  ...opts,
19
29
  updatedSettings: {
20
30
  auditConfig: {
21
31
  ...opts.auditConfig,
22
- ignoreCves: newIgnoreCves,
32
+ ignoreGhsas: newIgnoreGhsas,
23
33
  },
24
34
  },
25
35
  });
26
- return [...diffCve];
36
+ return [...diffGhsas];
27
37
  }
38
+ // Advisories for which no override can be produced — patched_versions is
39
+ // undefined when pnpm couldn't infer a patched range from vulnerable_versions.
40
+ // That is the only "no fix available" signal the bulk endpoint provides.
28
41
  function filterAdvisoriesWithNoResolutions(advisories) {
29
- return advisories.filter(({ patched_versions: patchedVersions }) => patchedVersions === '<0.0.0');
42
+ return advisories.filter(({ patched_versions: patchedVersions }) => patchedVersions == null);
30
43
  }
31
44
  //# sourceMappingURL=ignore.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.compliance.commands",
3
- "version": "1100.0.0",
3
+ "version": "1101.0.1",
4
4
  "description": "pnpm commands for audit, licenses, and sbom",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -33,26 +33,26 @@
33
33
  "ramda": "npm:@pnpm/ramda@0.28.1",
34
34
  "render-help": "^2.0.0",
35
35
  "semver": "^7.7.2",
36
- "@pnpm/cli.command": "1100.0.0",
37
36
  "@pnpm/cli.common-cli-options-help": "1100.0.0",
38
- "@pnpm/cli.meta": "1100.0.0",
39
- "@pnpm/config.writer": "1100.0.0",
37
+ "@pnpm/cli.utils": "1101.0.0",
38
+ "@pnpm/cli.command": "1100.0.0",
39
+ "@pnpm/cli.meta": "1100.0.1",
40
+ "@pnpm/config.reader": "1101.0.0",
40
41
  "@pnpm/constants": "1100.0.0",
41
- "@pnpm/deps.compliance.audit": "1100.0.0",
42
- "@pnpm/deps.compliance.license-scanner": "1100.0.0",
43
- "@pnpm/config.reader": "1100.0.0",
44
- "@pnpm/deps.compliance.sbom": "1100.0.0",
42
+ "@pnpm/config.writer": "1100.0.1",
43
+ "@pnpm/deps.compliance.license-scanner": "1100.0.2",
45
44
  "@pnpm/error": "1100.0.0",
46
- "@pnpm/installing.commands": "1100.0.0",
47
- "@pnpm/lockfile.fs": "1100.0.0",
48
- "@pnpm/lockfile.types": "1100.0.0",
49
- "@pnpm/lockfile.utils": "1100.0.0",
50
- "@pnpm/lockfile.walker": "1100.0.0",
51
- "@pnpm/cli.utils": "1100.0.0",
45
+ "@pnpm/lockfile.fs": "1100.0.1",
46
+ "@pnpm/deps.compliance.sbom": "1100.0.2",
47
+ "@pnpm/installing.commands": "1100.1.0",
48
+ "@pnpm/deps.compliance.audit": "1101.0.0",
49
+ "@pnpm/lockfile.utils": "1100.0.1",
50
+ "@pnpm/network.auth-header": "1100.0.1",
51
+ "@pnpm/lockfile.types": "1100.0.1",
52
52
  "@pnpm/store.path": "1100.0.0",
53
- "@pnpm/network.auth-header": "1100.0.0",
54
- "@pnpm/types": "1100.0.0",
55
- "@pnpm/workspace.project-manifest-reader": "1100.0.0"
53
+ "@pnpm/types": "1101.0.0",
54
+ "@pnpm/workspace.project-manifest-reader": "1100.0.2",
55
+ "@pnpm/lockfile.walker": "1100.0.1"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@pnpm/registry-mock": "6.0.0",
@@ -63,13 +63,13 @@
63
63
  "nock": "13.3.4",
64
64
  "read-yaml-file": "^3.0.0",
65
65
  "tempy": "3.0.0",
66
- "@pnpm/deps.compliance.commands": "1100.0.0",
67
- "@pnpm/prepare": "1100.0.0",
68
- "@pnpm/pkg-manifest.reader": "1100.0.0",
66
+ "@pnpm/deps.compliance.commands": "1101.0.1",
67
+ "@pnpm/pkg-manifest.reader": "1100.0.1",
68
+ "@pnpm/prepare": "1100.0.1",
69
+ "@pnpm/testing.mock-agent": "1100.0.1",
69
70
  "@pnpm/testing.command-defaults": "1100.0.0",
70
- "@pnpm/testing.mock-agent": "1100.0.0",
71
- "@pnpm/workspace.projects-filter": "1100.0.0",
72
- "@pnpm/test-fixtures": "1100.0.0"
71
+ "@pnpm/test-fixtures": "1100.0.0",
72
+ "@pnpm/workspace.projects-filter": "1100.0.2"
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=22.13"