Skip to content

Commit d93fcea

Browse files
authored
Updates the audit implementation to the bulk endpoint (#5501)
**What's the problem this PR addresses?** The current audit implementation uses the older `/audit/quick` endpoint, which has various problems. One particular is that its design requires to submit a nested payload, but since it doesn't make much sense in our case (because most of Yarn installs are flat), we flatten the package list. It causes problems when multiple packages with different versions can be found in the tree. Fixes #3861 Fixes #4117 Fixes #5408 Closes #5409 (Supercedes it) --- Edit by @merceyz Fixes #5450 Fixes #2507 Fixes #3778 Fixes #3945 Closes #5309 (Doesn't have a reproduction so I'm assuming it's the same as the others) --- **How did you fix it?** This change rewrites `yarn npm audit` to use the new endpoint. As part of the migration a couple of fields are reworked (`Via` is replaced by `Dependents`, the versions are now part of a tree item rather than concatenated, we don't get the "recommendation" anymore). The options remain the same for now. It's possible that some registries don't support the bulk endpoint. Given that it's fairly straightforward to implement, that it's been released for some time now, and that without it we would end up with an invalid `audit` implementation, I'd tend to let them deal with that. **Checklist** <!--- Don't worry if you miss something, chores are automatically tested. --> <!--- This checklist exists to help you remember doing the chores when you submit a PR. --> <!--- Put an `x` in all the boxes that apply. --> - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). <!-- See https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released for more details. --> <!-- Check with `yarn version check` and fix with `yarn version check -i` --> - [x] I have set the packages that need to be released for my changes to be effective. <!-- The "Testing chores" workflow validates that your PR follows our guidelines. --> <!-- If it doesn't pass, click on it to see details as to what your PR might be missing. --> - [x] I will check that all automated PR checks pass before the PR gets reviewed.
1 parent 4a0f70b commit d93fcea

File tree

62 files changed

+1667
-1569
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1667
-1569
lines changed

.pnp.cjs

Lines changed: 994 additions & 960 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
-69.4 KB
Binary file not shown.
70.5 KB
Binary file not shown.
26.9 KB
Binary file not shown.
-13.7 KB
Binary file not shown.

.yarn/versions/9161855d.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
releases:
2+
"@yarnpkg/builder": major
3+
"@yarnpkg/cli": major
4+
"@yarnpkg/core": major
5+
"@yarnpkg/doctor": major
6+
"@yarnpkg/plugin-constraints": major
7+
"@yarnpkg/plugin-dlx": major
8+
"@yarnpkg/plugin-essentials": major
9+
"@yarnpkg/plugin-git": major
10+
"@yarnpkg/plugin-init": major
11+
"@yarnpkg/plugin-interactive-tools": major
12+
"@yarnpkg/plugin-nm": major
13+
"@yarnpkg/plugin-npm-cli": major
14+
"@yarnpkg/plugin-pack": major
15+
"@yarnpkg/plugin-patch": major
16+
"@yarnpkg/plugin-pnp": major
17+
"@yarnpkg/plugin-pnpm": major
18+
"@yarnpkg/plugin-stage": major
19+
"@yarnpkg/plugin-version": major
20+
"@yarnpkg/plugin-workspace-tools": major
21+
"@yarnpkg/pnpify": major
22+
"@yarnpkg/sdks": major
23+
"@yarnpkg/shell": major
24+
25+
declined:
26+
- "@yarnpkg/plugin-compat"
27+
- "@yarnpkg/plugin-exec"
28+
- "@yarnpkg/plugin-file"
29+
- "@yarnpkg/plugin-github"
30+
- "@yarnpkg/plugin-http"
31+
- "@yarnpkg/plugin-link"
32+
- "@yarnpkg/plugin-npm"
33+
- "@yarnpkg/plugin-typescript"
34+
- "@yarnpkg/extensions"
35+
- "@yarnpkg/nm"

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Yarn now accepts sponsorships! Please give a look at our [OpenCollective](https:
1919
- The network settings configuration option has been renamed from `caFilePath` to `httpsCaFilePath`.
2020
- `yarn workspaces foreach` now automatically enables the `-v,--verbose` flag in interactive terminal environments.
2121
- `yarn npm audit` no longer takes into account publish registries. Use [`npmAuditRegistry`](https://yarnpkg.com/configuration/yarnrc#npmAuditRegistry) instead.
22+
- `yarn npm audit` has been fully rewritten to use the newer `/-/npm/v1/security/advisories/bulk` endpoint. Make sure your registry supports it, and let them know you need it otherwise (in the meantime you can use `npmAuditRegistry` to send your audit queries to the npm registry).
2223
- The `--assume-fresh-project` flag of `yarn init` has been removed. Should only affect people initializing Yarn 4+ projects using a Yarn 2 binary.
2324
- `yarn init` no longer enables zero-installs by default.
2425
- Yarn will no longer remove the old Yarn 2.x `.pnp.js` file when migrating.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@yarnpkg/fslib": "workspace:^",
2020
"@yarnpkg/libzip": "workspace:^",
2121
"@yarnpkg/sdks": "workspace:^",
22-
"clipanion": "^3.2.0-rc.10",
22+
"clipanion": "^3.2.1",
2323
"esbuild-wasm": "0.17.5",
2424
"eslint": "^8.2.0",
2525
"jest": "^29.2.1",

packages/acceptance-tests/pkg-tests-core/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@yarnpkg/core": "workspace:^",
1919
"@yarnpkg/fslib": "workspace:^",
2020
"@yarnpkg/parsers": "workspace:^",
21+
"@yarnpkg/plugin-npm-cli": "workspace:^",
2122
"finalhandler": "^1.1.2",
2223
"invariant": "^2.2.4",
2324
"pem": "dexus/pem",
@@ -37,6 +38,7 @@
3738
"node": ">=14.15.0"
3839
},
3940
"dependencies": {
41+
"typanion": "^3.12.1",
4042
"uuid": "^8.3.2"
4143
}
4244
}

packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import {semverUtils} from '@yarnpkg/core';
12
import {PortablePath, npath, toFilename, xfs, ppath, Filename} from '@yarnpkg/fslib';
3+
import {npmAuditTypes} from '@yarnpkg/plugin-npm-cli';
24
import assert from 'assert';
35
import crypto from 'crypto';
46
import finalhandler from 'finalhandler';
@@ -12,6 +14,7 @@ import pem from 'pem';
1214
import semver from 'semver';
1315
import serveStatic from 'serve-static';
1416
import stream from 'stream';
17+
import * as t from 'typanion';
1518
import {promisify} from 'util';
1619
import {v5 as uuidv5} from 'uuid';
1720
import {Gzip} from 'zlib';
@@ -53,6 +56,7 @@ export enum RequestType {
5356
Whoami = `whoami`,
5457
Repository = `repository`,
5558
Publish = `publish`,
59+
BulkAdvisories = `bulkAdvisories`,
5660
}
5761

5862
export type Request = {
@@ -76,6 +80,8 @@ export type Request = {
7680
type: RequestType.Publish;
7781
scope?: string;
7882
localName: string;
83+
} | {
84+
type: RequestType.BulkAdvisories;
7985
};
8086

8187
export class Login {
@@ -108,6 +114,42 @@ export class Login {
108114
}
109115
}
110116

117+
export const ADVISORIES = new Map<string, Array<npmAuditTypes.AuditMetadata>>([
118+
[`vulnerable`, [{
119+
id: 1,
120+
url: `https://example.com/advisories/1`,
121+
title: `Something is wrong`,
122+
severity: npmAuditTypes.Severity.High,
123+
vulnerable_versions: `<1.1.0`,
124+
}]],
125+
[`vulnerable-peer-deps`, [{
126+
id: 2,
127+
url: `https://example.com/advisories/2`,
128+
title: `Something else is wrong`,
129+
severity: npmAuditTypes.Severity.High,
130+
vulnerable_versions: `<1.1.0`,
131+
}]],
132+
[`vulnerable-many`, [{
133+
id: 3,
134+
url: `https://example.com/advisories/3`,
135+
title: `Something is wrong`,
136+
severity: npmAuditTypes.Severity.High,
137+
vulnerable_versions: `<1.1.0`,
138+
}, {
139+
id: 4,
140+
url: `https://example.com/advisories/4`,
141+
title: `Something is still wrong`,
142+
severity: npmAuditTypes.Severity.High,
143+
vulnerable_versions: `<1.1.0`,
144+
}, {
145+
id: 5,
146+
url: `https://example.com/advisories/5`,
147+
title: `Something is always wrong`,
148+
severity: npmAuditTypes.Severity.High,
149+
vulnerable_versions: `<1.1.0`,
150+
}]],
151+
]);
152+
111153
export const validLogins = {
112154
fooUser: new Login(`foo-user`),
113155
barUser: new Login(`bar-user`),
@@ -440,6 +482,7 @@ export const startPackageServer = ({type}: { type: keyof typeof packageServerUrl
440482
async [RequestType.Repository](parsedRequest, request, response) {
441483
staticServer(request as any, response as any, finalhandler(request, response));
442484
},
485+
443486
async [RequestType.Publish](parsedRequest, request, response) {
444487
if (parsedRequest.type !== RequestType.Publish)
445488
throw new Error(`Assertion failed: Invalid request type`);
@@ -472,6 +515,41 @@ export const startPackageServer = ({type}: { type: keyof typeof packageServerUrl
472515
return response.end(rawData);
473516
});
474517
},
518+
519+
async [RequestType.BulkAdvisories](parsedRequest, request, response) {
520+
if (parsedRequest.type !== RequestType.BulkAdvisories)
521+
throw new Error(`Assertion failed: Invalid request type`);
522+
523+
let rawData = ``;
524+
525+
request.on(`data`, chunk => rawData += chunk);
526+
request.on(`end`, () => {
527+
let body;
528+
try {
529+
body = JSON.parse(rawData);
530+
} catch (e) {
531+
return processError(response, 401, `Invalid`);
532+
}
533+
534+
t.assertWithErrors(body, t.isRecord(t.isArray(t.isString())));
535+
536+
const result = Object.create(null);
537+
for (const [packageName, versions] of Object.entries(body)) {
538+
const advisories = [];
539+
540+
for (const advisory of ADVISORIES.get(packageName) ?? [])
541+
if (versions.some(version => semverUtils.satisfiesWithPrereleases(version, advisory.vulnerable_versions)))
542+
advisories.push(advisory);
543+
544+
if (advisories.length > 0) {
545+
result[packageName] = advisories;
546+
}
547+
}
548+
549+
response.writeHead(200, {[`Content-Type`]: `application/json`});
550+
return response.end(JSON.stringify(result));
551+
});
552+
},
475553
};
476554

477555
const sendError = (res: ServerResponse, statusCode: number, errorMessage: string): void => {
@@ -508,6 +586,10 @@ export const startPackageServer = ({type}: { type: keyof typeof packageServerUrl
508586
// Set later when login is parsed
509587
login: null as any,
510588
};
589+
} else if (url === `/-/npm/v1/security/advisories/bulk`) {
590+
return {
591+
type: RequestType.BulkAdvisories,
592+
};
511593
} else if ((match = url.match(/^\/(?:(@[^/]+)\/)?([^@/][^/]*)$/)) && method == `PUT`) {
512594
const [, scope, localName] = match;
513595

0 commit comments

Comments
 (0)