socket 0.14.40-alpha.9 → 0.14.40

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.
@@ -9,26 +9,154 @@ function _socketInterop(e) {
9
9
  return c ? e.default : e
10
10
  }
11
11
 
12
- var events = require('node:events');
13
- var fs = require('node:fs');
14
- var https = require('node:https');
15
12
  var path = require('node:path');
16
- var readline = require('node:readline');
17
- var promises = require('node:timers/promises');
18
- var npa = _socketInterop(require('npm-package-arg'));
13
+ var process = require('node:process');
19
14
  var semver = _socketInterop(require('semver'));
20
- var config = require('@socketsecurity/config');
21
15
  var registry = require('@socketsecurity/registry');
22
16
  var objects = require('@socketsecurity/registry/lib/objects');
23
17
  var packages = require('@socketsecurity/registry/lib/packages');
24
18
  var prompts = require('@socketsecurity/registry/lib/prompts');
25
19
  var spinner = require('@socketsecurity/registry/lib/spinner');
20
+ var events = require('node:events');
21
+ var https = require('node:https');
22
+ var readline = require('node:readline');
26
23
  var constants = require('./constants.js');
27
- var sdk = require('./sdk.js');
24
+ var socketUrl = require('./socket-url.js');
25
+ var fs = require('node:fs');
26
+ var promises = require('node:timers/promises');
27
+ var config = require('@socketsecurity/config');
28
28
  var pathResolve = require('./path-resolve.js');
29
+ var npa = _socketInterop(require('npm-package-arg'));
29
30
 
30
- //#region UX Constants
31
+ const {
32
+ API_V0_URL,
33
+ LOOP_SENTINEL: LOOP_SENTINEL$2,
34
+ SOCKET_CLI_FIX_PACKAGE_LOCK_FILE: SOCKET_CLI_FIX_PACKAGE_LOCK_FILE$1,
35
+ abortSignal: abortSignal$2
36
+ } = constants;
37
+ async function* batchScan(pkgIds) {
38
+ const req = https.request(`${API_V0_URL}/purl?alerts=true`, {
39
+ method: 'POST',
40
+ headers: {
41
+ Authorization: `Basic ${Buffer.from(`${socketUrl.getPublicToken()}:`).toString('base64url')}`
42
+ },
43
+ signal: abortSignal$2
44
+ }).end(JSON.stringify({
45
+ components: pkgIds.map(id => ({
46
+ purl: `pkg:npm/${id}`
47
+ }))
48
+ }));
49
+ const {
50
+ 0: res
51
+ } = await events.once(req, 'response');
52
+ const ok = res.statusCode >= 200 && res.statusCode <= 299;
53
+ if (!ok) {
54
+ throw new Error(`Socket API Error: ${res.statusCode}`);
55
+ }
56
+ const rli = readline.createInterface(res);
57
+ for await (const line of rli) {
58
+ yield JSON.parse(line);
59
+ }
60
+ }
61
+ function isAlertFixable(alert) {
62
+ return alert.type === 'socketUpgradeAvailable' || isAlertFixableCve(alert);
63
+ }
64
+ function isAlertFixableCve(alert) {
65
+ const {
66
+ type
67
+ } = alert;
68
+ return (type === 'cve' || type === 'mediumCVE' || type === 'mildCVE' || type === 'criticalCVE') && !!alert.props?.['firstPatchedVersionIdentifier'];
69
+ }
70
+ function toRepoUrl(resolved) {
71
+ try {
72
+ return URL.parse(resolved)?.origin ?? '';
73
+ } catch {}
74
+ return '';
75
+ }
76
+ function walk(diff_, options) {
77
+ const {
78
+ // Lazily access constants.IPC.
79
+ fix = constants.IPC[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE$1]
80
+ } = {
81
+ __proto__: null,
82
+ ...options
83
+ };
84
+ const needInfoOn = [];
85
+ // `diff_` is `null` when `npm install --package-lock-only` is passed.
86
+ if (!diff_) {
87
+ return needInfoOn;
88
+ }
89
+ const queue = [...diff_.children];
90
+ let pos = 0;
91
+ let {
92
+ length: queueLength
93
+ } = queue;
94
+ while (pos < queueLength) {
95
+ if (pos === LOOP_SENTINEL$2) {
96
+ throw new Error('Detected infinite loop while walking Arborist diff');
97
+ }
98
+ const diff = queue[pos++];
99
+ const {
100
+ action
101
+ } = diff;
102
+ if (action) {
103
+ // The `pkgNode`, i.e. the `ideal` node, will be `undefined` if the diff
104
+ // action is 'REMOVE'
105
+ // The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff
106
+ // action is 'ADD'.
107
+ const {
108
+ actual: oldNode,
109
+ ideal: pkgNode
110
+ } = diff;
111
+ let existing;
112
+ let keep = false;
113
+ if (action === 'CHANGE') {
114
+ if (pkgNode?.package.version !== oldNode?.package.version) {
115
+ keep = true;
116
+ if (oldNode?.package.name && oldNode.package.name === pkgNode?.package.name) {
117
+ existing = oldNode.pkgid;
118
+ }
119
+ }
120
+ } else {
121
+ keep = action !== 'REMOVE';
122
+ }
123
+ if (keep && pkgNode?.resolved && (!oldNode || oldNode.resolved)) {
124
+ needInfoOn.push({
125
+ existing,
126
+ pkgid: pkgNode.pkgid,
127
+ repository_url: toRepoUrl(pkgNode.resolved)
128
+ });
129
+ }
130
+ }
131
+ for (const child of diff.children) {
132
+ queue[queueLength++] = child;
133
+ }
134
+ }
135
+ if (fix) {
136
+ const {
137
+ unchanged
138
+ } = diff_;
139
+ for (let i = 0, {
140
+ length
141
+ } = unchanged; i < length; i += 1) {
142
+ const pkgNode = unchanged[i];
143
+ needInfoOn.push({
144
+ existing: pkgNode.pkgid,
145
+ pkgid: pkgNode.pkgid,
146
+ repository_url: toRepoUrl(pkgNode.resolved)
147
+ });
148
+ }
149
+ }
150
+ return needInfoOn;
151
+ }
31
152
 
153
+ const {
154
+ abortSignal: abortSignal$1
155
+ } = constants;
156
+ const ERROR_UX = {
157
+ block: true,
158
+ display: true
159
+ };
32
160
  const IGNORE_UX = {
33
161
  block: false,
34
162
  display: false
@@ -37,18 +165,41 @@ const WARN_UX = {
37
165
  block: false,
38
166
  display: true
39
167
  };
40
- const ERROR_UX = {
41
- block: true,
42
- display: true
43
- };
44
- //#endregion
45
- //#region utils
168
+ function findSocketYmlSync() {
169
+ let prevDir = null;
170
+ let dir = process.cwd();
171
+ while (dir !== prevDir) {
172
+ let ymlPath = path.join(dir, 'socket.yml');
173
+ let yml = maybeReadfileSync(ymlPath);
174
+ if (yml === undefined) {
175
+ ymlPath = path.join(dir, 'socket.yaml');
176
+ yml = maybeReadfileSync(ymlPath);
177
+ }
178
+ if (typeof yml === 'string') {
179
+ try {
180
+ return {
181
+ path: ymlPath,
182
+ parsed: config.parseSocketConfig(yml)
183
+ };
184
+ } catch {
185
+ throw new Error(`Found file but was unable to parse ${ymlPath}`);
186
+ }
187
+ }
188
+ prevDir = dir;
189
+ dir = path.join(dir, '..');
190
+ }
191
+ return null;
192
+ }
193
+ function maybeReadfileSync(filepath) {
194
+ try {
195
+ return fs.readFileSync(filepath, 'utf8');
196
+ } catch {}
197
+ return undefined;
198
+ }
46
199
 
47
- /**
48
- * Iterates over all entries with ordered issue rule for deferral. Iterates over
49
- * all issue rules and finds the first defined value that does not defer otherwise
50
- * uses the defaultValue. Takes the value and converts into a UX workflow.
51
- */
200
+ // Iterates over all entries with ordered issue rule for deferral. Iterates over
201
+ // all issue rules and finds the first defined value that does not defer otherwise
202
+ // uses the defaultValue. Takes the value and converts into a UX workflow.
52
203
  function resolveAlertRuleUX(orderedRulesCollection, defaultValue) {
53
204
  if (defaultValue === true || defaultValue === null || defaultValue === undefined) {
54
205
  defaultValue = {
@@ -87,9 +238,7 @@ function resolveAlertRuleUX(orderedRulesCollection, defaultValue) {
87
238
  };
88
239
  }
89
240
 
90
- /**
91
- * Negative form because it is narrowing the type.
92
- */
241
+ // Negative form because it is narrowing the type.
93
242
  function ruleValueDoesNotDefer(rule) {
94
243
  if (rule === undefined) {
95
244
  return false;
@@ -105,9 +254,7 @@ function ruleValueDoesNotDefer(rule) {
105
254
  return true;
106
255
  }
107
256
 
108
- /**
109
- * Handles booleans for backwards compatibility.
110
- */
257
+ // Handles booleans for backwards compatibility.
111
258
  function uxForDefinedNonDeferValue(ruleValue) {
112
259
  if (typeof ruleValue === 'boolean') {
113
260
  return ruleValue ? ERROR_UX : IGNORE_UX;
@@ -122,10 +269,6 @@ function uxForDefinedNonDeferValue(ruleValue) {
122
269
  }
123
270
  return ERROR_UX;
124
271
  }
125
- //#endregion
126
-
127
- //#region exports
128
-
129
272
  function createAlertUXLookup(settings) {
130
273
  const cachedUX = new Map();
131
274
  return context => {
@@ -171,24 +314,116 @@ function createAlertUXLookup(settings) {
171
314
  return ux;
172
315
  };
173
316
  }
174
- //#endregion
317
+ let _uxLookup;
318
+ async function uxLookup(settings) {
319
+ while (_uxLookup === undefined) {
320
+ // eslint-disable-next-line no-await-in-loop
321
+ await promises.setTimeout(1, {
322
+ signal: abortSignal$1
323
+ });
324
+ }
325
+ return _uxLookup(settings);
326
+ }
327
+
328
+ // Start initializing the AlertUxLookupResult immediately.
329
+ void (async () => {
330
+ const {
331
+ orgs,
332
+ settings
333
+ } = await (async () => {
334
+ try {
335
+ const socketSdk = await socketUrl.setupSdk(socketUrl.getPublicToken());
336
+ const orgResult = await socketSdk.getOrganizations();
337
+ if (!orgResult.success) {
338
+ throw new Error(`Failed to fetch Socket organization info: ${orgResult.error.message}`);
339
+ }
340
+ const orgs = [];
341
+ for (const org of Object.values(orgResult.data.organizations)) {
342
+ if (org) {
343
+ orgs.push(org);
344
+ }
345
+ }
346
+ const result = await socketSdk.postSettings(orgs.map(org => ({
347
+ organization: org.id
348
+ })));
349
+ if (!result.success) {
350
+ throw new Error(`Failed to fetch API key settings: ${result.error.message}`);
351
+ }
352
+ return {
353
+ orgs,
354
+ settings: result.data
355
+ };
356
+ } catch (e) {
357
+ const cause = objects.isObject(e) && 'cause' in e ? e.cause : undefined;
358
+ if (socketUrl.isErrnoException(cause) && (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED')) {
359
+ throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
360
+ cause: e
361
+ });
362
+ }
363
+ throw e;
364
+ }
365
+ })();
366
+
367
+ // Remove any organizations not being enforced.
368
+ const enforcedOrgs = socketUrl.getSetting('enforcedOrgs') ?? [];
369
+ for (const {
370
+ 0: i,
371
+ 1: org
372
+ } of orgs.entries()) {
373
+ if (!enforcedOrgs.includes(org.id)) {
374
+ settings.entries.splice(i, 1);
375
+ }
376
+ }
377
+ const socketYml = findSocketYmlSync();
378
+ if (socketYml) {
379
+ settings.entries.push({
380
+ start: socketYml.path,
381
+ settings: {
382
+ [socketYml.path]: {
383
+ deferTo: null,
384
+ // TODO: TypeScript complains about the type not matching. We should
385
+ // figure out why are providing
386
+ // issueRules: { [issueName: string]: boolean }
387
+ // but expecting
388
+ // issueRules: { [issueName: string]: { action: 'defer' | 'error' | 'ignore' | 'monitor' | 'warn' } }
389
+ issueRules: socketYml.parsed.issueRules
390
+ }
391
+ }
392
+ });
393
+ }
394
+ _uxLookup = createAlertUXLookup(settings);
395
+ })();
175
396
 
176
397
  const {
177
- API_V0_URL,
178
- ENV,
179
- LOOP_SENTINEL,
180
- NPM,
181
- NPM_REGISTRY_URL,
182
- SOCKET_CLI_FIX_PACKAGE_LOCK_FILE,
183
- SOCKET_CLI_ISSUES_URL,
184
- SOCKET_CLI_UPDATE_OVERRIDES_IN_PACKAGE_LOCK_FILE,
185
- SOCKET_PUBLIC_API_KEY,
186
- abortSignal,
187
- rootPath
398
+ NODE_MODULES,
399
+ SOCKET_CLI_ISSUES_URL
188
400
  } = constants;
189
- const POTENTIAL_BUG_ERROR_MESSAGE = `This is may be a bug with socket-npm related to changes to the npm CLI.\nPlease report to ${SOCKET_CLI_ISSUES_URL}.`;
190
- const npmEntrypoint = fs.realpathSync(process.argv[1]);
401
+ const npmEntrypoint = fs.realpathSync.native(process.argv[1]);
191
402
  const npmRootPath = pathResolve.findRoot(path.dirname(npmEntrypoint));
403
+ if (npmRootPath === undefined) {
404
+ console.error(`Unable to find npm CLI install directory.
405
+ Searched parent directories of ${npmEntrypoint}.
406
+
407
+ This is may be a bug with socket-npm related to changes to the npm CLI.
408
+ Please report to ${SOCKET_CLI_ISSUES_URL}.`);
409
+ // The exit code 127 indicates that the command or binary being executed
410
+ // could not be found.
411
+ process.exit(127);
412
+ }
413
+ const npmNmPath = path.join(npmRootPath, NODE_MODULES);
414
+ const arboristPkgPath = path.join(npmNmPath, '@npmcli/arborist');
415
+ const arboristClassPath = path.join(arboristPkgPath, 'lib/arborist/index.js');
416
+ const arboristDepValidPath = path.join(arboristPkgPath, 'lib/dep-valid.js');
417
+ const arboristEdgeClassPath = path.join(arboristPkgPath, 'lib/edge.js');
418
+ const arboristNodeClassPath = path.join(arboristPkgPath, 'lib/node.js');
419
+ const arboristOverrideSetClassPath = path.join(arboristPkgPath, 'lib/override-set.js');
420
+ const pacotePath = path.join(npmNmPath, 'pacote');
421
+
422
+ const depValid = require(arboristDepValidPath);
423
+
424
+ const {
425
+ UNDEFINED_TOKEN
426
+ } = constants;
192
427
  function tryRequire(...ids) {
193
428
  for (const data of ids) {
194
429
  let id;
@@ -211,350 +446,432 @@ function tryRequire(...ids) {
211
446
  }
212
447
  return undefined;
213
448
  }
214
- if (npmRootPath === undefined) {
215
- console.error(`Unable to find npm CLI install directory.\nSearched parent directories of ${npmEntrypoint}.\n\n${POTENTIAL_BUG_ERROR_MESSAGE}`);
216
- // The exit code 127 indicates that the command or binary being executed
217
- // could not be found.
218
- process.exit(127);
219
- }
220
- const npmNmPath = path.join(npmRootPath, 'node_modules');
221
- const arboristPkgPath = path.join(npmNmPath, '@npmcli/arborist');
222
- const arboristClassPath = path.join(arboristPkgPath, 'lib/arborist/index.js');
223
- const arboristDepValidPath = path.join(arboristPkgPath, 'lib/dep-valid.js');
224
- const arboristEdgeClassPath = path.join(arboristPkgPath, 'lib/edge.js');
225
- const arboristNodeClassPath = path.join(arboristPkgPath, 'lib/node.js');
226
- const arboristOverrideSetClassPatch = path.join(arboristPkgPath, 'lib/override-set.js');
227
- const log = tryRequire([path.join(npmNmPath, 'proc-log/lib/index.js'),
228
- // The proc-log DefinitelyTyped definition is incorrect. The type definition
229
- // is really that of its export log.
230
- mod => mod.log], path.join(npmNmPath, 'npmlog/lib/log.js'));
231
- const pacote = require(path.join(npmNmPath, 'pacote'));
232
- const translations = require(path.join(rootPath, 'translations.json'));
233
- const Arborist = require(arboristClassPath);
234
- const depValid = require(arboristDepValidPath);
235
- const Edge = require(arboristEdgeClassPath);
236
- const Node = require(arboristNodeClassPath);
237
- const OverrideSet = require(arboristOverrideSetClassPatch);
238
- const kCtorArgs = Symbol('ctorArgs');
239
- const kRiskyReify = Symbol('riskyReify');
240
- const formatter = new sdk.ColorOrMarkdown(false);
241
- const pubToken = sdk.getDefaultKey() ?? SOCKET_PUBLIC_API_KEY;
242
- let _uxLookup;
243
- async function uxLookup(settings) {
244
- while (_uxLookup === undefined) {
245
- // eslint-disable-next-line no-await-in-loop
246
- await promises.setTimeout(1, {
247
- signal: abortSignal
248
- });
249
- }
250
- return _uxLookup(settings);
251
- }
252
- function packageAlertsToReport(alerts) {
253
- let report = null;
254
- for (const alert of alerts) {
255
- if (!isAlertFixableCve(alert.raw)) {
256
- continue;
257
- }
258
- const {
259
- name
260
- } = alert;
261
- if (!report) {
262
- report = {};
263
- }
264
- if (!report[name]) {
265
- report[name] = [];
266
- }
267
- const props = alert.raw?.props;
268
- report[name].push({
269
- id: -1,
270
- url: props?.url,
271
- title: props?.title,
272
- severity: alert.raw?.severity?.toLowerCase(),
273
- vulnerable_versions: props?.vulnerableVersionRange,
274
- cwe: props?.cwes,
275
- cvss: props?.csvs,
276
- name
277
- });
449
+ let _log = UNDEFINED_TOKEN;
450
+ function getLogger() {
451
+ if (_log === UNDEFINED_TOKEN) {
452
+ _log = tryRequire([path.join(npmNmPath, 'proc-log/lib/index.js'),
453
+ // The proc-log DefinitelyTyped definition is incorrect. The type definition
454
+ // is really that of its export log.
455
+ mod => mod.log], path.join(npmNmPath, 'npmlog/lib/log.js'));
278
456
  }
279
- return report;
457
+ return _log;
280
458
  }
281
- async function* batchScan(pkgIds) {
282
- const req = https.request(`${API_V0_URL}/purl?alerts=true`, {
283
- method: 'POST',
284
- headers: {
285
- Authorization: `Basic ${Buffer.from(`${pubToken}:`).toString('base64url')}`
286
- },
287
- signal: abortSignal
288
- }).end(JSON.stringify({
289
- components: pkgIds.map(id => ({
290
- purl: `pkg:npm/${id}`
291
- }))
292
- }));
293
- const {
294
- 0: res
295
- } = await events.once(req, 'response');
296
- const ok = res.statusCode >= 200 && res.statusCode <= 299;
297
- if (!ok) {
298
- throw new Error(`Socket API Error: ${res.statusCode}`);
299
- }
300
- const rli = readline.createInterface(res);
301
- for await (const line of rli) {
302
- yield JSON.parse(line);
459
+
460
+ const {
461
+ LOOP_SENTINEL: LOOP_SENTINEL$1
462
+ } = constants;
463
+ const OverrideSet = require(arboristOverrideSetClassPath);
464
+
465
+ // Implementation code not related to patch https://github.com/npm/cli/pull/7025
466
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/override-set.js:
467
+ class SafeOverrideSet extends OverrideSet {
468
+ // Patch adding doOverrideSetsConflict is based on
469
+ // https://github.com/npm/cli/pull/7025.
470
+ static doOverrideSetsConflict(first, second) {
471
+ // If override sets contain one another then we can try to use the more specific
472
+ // one. However, if neither one is more specific, then we consider them to be
473
+ // in conflict.
474
+ return this.findSpecificOverrideSet(first, second) === undefined;
303
475
  }
304
- }
305
476
 
306
- // Patch adding doOverrideSetsConflict is based on
307
- // https://github.com/npm/cli/pull/7025.
308
- function doOverrideSetsConflict(first, second) {
309
- // If override sets contain one another then we can try to use the more specific
310
- // one. However, if neither one is more specific, then we consider them to be
311
- // in conflict.
312
- return findSpecificOverrideSet(first, second) === undefined;
313
- }
314
- function findSocketYmlSync() {
315
- let prevDir = null;
316
- let dir = process.cwd();
317
- while (dir !== prevDir) {
318
- let ymlPath = path.join(dir, 'socket.yml');
319
- let yml = maybeReadfileSync(ymlPath);
320
- if (yml === undefined) {
321
- ymlPath = path.join(dir, 'socket.yaml');
322
- yml = maybeReadfileSync(ymlPath);
477
+ // Patch adding findSpecificOverrideSet is based on
478
+ // https://github.com/npm/cli/pull/7025.
479
+ static findSpecificOverrideSet(first, second) {
480
+ let overrideSet = second;
481
+ while (overrideSet) {
482
+ if (overrideSet.isEqual(first)) {
483
+ return second;
484
+ }
485
+ overrideSet = overrideSet.parent;
323
486
  }
324
- if (typeof yml === 'string') {
325
- try {
326
- return {
327
- path: ymlPath,
328
- parsed: config.parseSocketConfig(yml)
329
- };
330
- } catch {
331
- throw new Error(`Found file but was unable to parse ${ymlPath}`);
487
+ overrideSet = first;
488
+ while (overrideSet) {
489
+ if (overrideSet.isEqual(second)) {
490
+ return first;
332
491
  }
492
+ overrideSet = overrideSet.parent;
333
493
  }
334
- prevDir = dir;
335
- dir = path.join(dir, '..');
494
+ // The override sets are incomparable. Neither one contains the other.
495
+ const log = getLogger();
496
+ log?.silly('Conflicting override sets', first, second);
497
+ return undefined;
336
498
  }
337
- return null;
338
- }
339
499
 
340
- // Patch adding findSpecificOverrideSet is based on
341
- // https://github.com/npm/cli/pull/7025.
342
- function findSpecificOverrideSet(first, second) {
343
- let overrideSet = second;
344
- while (overrideSet) {
345
- if (overrideSet.isEqual(first)) {
346
- return second;
347
- }
348
- overrideSet = overrideSet.parent;
349
- }
350
- overrideSet = first;
351
- while (overrideSet) {
352
- if (overrideSet.isEqual(second)) {
353
- return first;
354
- }
355
- overrideSet = overrideSet.parent;
356
- }
357
- // The override sets are incomparable. Neither one contains the other.
358
- log?.silly('Conflicting override sets', first, second);
359
- return undefined;
360
- }
361
- function isAlertFixable(alert) {
362
- return alert.type === 'socketUpgradeAvailable' || isAlertFixableCve(alert);
363
- }
364
- function isAlertFixableCve(alert) {
365
- const {
366
- type
367
- } = alert;
368
- return (type === 'cve' || type === 'mediumCVE' || type === 'mildCVE' || type === 'criticalCVE') && !!alert.props?.['firstPatchedVersionIdentifier'];
369
- }
370
- function maybeReadfileSync(filepath) {
371
- try {
372
- return fs.readFileSync(filepath, 'utf8');
373
- } catch {}
374
- return undefined;
375
- }
376
- async function getPackagesAlerts(safeArb, pkgs, output) {
377
- const spinner$1 = new spinner.Spinner({
378
- stream: output
379
- });
380
- let {
381
- length: remaining
382
- } = pkgs;
383
- const packageAlerts = [];
384
- if (!remaining) {
385
- spinner$1.success('No changes detected');
386
- return packageAlerts;
387
- }
388
- const getText = () => `Looking up data for ${remaining} packages`;
389
- spinner$1.start(getText());
390
- try {
391
- for await (const artifact of batchScan(pkgs.map(p => p.pkgid))) {
392
- if (!artifact.name || !artifact.version || !artifact.alerts?.length) {
393
- continue;
500
+ // Patch adding childrenAreEqual is based on
501
+ // https://github.com/npm/cli/pull/7025.
502
+ childrenAreEqual(otherOverrideSet) {
503
+ const queue = [[this, otherOverrideSet]];
504
+ let pos = 0;
505
+ let {
506
+ length: queueLength
507
+ } = queue;
508
+ while (pos < queueLength) {
509
+ if (pos === LOOP_SENTINEL$1) {
510
+ throw new Error('Detected infinite loop while comparing override sets');
394
511
  }
395
512
  const {
396
- version
397
- } = artifact;
398
- const name = packages.resolvePackageName(artifact);
399
- const id = `${name}@${artifact.version}`;
400
- let blocked = false;
401
- let displayWarning = false;
402
- let alerts = [];
403
- for (const alert of artifact.alerts) {
404
- // eslint-disable-next-line no-await-in-loop
405
- const ux = await uxLookup({
406
- package: {
407
- name,
408
- version
409
- },
410
- alert: {
411
- type: alert.type
412
- }
413
- });
414
- if (ux.block) {
415
- blocked = true;
416
- }
417
- if (ux.display) {
418
- displayWarning = true;
419
- }
420
- if (ux.block || ux.display) {
421
- alerts.push({
422
- name,
423
- version,
424
- type: alert.type,
425
- block: ux.block,
426
- raw: alert,
427
- fixable: isAlertFixable(alert)
428
- });
429
- if (!ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
430
- // Before we ask about problematic issues, check to see if they
431
- // already existed in the old version if they did, be quiet.
432
- const existing = pkgs.find(p => p.existing?.startsWith(`${name}@`))?.existing;
433
- if (existing) {
434
- const oldArtifact =
435
- // eslint-disable-next-line no-await-in-loop
436
- (await batchScan([existing]).next()).value;
437
- if (oldArtifact?.alerts?.length) {
438
- alerts = alerts.filter(({
439
- type
440
- }) => !oldArtifact.alerts?.find(a => a.type === type));
441
- }
442
- }
443
- }
444
- }
445
- }
446
- if (!blocked) {
447
- const pkg = pkgs.find(p => p.pkgid === id);
448
- if (pkg) {
449
- await pacote.tarball.stream(id, stream => {
450
- stream.resume();
451
- return stream.promise();
452
- }, {
453
- ...safeArb[kCtorArgs][0]
454
- });
455
- }
513
+ 0: currSet,
514
+ 1: currOtherSet
515
+ } = queue[pos++];
516
+ const {
517
+ children
518
+ } = currSet;
519
+ const {
520
+ children: otherChildren
521
+ } = currOtherSet;
522
+ if (children.size !== otherChildren.size) {
523
+ return false;
456
524
  }
457
- if (displayWarning) {
458
- spinner$1.stop(`(socket) ${formatter.hyperlink(id, `https://socket.dev/npm/package/${name}/overview/${version}`)} contains risks:`);
459
- alerts.sort((a, b) => a.type < b.type ? -1 : 1);
460
- const lines = new Set();
461
- for (const alert of alerts) {
462
- // Based data from { pageProps: { alertTypes } } of:
463
- // https://socket.dev/_next/data/94666139314b6437ee4491a0864e72b264547585/en-US.json
464
- const info = translations.alerts[alert.type];
465
- const title = info?.title ?? alert.type;
466
- const attributes = [...(alert.fixable ? ['fixable'] : []), ...(alert.block ? [] : ['non-blocking'])];
467
- const maybeAttributes = attributes.length ? ` (${attributes.join('; ')})` : '';
468
- const maybeDesc = info?.description ? ` - ${info.description}` : '';
469
- // TODO: emoji seems to mis-align terminals sometimes
470
- lines.add(` ${title}${maybeAttributes}${maybeDesc}\n`);
525
+ for (const key of children.keys()) {
526
+ if (!otherChildren.has(key)) {
527
+ return false;
471
528
  }
472
- for (const line of lines) {
473
- output?.write(line);
529
+ const child = children.get(key);
530
+ const otherChild = otherChildren.get(key);
531
+ if (child.value !== otherChild.value) {
532
+ return false;
474
533
  }
475
- spinner$1.start();
534
+ queue[queueLength++] = [child, otherChild];
476
535
  }
477
- remaining -= 1;
478
- spinner$1.text = remaining > 0 ? getText() : '';
479
- packageAlerts.push(...alerts);
480
536
  }
481
- } finally {
482
- spinner$1.stop();
537
+ return true;
483
538
  }
484
- return packageAlerts;
485
- }
486
- function toRepoUrl(resolved) {
487
- try {
488
- return URL.parse(resolved)?.origin ?? '';
489
- } catch {}
490
- return '';
491
- }
492
- function walk(diff_) {
493
- const needInfoOn = [];
494
- const queue = [...diff_.children];
495
- let pos = 0;
496
- let {
497
- length: queueLength
498
- } = queue;
499
- while (pos < queueLength) {
500
- if (pos === LOOP_SENTINEL) {
501
- throw new Error('Detected infinite loop while walking Arborist diff');
502
- }
503
- const diff = queue[pos++];
504
- const {
505
- action
506
- } = diff;
507
- if (action) {
508
- // The `pkgNode`, i.e. the `ideal` node, will be `undefined` if the diff
509
- // action is 'REMOVE'
510
- // The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff
511
- // action is 'ADD'.
512
- const {
513
- actual: oldNode,
514
- ideal: pkgNode
515
- } = diff;
516
- let existing;
517
- let keep = false;
518
- if (action === 'CHANGE') {
519
- if (pkgNode?.package.version !== oldNode?.package.version) {
520
- keep = true;
521
- if (oldNode?.package.name && oldNode.package.name === pkgNode?.package.name) {
522
- existing = oldNode.pkgid;
523
- }
539
+ getEdgeRule(edge) {
540
+ for (const rule of this.ruleset.values()) {
541
+ if (rule.name !== edge.name) {
542
+ continue;
543
+ }
544
+ // If keySpec is * we found our override.
545
+ if (rule.keySpec === '*') {
546
+ return rule;
547
+ }
548
+ // Patch replacing
549
+ // let spec = npa(`${edge.name}@${edge.spec}`)
550
+ // is based on https://github.com/npm/cli/pull/7025.
551
+ //
552
+ // We need to use the rawSpec here, because the spec has the overrides
553
+ // applied to it already.
554
+ let spec = npa(`${edge.name}@${edge.rawSpec}`);
555
+ if (spec.type === 'alias') {
556
+ spec = spec.subSpec;
557
+ }
558
+ if (spec.type === 'git') {
559
+ if (spec.gitRange && rule.keySpec && semver.intersects(spec.gitRange, rule.keySpec)) {
560
+ return rule;
524
561
  }
525
- } else {
526
- keep = action !== 'REMOVE';
562
+ continue;
527
563
  }
528
- if (keep && pkgNode?.resolved && (!oldNode || oldNode.resolved)) {
529
- needInfoOn.push({
530
- existing,
531
- pkgid: pkgNode.pkgid,
532
- repository_url: toRepoUrl(pkgNode.resolved)
533
- });
564
+ if (spec.type === 'range' || spec.type === 'version') {
565
+ if (rule.keySpec && semver.intersects(spec.fetchSpec, rule.keySpec)) {
566
+ return rule;
567
+ }
568
+ continue;
534
569
  }
570
+ // If we got this far, the spec type is one of tag, directory or file
571
+ // which means we have no real way to make version comparisons, so we
572
+ // just accept the override.
573
+ return rule;
535
574
  }
536
- for (const child of diff.children) {
537
- queue[queueLength++] = child;
575
+ return this;
576
+ }
577
+
578
+ // Patch adding isEqual is based on
579
+ // https://github.com/npm/cli/pull/7025.
580
+ isEqual(otherOverrideSet) {
581
+ if (this === otherOverrideSet) {
582
+ return true;
583
+ }
584
+ if (!otherOverrideSet) {
585
+ return false;
538
586
  }
587
+ if (this.key !== otherOverrideSet.key || this.value !== otherOverrideSet.value) {
588
+ return false;
589
+ }
590
+ if (!this.childrenAreEqual(otherOverrideSet)) {
591
+ return false;
592
+ }
593
+ if (!this.parent) {
594
+ return !otherOverrideSet.parent;
595
+ }
596
+ return this.parent.isEqual(otherOverrideSet.parent);
539
597
  }
540
- if (ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
598
+ }
599
+
600
+ const Node = require(arboristNodeClassPath);
601
+
602
+ // Implementation code not related to patch https://github.com/npm/cli/pull/7025
603
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/node.js:
604
+ class SafeNode extends Node {
605
+ // Return true if it's safe to remove this node, because anything that is
606
+ // depending on it would be fine with the thing that they would resolve to if
607
+ // it was removed, or nothing is depending on it in the first place.
608
+ canDedupe(preferDedupe = false) {
609
+ // Not allowed to mess with shrinkwraps or bundles.
610
+ if (this.inDepBundle || this.inShrinkwrap) {
611
+ return false;
612
+ }
613
+ // It's a top level pkg, or a dep of one.
614
+ if (!this.resolveParent?.resolveParent) {
615
+ return false;
616
+ }
617
+ // No one wants it, remove it.
618
+ if (this.edgesIn.size === 0) {
619
+ return true;
620
+ }
621
+ const other = this.resolveParent.resolveParent.resolve(this.name);
622
+ // Nothing else, need this one.
623
+ if (!other) {
624
+ return false;
625
+ }
626
+ // If it's the same thing, then always fine to remove.
627
+ if (other.matches(this)) {
628
+ return true;
629
+ }
630
+ // If the other thing can't replace this, then skip it.
631
+ if (!other.canReplace(this)) {
632
+ return false;
633
+ }
634
+ // Patch replacing
635
+ // if (preferDedupe || semver.gte(other.version, this.version)) {
636
+ // return true
637
+ // }
638
+ // is based on https://github.com/npm/cli/pull/7025.
639
+ //
640
+ // If we prefer dedupe, or if the version is equal, take the other.
641
+ if (preferDedupe || semver.eq(other.version, this.version)) {
642
+ return true;
643
+ }
644
+ // If our current version isn't the result of an override, then prefer to
645
+ // take the greater version.
646
+ if (!this.overridden && semver.gt(other.version, this.version)) {
647
+ return true;
648
+ }
649
+ return false;
650
+ }
651
+
652
+ // Is it safe to replace one node with another? check the edges to
653
+ // make sure no one will get upset. Note that the node might end up
654
+ // having its own unmet dependencies, if the new node has new deps.
655
+ // Note that there are cases where Arborist will opt to insert a node
656
+ // into the tree even though this function returns false! This is
657
+ // necessary when a root dependency is added or updated, or when a
658
+ // root dependency brings peer deps along with it. In that case, we
659
+ // will go ahead and create the invalid state, and then try to resolve
660
+ // it with more tree construction, because it's a user request.
661
+ canReplaceWith(node, ignorePeers) {
662
+ if (this.name !== node.name || this.packageName !== node.packageName) {
663
+ return false;
664
+ }
665
+ // Patch replacing
666
+ // if (node.overrides !== this.overrides) {
667
+ // return false
668
+ // }
669
+ // is based on https://github.com/npm/cli/pull/7025.
670
+ //
671
+ // If this node has no dependencies, then it's irrelevant to check the
672
+ // override rules of the replacement node.
673
+ if (this.edgesOut.size) {
674
+ // XXX need to check for two root nodes?
675
+ if (node.overrides) {
676
+ if (!node.overrides.isEqual(this.overrides)) {
677
+ return false;
678
+ }
679
+ } else {
680
+ if (this.overrides) {
681
+ return false;
682
+ }
683
+ }
684
+ }
685
+ // To satisfy the patch we ensure `node.overrides === this.overrides`
686
+ // so that the condition we want to replace,
687
+ // if (this.overrides !== node.overrides) {
688
+ // , is not hit.`
689
+ const oldOverrideSet = this.overrides;
690
+ let result = true;
691
+ if (oldOverrideSet !== node.overrides) {
692
+ this.overrides = node.overrides;
693
+ }
694
+ try {
695
+ result = super.canReplaceWith(node, ignorePeers);
696
+ this.overrides = oldOverrideSet;
697
+ } catch (e) {
698
+ this.overrides = oldOverrideSet;
699
+ throw e;
700
+ }
701
+ return result;
702
+ }
703
+
704
+ // Patch adding deleteEdgeIn is based on https://github.com/npm/cli/pull/7025.
705
+ deleteEdgeIn(edge) {
706
+ this.edgesIn.delete(edge);
541
707
  const {
542
- unchanged
543
- } = diff_;
544
- for (let i = 0, {
545
- length
546
- } = unchanged; i < length; i += 1) {
547
- const pkgNode = unchanged[i];
548
- needInfoOn.push({
549
- existing: pkgNode.pkgid,
550
- pkgid: pkgNode.pkgid,
551
- repository_url: toRepoUrl(pkgNode.resolved)
708
+ overrides
709
+ } = edge;
710
+ if (overrides) {
711
+ this.updateOverridesEdgeInRemoved(overrides);
712
+ }
713
+ }
714
+ addEdgeIn(edge) {
715
+ // Patch replacing
716
+ // if (edge.overrides) {
717
+ // this.overrides = edge.overrides
718
+ // }
719
+ // is based on https://github.com/npm/cli/pull/7025.
720
+ //
721
+ // We need to handle the case where the new edge in has an overrides field
722
+ // which is different from the current value.
723
+ if (!this.overrides || !this.overrides.isEqual(edge.overrides)) {
724
+ this.updateOverridesEdgeInAdded(edge.overrides);
725
+ }
726
+ this.edgesIn.add(edge);
727
+ // Try to get metadata from the yarn.lock file.
728
+ this.root.meta?.addEdge(edge);
729
+ }
730
+
731
+ // @ts-ignore: Incorrectly typed as a property instead of an accessor.
732
+ get overridden() {
733
+ // Patch replacing
734
+ // return !!(this.overrides && this.overrides.value && this.overrides.name === this.name)
735
+ // is based on https://github.com/npm/cli/pull/7025.
736
+ if (!this.overrides || !this.overrides.value || this.overrides.name !== this.name) {
737
+ return false;
738
+ }
739
+ // The overrides rule is for a package with this name, but some override rules
740
+ // only apply to specific versions. To make sure this package was actually
741
+ // overridden, we check whether any edge going in had the rule applied to it,
742
+ // in which case its overrides set is different than its source node.
743
+ for (const edge of this.edgesIn) {
744
+ if (edge.overrides && edge.overrides.name === this.name && edge.overrides.value === this.version) {
745
+ if (!edge.overrides?.isEqual(edge.from?.overrides)) {
746
+ return true;
747
+ }
748
+ }
749
+ }
750
+ return false;
751
+ }
752
+
753
+ // Patch adding recalculateOutEdgesOverrides is based on
754
+ // https://github.com/npm/cli/pull/7025.
755
+ recalculateOutEdgesOverrides() {
756
+ // For each edge out propagate the new overrides through.
757
+ for (const edge of this.edgesOut.values()) {
758
+ edge.reload(true);
759
+ if (edge.to) {
760
+ edge.to.updateOverridesEdgeInAdded(edge.overrides);
761
+ }
762
+ }
763
+ }
764
+
765
+ // @ts-ignore: Incorrectly typed to accept null.
766
+ set root(newRoot) {
767
+ // Patch removing
768
+ // if (!this.overrides && this.parent && this.parent.overrides) {
769
+ // this.overrides = this.parent.overrides.getNodeRule(this)
770
+ // }
771
+ // is based on https://github.com/npm/cli/pull/7025.
772
+ //
773
+ // The "root" setter is a really large and complex function. To satisfy the
774
+ // patch we add a dummy value to `this.overrides` so that the condition we
775
+ // want to remove,
776
+ // if (!this.overrides && this.parent && this.parent.overrides) {
777
+ // , is not hit.
778
+ if (!this.overrides) {
779
+ this.overrides = new SafeOverrideSet({
780
+ overrides: ''
552
781
  });
553
782
  }
783
+ try {
784
+ super.root = newRoot;
785
+ this.overrides = undefined;
786
+ } catch (e) {
787
+ this.overrides = undefined;
788
+ throw e;
789
+ }
790
+ }
791
+
792
+ // Patch adding updateOverridesEdgeInAdded is based on
793
+ // https://github.com/npm/cli/pull/7025.
794
+ //
795
+ // This logic isn't perfect either. When we have two edges in that have
796
+ // different override sets, then we have to decide which set is correct. This
797
+ // function assumes the more specific override set is applicable, so if we have
798
+ // dependencies A->B->C and A->C and an override set that specifies what happens
799
+ // for C under A->B, this will work even if the new A->C edge comes along and
800
+ // tries to change the override set. The strictly correct logic is not to allow
801
+ // two edges with different overrides to point to the same node, because even
802
+ // if this node can satisfy both, one of its dependencies might need to be
803
+ // different depending on the edge leading to it. However, this might cause a
804
+ // lot of duplication, because the conflict in the dependencies might never
805
+ // actually happen.
806
+ updateOverridesEdgeInAdded(otherOverrideSet) {
807
+ if (!otherOverrideSet) {
808
+ // Assuming there are any overrides at all, the overrides field is never
809
+ // undefined for any node at the end state of the tree. So if the new edge's
810
+ // overrides is undefined it will be updated later. So we can wait with
811
+ // updating the node's overrides field.
812
+ return false;
813
+ }
814
+ if (!this.overrides) {
815
+ this.overrides = otherOverrideSet;
816
+ this.recalculateOutEdgesOverrides();
817
+ return true;
818
+ }
819
+ if (this.overrides.isEqual(otherOverrideSet)) {
820
+ return false;
821
+ }
822
+ const newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(this.overrides, otherOverrideSet);
823
+ if (newOverrideSet) {
824
+ if (this.overrides.isEqual(newOverrideSet)) {
825
+ return false;
826
+ }
827
+ this.overrides = newOverrideSet;
828
+ this.recalculateOutEdgesOverrides();
829
+ return true;
830
+ }
831
+ // This is an error condition. We can only get here if the new override set
832
+ // is in conflict with the existing.
833
+ const log = getLogger();
834
+ log?.silly('Conflicting override sets', this.name);
835
+ return false;
836
+ }
837
+
838
+ // Patch adding updateOverridesEdgeInRemoved is based on
839
+ // https://github.com/npm/cli/pull/7025.
840
+ updateOverridesEdgeInRemoved(otherOverrideSet) {
841
+ // If this edge's overrides isn't equal to this node's overrides,
842
+ // then removing it won't change newOverrideSet later.
843
+ if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) {
844
+ return false;
845
+ }
846
+ let newOverrideSet;
847
+ for (const edge of this.edgesIn) {
848
+ const {
849
+ overrides: edgeOverrides
850
+ } = edge;
851
+ if (newOverrideSet && edgeOverrides) {
852
+ newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(edgeOverrides, newOverrideSet);
853
+ } else {
854
+ newOverrideSet = edgeOverrides;
855
+ }
856
+ }
857
+ if (this.overrides.isEqual(newOverrideSet)) {
858
+ return false;
859
+ }
860
+ this.overrides = newOverrideSet;
861
+ if (newOverrideSet) {
862
+ // Optimization: If there's any override set at all, then no non-extraneous
863
+ // node has an empty override set. So if we temporarily have no override set
864
+ // (for example, we removed all the edges in), there's no use updating all
865
+ // the edges out right now. Let's just wait until we have an actual override
866
+ // set later.
867
+ this.recalculateOutEdgesOverrides();
868
+ }
869
+ return true;
554
870
  }
555
- return needInfoOn;
556
871
  }
557
872
 
873
+ const Edge = require(arboristEdgeClassPath);
874
+
558
875
  // The Edge class makes heavy use of private properties which subclasses do NOT
559
876
  // have access to. So we have to recreate any functionality that relies on those
560
877
  // private properties and use our own "safe" prefixed non-conflicting private
@@ -618,7 +935,7 @@ class SafeEdge extends Edge {
618
935
  }
619
936
  // Patch adding "else if" condition is based on
620
937
  // https://github.com/npm/cli/pull/7025.
621
- else if (this.overrides && this.#safeTo.edgesOut.size && doOverrideSetsConflict(this.overrides, this.#safeTo.overrides)) {
938
+ else if (this.overrides && this.#safeTo.edgesOut.size && SafeOverrideSet.doOverrideSetsConflict(this.overrides, this.#safeTo.overrides)) {
622
939
  // Any inconsistency between the edge's override set and the target's
623
940
  // override set is potentially problematic. But we only say the edge is
624
941
  // in error if the override sets are plainly conflicting. Note that if
@@ -789,501 +1106,252 @@ class SafeEdge extends Edge {
789
1106
  }
790
1107
  // Patch replacing
791
1108
  // return depValid(node, this.spec, this.#accept, this.#from)
792
- // is based on https://github.com/npm/cli/pull/7025.
793
- //
794
- // If there's no override we just use the spec.
795
- if (!this.overrides?.keySpec) {
796
- return depValid(node, this.spec, this.#safeAccept, this.#safeFrom);
797
- }
798
- // There's some override. If the target node satisfies the overriding spec
799
- // then it's okay.
800
- if (depValid(node, this.spec, this.#safeAccept, this.#safeFrom)) {
801
- return true;
802
- }
803
- // If it doesn't, then it should at least satisfy the original spec.
804
- if (!depValid(node, this.rawSpec, this.#safeAccept, this.#safeFrom)) {
805
- return false;
806
- }
807
- // It satisfies the original spec, not the overriding spec. We need to make
808
- // sure it doesn't use the overridden spec.
809
- // For example, we might have an ^8.0.0 rawSpec, and an override that makes
810
- // keySpec=8.23.0 and the override value spec=9.0.0.
811
- // If the node is 9.0.0, then it's okay because it's consistent with spec.
812
- // If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.
813
- // If the node is 8.23.0, then it's not okay because even though it's consistent
814
- // with the rawSpec, it's also consistent with the keySpec.
815
- // So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.
816
- return !depValid(node, this.overrides.keySpec, this.#safeAccept, this.#safeFrom);
817
- }
818
- }
819
-
820
- // Implementation code not related to patch https://github.com/npm/cli/pull/7025
821
- // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/node.js:
822
- class SafeNode extends Node {
823
- // Return true if it's safe to remove this node, because anything that is
824
- // depending on it would be fine with the thing that they would resolve to if
825
- // it was removed, or nothing is depending on it in the first place.
826
- canDedupe(preferDedupe = false) {
827
- // Not allowed to mess with shrinkwraps or bundles.
828
- if (this.inDepBundle || this.inShrinkwrap) {
829
- return false;
830
- }
831
- // It's a top level pkg, or a dep of one.
832
- if (!this.resolveParent?.resolveParent) {
833
- return false;
834
- }
835
- // No one wants it, remove it.
836
- if (this.edgesIn.size === 0) {
837
- return true;
838
- }
839
- const other = this.resolveParent.resolveParent.resolve(this.name);
840
- // Nothing else, need this one.
841
- if (!other) {
842
- return false;
843
- }
844
- // If it's the same thing, then always fine to remove.
845
- if (other.matches(this)) {
846
- return true;
847
- }
848
- // If the other thing can't replace this, then skip it.
849
- if (!other.canReplace(this)) {
850
- return false;
851
- }
852
- // Patch replacing
853
- // if (preferDedupe || semver.gte(other.version, this.version)) {
854
- // return true
855
- // }
856
- // is based on https://github.com/npm/cli/pull/7025.
857
- //
858
- // If we prefer dedupe, or if the version is equal, take the other.
859
- if (preferDedupe || semver.eq(other.version, this.version)) {
860
- return true;
861
- }
862
- // If our current version isn't the result of an override, then prefer to
863
- // take the greater version.
864
- if (!this.overridden && semver.gt(other.version, this.version)) {
865
- return true;
866
- }
867
- return false;
868
- }
869
-
870
- // Is it safe to replace one node with another? check the edges to
871
- // make sure no one will get upset. Note that the node might end up
872
- // having its own unmet dependencies, if the new node has new deps.
873
- // Note that there are cases where Arborist will opt to insert a node
874
- // into the tree even though this function returns false! This is
875
- // necessary when a root dependency is added or updated, or when a
876
- // root dependency brings peer deps along with it. In that case, we
877
- // will go ahead and create the invalid state, and then try to resolve
878
- // it with more tree construction, because it's a user request.
879
- canReplaceWith(node, ignorePeers) {
880
- if (this.name !== node.name || this.packageName !== node.packageName) {
881
- return false;
882
- }
883
- // Patch replacing
884
- // if (node.overrides !== this.overrides) {
885
- // return false
886
- // }
887
- // is based on https://github.com/npm/cli/pull/7025.
888
- //
889
- // If this node has no dependencies, then it's irrelevant to check the
890
- // override rules of the replacement node.
891
- if (this.edgesOut.size) {
892
- // XXX need to check for two root nodes?
893
- if (node.overrides) {
894
- if (!node.overrides.isEqual(this.overrides)) {
895
- return false;
896
- }
897
- } else {
898
- if (this.overrides) {
899
- return false;
900
- }
901
- }
902
- }
903
- // To satisfy the patch we ensure `node.overrides === this.overrides`
904
- // so that the condition we want to replace,
905
- // if (this.overrides !== node.overrides) {
906
- // , is not hit.`
907
- const oldOverrideSet = this.overrides;
908
- let result = true;
909
- if (oldOverrideSet !== node.overrides) {
910
- this.overrides = node.overrides;
911
- }
912
- try {
913
- result = super.canReplaceWith(node, ignorePeers);
914
- this.overrides = oldOverrideSet;
915
- } catch (e) {
916
- this.overrides = oldOverrideSet;
917
- throw e;
918
- }
919
- return result;
920
- }
921
-
922
- // Patch adding deleteEdgeIn is based on https://github.com/npm/cli/pull/7025.
923
- deleteEdgeIn(edge) {
924
- this.edgesIn.delete(edge);
925
- const {
926
- overrides
927
- } = edge;
928
- if (overrides) {
929
- this.updateOverridesEdgeInRemoved(overrides);
930
- }
931
- }
932
- addEdgeIn(edge) {
933
- // Patch replacing
934
- // if (edge.overrides) {
935
- // this.overrides = edge.overrides
936
- // }
937
- // is based on https://github.com/npm/cli/pull/7025.
938
- //
939
- // We need to handle the case where the new edge in has an overrides field
940
- // which is different from the current value.
941
- if (!this.overrides || !this.overrides.isEqual(edge.overrides)) {
942
- this.updateOverridesEdgeInAdded(edge.overrides);
943
- }
944
- this.edgesIn.add(edge);
945
- // Try to get metadata from the yarn.lock file.
946
- this.root.meta?.addEdge(edge);
947
- }
948
-
949
- // @ts-ignore: Incorrectly typed as a property instead of an accessor.
950
- get overridden() {
951
- // Patch replacing
952
- // return !!(this.overrides && this.overrides.value && this.overrides.name === this.name)
953
- // is based on https://github.com/npm/cli/pull/7025.
954
- if (!this.overrides || !this.overrides.value || this.overrides.name !== this.name) {
955
- return false;
956
- }
957
- // The overrides rule is for a package with this name, but some override rules
958
- // only apply to specific versions. To make sure this package was actually
959
- // overridden, we check whether any edge going in had the rule applied to it,
960
- // in which case its overrides set is different than its source node.
961
- for (const edge of this.edgesIn) {
962
- if (edge.overrides && edge.overrides.name === this.name && edge.overrides.value === this.version) {
963
- if (!edge.overrides?.isEqual(edge.from?.overrides)) {
964
- return true;
965
- }
966
- }
967
- }
968
- return false;
969
- }
970
-
971
- // Patch adding recalculateOutEdgesOverrides is based on
972
- // https://github.com/npm/cli/pull/7025.
973
- recalculateOutEdgesOverrides() {
974
- // For each edge out propagate the new overrides through.
975
- for (const edge of this.edgesOut.values()) {
976
- edge.reload(true);
977
- if (edge.to) {
978
- edge.to.updateOverridesEdgeInAdded(edge.overrides);
979
- }
980
- }
981
- }
982
-
983
- // @ts-ignore: Incorrectly typed to accept null.
984
- set root(newRoot) {
985
- // Patch removing
986
- // if (!this.overrides && this.parent && this.parent.overrides) {
987
- // this.overrides = this.parent.overrides.getNodeRule(this)
988
- // }
989
- // is based on https://github.com/npm/cli/pull/7025.
990
- //
991
- // The "root" setter is a really large and complex function. To satisfy the
992
- // patch we add a dummy value to `this.overrides` so that the condition we
993
- // want to remove,
994
- // if (!this.overrides && this.parent && this.parent.overrides) {
995
- // , is not hit.
996
- if (!this.overrides) {
997
- this.overrides = new OverrideSet({
998
- overrides: ''
999
- });
1000
- }
1001
- try {
1002
- super.root = newRoot;
1003
- this.overrides = undefined;
1004
- } catch (e) {
1005
- this.overrides = undefined;
1006
- throw e;
1007
- }
1008
- }
1009
-
1010
- // Patch adding updateOverridesEdgeInAdded is based on
1011
- // https://github.com/npm/cli/pull/7025.
1012
- //
1013
- // This logic isn't perfect either. When we have two edges in that have
1014
- // different override sets, then we have to decide which set is correct. This
1015
- // function assumes the more specific override set is applicable, so if we have
1016
- // dependencies A->B->C and A->C and an override set that specifies what happens
1017
- // for C under A->B, this will work even if the new A->C edge comes along and
1018
- // tries to change the override set. The strictly correct logic is not to allow
1019
- // two edges with different overrides to point to the same node, because even
1020
- // if this node can satisfy both, one of its dependencies might need to be
1021
- // different depending on the edge leading to it. However, this might cause a
1022
- // lot of duplication, because the conflict in the dependencies might never
1023
- // actually happen.
1024
- updateOverridesEdgeInAdded(otherOverrideSet) {
1025
- if (!otherOverrideSet) {
1026
- // Assuming there are any overrides at all, the overrides field is never
1027
- // undefined for any node at the end state of the tree. So if the new edge's
1028
- // overrides is undefined it will be updated later. So we can wait with
1029
- // updating the node's overrides field.
1030
- return false;
1109
+ // is based on https://github.com/npm/cli/pull/7025.
1110
+ //
1111
+ // If there's no override we just use the spec.
1112
+ if (!this.overrides?.keySpec) {
1113
+ return depValid(node, this.spec, this.#safeAccept, this.#safeFrom);
1031
1114
  }
1032
- if (!this.overrides) {
1033
- this.overrides = otherOverrideSet;
1034
- this.recalculateOutEdgesOverrides();
1115
+ // There's some override. If the target node satisfies the overriding spec
1116
+ // then it's okay.
1117
+ if (depValid(node, this.spec, this.#safeAccept, this.#safeFrom)) {
1035
1118
  return true;
1036
1119
  }
1037
- if (this.overrides.isEqual(otherOverrideSet)) {
1120
+ // If it doesn't, then it should at least satisfy the original spec.
1121
+ if (!depValid(node, this.rawSpec, this.#safeAccept, this.#safeFrom)) {
1038
1122
  return false;
1039
1123
  }
1040
- const newOverrideSet = findSpecificOverrideSet(this.overrides, otherOverrideSet);
1041
- if (newOverrideSet) {
1042
- if (this.overrides.isEqual(newOverrideSet)) {
1043
- return false;
1044
- }
1045
- this.overrides = newOverrideSet;
1046
- this.recalculateOutEdgesOverrides();
1047
- return true;
1048
- }
1049
- // This is an error condition. We can only get here if the new override set
1050
- // is in conflict with the existing.
1051
- log?.silly('Conflicting override sets', this.name);
1052
- return false;
1124
+ // It satisfies the original spec, not the overriding spec. We need to make
1125
+ // sure it doesn't use the overridden spec.
1126
+ // For example, we might have an ^8.0.0 rawSpec, and an override that makes
1127
+ // keySpec=8.23.0 and the override value spec=9.0.0.
1128
+ // If the node is 9.0.0, then it's okay because it's consistent with spec.
1129
+ // If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.
1130
+ // If the node is 8.23.0, then it's not okay because even though it's consistent
1131
+ // with the rawSpec, it's also consistent with the keySpec.
1132
+ // So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.
1133
+ return !depValid(node, this.overrides.keySpec, this.#safeAccept, this.#safeFrom);
1053
1134
  }
1135
+ }
1054
1136
 
1055
- // Patch adding updateOverridesEdgeInRemoved is based on
1056
- // https://github.com/npm/cli/pull/7025.
1057
- updateOverridesEdgeInRemoved(otherOverrideSet) {
1058
- // If this edge's overrides isn't equal to this node's overrides,
1059
- // then removing it won't change newOverrideSet later.
1060
- if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) {
1061
- return false;
1137
+ const pacote = require(pacotePath);
1138
+ const {
1139
+ LOOP_SENTINEL,
1140
+ NPM,
1141
+ NPM_REGISTRY_URL,
1142
+ SOCKET_CLI_FIX_PACKAGE_LOCK_FILE,
1143
+ SOCKET_CLI_UPDATE_OVERRIDES_IN_PACKAGE_LOCK_FILE,
1144
+ abortSignal
1145
+ } = constants;
1146
+ const formatter = new socketUrl.ColorOrMarkdown(false);
1147
+ function findBestPatchVersion(name, availableVersions, currentMajorVersion, vulnerableRange) {
1148
+ const manifestVersion = registry.getManifestData(NPM, name)?.version;
1149
+ // Filter versions that are within the current major version and are not in the vulnerable range
1150
+ const eligibleVersions = availableVersions.filter(version => {
1151
+ const isSameMajor = semver.major(version) === currentMajorVersion;
1152
+ const isNotVulnerable = !semver.satisfies(version, vulnerableRange);
1153
+ if (isSameMajor && isNotVulnerable) {
1154
+ return true;
1062
1155
  }
1063
- let newOverrideSet;
1064
- for (const edge of this.edgesIn) {
1065
- const {
1066
- overrides: edgeOverrides
1067
- } = edge;
1068
- if (newOverrideSet && edgeOverrides) {
1069
- newOverrideSet = findSpecificOverrideSet(edgeOverrides, newOverrideSet);
1070
- } else {
1071
- newOverrideSet = edgeOverrides;
1072
- }
1156
+ return !!manifestVersion;
1157
+ });
1158
+ if (eligibleVersions.length === 0) {
1159
+ return null;
1160
+ }
1161
+ // Use semver to find the max satisfying version.
1162
+ return semver.maxSatisfying(eligibleVersions, '*');
1163
+ }
1164
+ function findPackage(tree, packageName) {
1165
+ const queue = [{
1166
+ node: tree
1167
+ }];
1168
+ let sentinel = 0;
1169
+ while (queue.length) {
1170
+ if (sentinel++ === LOOP_SENTINEL) {
1171
+ throw new Error('Detected infinite loop in findPackage');
1073
1172
  }
1074
- if (this.overrides.isEqual(newOverrideSet)) {
1075
- return false;
1173
+ const {
1174
+ node: currentNode
1175
+ } = queue.pop();
1176
+ const node = currentNode.children.get(packageName);
1177
+ if (node) {
1178
+ // Found package.
1179
+ return node;
1076
1180
  }
1077
- this.overrides = newOverrideSet;
1078
- if (newOverrideSet) {
1079
- // Optimization: If there's any override set at all, then no non-extraneous
1080
- // node has an empty override set. So if we temporarily have no override set
1081
- // (for example, we removed all the edges in), there's no use updating all
1082
- // the edges out right now. Let's just wait until we have an actual override
1083
- // set later.
1084
- this.recalculateOutEdgesOverrides();
1181
+ const children = [...currentNode.children.values()];
1182
+ for (let i = children.length - 1; i >= 0; i -= 1) {
1183
+ queue.push({
1184
+ node: children[i]
1185
+ });
1085
1186
  }
1086
- return true;
1087
1187
  }
1188
+ return null;
1088
1189
  }
1089
-
1090
- // Implementation code not related to patch https://github.com/npm/cli/pull/7025
1091
- // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/override-set.js:
1092
- class SafeOverrideSet extends OverrideSet {
1093
- // Patch adding childrenAreEqual is based on
1094
- // https://github.com/npm/cli/pull/7025.
1095
- childrenAreEqual(otherOverrideSet) {
1096
- const queue = [[this, otherOverrideSet]];
1097
- let pos = 0;
1098
- let {
1099
- length: queueLength
1100
- } = queue;
1101
- while (pos < queueLength) {
1102
- if (pos === LOOP_SENTINEL) {
1103
- throw new Error('Detected infinite loop while comparing override sets');
1104
- }
1105
- const {
1106
- 0: currSet,
1107
- 1: currOtherSet
1108
- } = queue[pos++];
1109
- const {
1110
- children
1111
- } = currSet;
1112
- const {
1113
- children: otherChildren
1114
- } = currOtherSet;
1115
- if (children.size !== otherChildren.size) {
1116
- return false;
1117
- }
1118
- for (const key of children.keys()) {
1119
- if (!otherChildren.has(key)) {
1120
- return false;
1121
- }
1122
- const child = children.get(key);
1123
- const otherChild = otherChildren.get(key);
1124
- if (child.value !== otherChild.value) {
1125
- return false;
1126
- }
1127
- queue[queueLength++] = [child, otherChild];
1128
- }
1129
- }
1130
- return true;
1190
+ async function getPackagesAlerts(safeArb, pkgs, options) {
1191
+ let {
1192
+ length: remaining
1193
+ } = pkgs;
1194
+ const packageAlerts = [];
1195
+ if (!remaining) {
1196
+ return packageAlerts;
1131
1197
  }
1132
- getEdgeRule(edge) {
1133
- for (const rule of this.ruleset.values()) {
1134
- if (rule.name !== edge.name) {
1198
+ const {
1199
+ fixable,
1200
+ output
1201
+ } = {
1202
+ __proto__: null,
1203
+ ...options
1204
+ };
1205
+ const spinner$1 = output ? new spinner.Spinner({
1206
+ stream: output
1207
+ }) : undefined;
1208
+ const getText = spinner$1 ? () => `Looking up data for ${remaining} packages` : () => '';
1209
+ spinner$1?.start(getText());
1210
+ try {
1211
+ for await (const artifact of batchScan(pkgs.map(p => p.pkgid))) {
1212
+ if (!artifact.name || !artifact.version || !artifact.alerts?.length) {
1135
1213
  continue;
1136
1214
  }
1137
- // If keySpec is * we found our override.
1138
- if (rule.keySpec === '*') {
1139
- return rule;
1140
- }
1141
- // Patch replacing
1142
- // let spec = npa(`${edge.name}@${edge.spec}`)
1143
- // is based on https://github.com/npm/cli/pull/7025.
1144
- //
1145
- // We need to use the rawSpec here, because the spec has the overrides
1146
- // applied to it already.
1147
- let spec = npa(`${edge.name}@${edge.rawSpec}`);
1148
- if (spec.type === 'alias') {
1149
- spec = spec.subSpec;
1150
- }
1151
- if (spec.type === 'git') {
1152
- if (spec.gitRange && rule.keySpec && semver.intersects(spec.gitRange, rule.keySpec)) {
1153
- return rule;
1215
+ const {
1216
+ version
1217
+ } = artifact;
1218
+ const name = packages.resolvePackageName(artifact);
1219
+ const id = `${name}@${artifact.version}`;
1220
+ let blocked = false;
1221
+ let displayWarning = false;
1222
+ let alerts = [];
1223
+ for (const alert of artifact.alerts) {
1224
+ // eslint-disable-next-line no-await-in-loop
1225
+ const ux = await uxLookup({
1226
+ package: {
1227
+ name,
1228
+ version
1229
+ },
1230
+ alert: {
1231
+ type: alert.type
1232
+ }
1233
+ });
1234
+ if (ux.block) {
1235
+ blocked = true;
1154
1236
  }
1155
- continue;
1156
- }
1157
- if (spec.type === 'range' || spec.type === 'version') {
1158
- if (rule.keySpec && semver.intersects(spec.fetchSpec, rule.keySpec)) {
1159
- return rule;
1237
+ if (ux.display && output) {
1238
+ displayWarning = true;
1239
+ }
1240
+ if (ux.block || ux.display) {
1241
+ const isFixable = isAlertFixable(alert);
1242
+ if (!fixable || isFixable) {
1243
+ alerts.push({
1244
+ name,
1245
+ version,
1246
+ key: alert.key,
1247
+ type: alert.type,
1248
+ block: ux.block,
1249
+ raw: alert,
1250
+ fixable: isFixable
1251
+ });
1252
+ }
1253
+ // Lazily access constants.IPC.
1254
+ if (!fixable && !constants.IPC[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
1255
+ // Before we ask about problematic issues, check to see if they
1256
+ // already existed in the old version if they did, be quiet.
1257
+ const existing = pkgs.find(p => p.existing?.startsWith(`${name}@`))?.existing;
1258
+ if (existing) {
1259
+ const oldArtifact =
1260
+ // eslint-disable-next-line no-await-in-loop
1261
+ (await batchScan([existing]).next()).value;
1262
+ if (oldArtifact?.alerts?.length) {
1263
+ alerts = alerts.filter(({
1264
+ type
1265
+ }) => !oldArtifact.alerts?.find(a => a.type === type));
1266
+ }
1267
+ }
1268
+ }
1160
1269
  }
1161
- continue;
1162
1270
  }
1163
- // If we got this far, the spec type is one of tag, directory or file
1164
- // which means we have no real way to make version comparisons, so we
1165
- // just accept the override.
1166
- return rule;
1167
- }
1168
- return this;
1169
- }
1170
-
1171
- // Patch adding isEqual is based on
1172
- // https://github.com/npm/cli/pull/7025.
1173
- isEqual(otherOverrideSet) {
1174
- if (this === otherOverrideSet) {
1175
- return true;
1176
- }
1177
- if (!otherOverrideSet) {
1178
- return false;
1179
- }
1180
- if (this.key !== otherOverrideSet.key || this.value !== otherOverrideSet.value) {
1181
- return false;
1182
- }
1183
- if (!this.childrenAreEqual(otherOverrideSet)) {
1184
- return false;
1185
- }
1186
- if (!this.parent) {
1187
- return !otherOverrideSet.parent;
1271
+ if (!blocked) {
1272
+ const pkg = pkgs.find(p => p.pkgid === id);
1273
+ if (pkg) {
1274
+ await pacote.tarball.stream(id, stream => {
1275
+ stream.resume();
1276
+ return stream.promise();
1277
+ }, {
1278
+ ...safeArb[kCtorArgs][0]
1279
+ });
1280
+ }
1281
+ }
1282
+ if (displayWarning && spinner$1) {
1283
+ spinner$1.stop(`(socket) ${formatter.hyperlink(id, socketUrl.getSocketDevPackageOverviewUrl(NPM, name, version))} contains risks:`);
1284
+ }
1285
+ alerts.sort((a, b) => a.type < b.type ? -1 : 1);
1286
+ if (output) {
1287
+ const lines = new Set();
1288
+ const translations = getTranslations();
1289
+ for (const alert of alerts) {
1290
+ const attributes = [...(alert.fixable ? ['fixable'] : []), ...(alert.block ? [] : ['non-blocking'])];
1291
+ const maybeAttributes = attributes.length ? ` (${attributes.join('; ')})` : '';
1292
+ // Based data from { pageProps: { alertTypes } } of:
1293
+ // https://socket.dev/_next/data/94666139314b6437ee4491a0864e72b264547585/en-US.json
1294
+ const info = translations.alerts[alert.type];
1295
+ const title = info?.title ?? alert.type;
1296
+ const maybeDesc = info?.description ? ` - ${info.description}` : '';
1297
+ // TODO: emoji seems to mis-align terminals sometimes
1298
+ lines.add(` ${title}${maybeAttributes}${maybeDesc}\n`);
1299
+ }
1300
+ for (const line of lines) {
1301
+ output?.write(line);
1302
+ }
1303
+ }
1304
+ spinner$1?.start();
1305
+ remaining -= 1;
1306
+ if (spinner$1) {
1307
+ spinner$1.text = remaining > 0 ? getText() : '';
1308
+ }
1309
+ packageAlerts.push(...alerts);
1188
1310
  }
1189
- return this.parent.isEqual(otherOverrideSet.parent);
1311
+ } catch (e) {
1312
+ pathResolve.debugLog(e);
1313
+ } finally {
1314
+ spinner$1?.stop();
1190
1315
  }
1316
+ return packageAlerts;
1191
1317
  }
1192
-
1193
- // Implementation code not related to our custom behavior is based on
1194
- // https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/index.js:
1195
- class SafeArborist extends Arborist {
1196
- constructor(...ctorArgs) {
1197
- const mutedArguments = [{
1198
- ...ctorArgs[0],
1199
- audit: true,
1200
- dryRun: true,
1201
- ignoreScripts: true,
1202
- save: false,
1203
- saveBundle: false,
1204
- // progress: false,
1205
- fund: false
1206
- }, ctorArgs.slice(1)];
1207
- super(...mutedArguments);
1208
- this[kCtorArgs] = ctorArgs;
1209
- }
1210
- async [kRiskyReify](...args) {
1211
- // SafeArborist has suffered side effects and must be rebuilt from scratch.
1212
- const arb = new Arborist(...this[kCtorArgs]);
1213
- arb.idealTree = this.idealTree;
1214
- const ret = await arb.reify(...args);
1215
- Object.assign(this, arb);
1216
- return ret;
1318
+ let _translations;
1319
+ function getTranslations() {
1320
+ if (_translations === undefined) {
1321
+ _translations = require(
1322
+ // Lazily access constants.rootPath.
1323
+ path.join(constants.rootPath, 'translations.json'));
1217
1324
  }
1218
-
1219
- // @ts-ignore Incorrectly typed.
1220
- async reify(...args) {
1221
- const options = args[0] ? {
1222
- ...args[0]
1223
- } : {};
1224
- if (options.dryRun) {
1225
- return await this[kRiskyReify](...args);
1325
+ return _translations;
1326
+ }
1327
+ function packageAlertsToReport(alerts) {
1328
+ let report = null;
1329
+ for (const alert of alerts) {
1330
+ if (!isAlertFixableCve(alert.raw)) {
1331
+ continue;
1226
1332
  }
1227
- const old = {
1228
- ...options,
1229
- dryRun: false,
1230
- save: Boolean(options['save'] ?? true),
1231
- saveBundle: Boolean(options['saveBundle'] ?? false)
1232
- };
1233
- args[0] = options;
1234
- options.dryRun = true;
1235
- options['save'] = false;
1236
- options['saveBundle'] = false;
1237
- // TODO: Make this deal with any refactor to private fields by punching the
1238
- // class itself.
1239
- await super.reify(...args);
1240
- options.dryRun = old.dryRun;
1241
- options['save'] = old.save;
1242
- options['saveBundle'] = old.saveBundle;
1243
1333
  const {
1244
- diff
1245
- } = this;
1246
- // `diff` is `null` when `options.packageLockOnly`, --package-lock-only,
1247
- // is `true`.
1248
- const needInfoOn = diff ? walk(diff) : [];
1249
- if (needInfoOn.findIndex(c => c.repository_url === NPM_REGISTRY_URL) === -1) {
1250
- // Nothing to check, hmmm already installed or all private?
1251
- return await this[kRiskyReify](...args);
1334
+ name
1335
+ } = alert;
1336
+ if (!report) {
1337
+ report = {};
1252
1338
  }
1253
- const input = process.stdin;
1254
- const output = process.stderr;
1255
- let alerts;
1256
- const proceed = ENV[SOCKET_CLI_UPDATE_OVERRIDES_IN_PACKAGE_LOCK_FILE] || (await (async () => {
1257
- alerts = await getPackagesAlerts(this, needInfoOn, output);
1258
- if (!alerts.length || ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
1259
- return true;
1260
- }
1261
- return await prompts.confirm({
1262
- message: 'Accept risks of installing these packages?',
1263
- default: false
1264
- }, {
1265
- input,
1266
- output,
1267
- signal: abortSignal
1268
- });
1269
- })());
1270
- if (proceed) {
1271
- const fix = !!alerts?.length && (ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE] || (await prompts.confirm({
1272
- message: 'Try to fix alerts?',
1273
- default: true
1274
- }, {
1275
- input,
1276
- output,
1277
- signal: abortSignal
1278
- })));
1279
- if (fix) {
1280
- await updateAdvisoryDependencies(this, alerts);
1281
- }
1282
- return await this[kRiskyReify](...args);
1283
- } else {
1284
- throw new Error('Socket npm exiting due to risks');
1339
+ if (!report[name]) {
1340
+ report[name] = [];
1285
1341
  }
1342
+ const props = alert.raw?.props;
1343
+ report[name].push({
1344
+ id: -1,
1345
+ url: props?.url,
1346
+ title: props?.title,
1347
+ severity: alert.raw?.severity?.toLowerCase(),
1348
+ vulnerable_versions: props?.vulnerableVersionRange,
1349
+ cwe: props?.cwes,
1350
+ cvss: props?.csvs,
1351
+ name
1352
+ });
1286
1353
  }
1354
+ return report;
1287
1355
  }
1288
1356
  async function updateAdvisoryDependencies(arb, alerts) {
1289
1357
  const report = packageAlertsToReport(alerts);
@@ -1295,7 +1363,7 @@ async function updateAdvisoryDependencies(arb, alerts) {
1295
1363
  const tree = arb.idealTree;
1296
1364
  for (const name of Object.keys(report)) {
1297
1365
  const advisories = report[name];
1298
- const node = findPackageRecursively(tree, name);
1366
+ const node = findPackage(tree, name);
1299
1367
  if (!node) {
1300
1368
  // Package not found in the tree.
1301
1369
  continue;
@@ -1330,7 +1398,7 @@ async function updateAdvisoryDependencies(arb, alerts) {
1330
1398
  });
1331
1399
  node.package.version = targetVersion;
1332
1400
  // Update resolved and clear integrity for the new version.
1333
- node.resolved = `https://registry.npmjs.org/${name}/-/${name}-${targetVersion}.tgz`;
1401
+ node.resolved = `${NPM_REGISTRY_URL}/${name}/-/${name}-${targetVersion}.tgz`;
1334
1402
  if (node.integrity) {
1335
1403
  delete node.integrity;
1336
1404
  }
@@ -1366,49 +1434,142 @@ async function updateAdvisoryDependencies(arb, alerts) {
1366
1434
  }
1367
1435
  }
1368
1436
  }
1369
- function findPackageRecursively(tree, packageName) {
1370
- const queue = [{
1371
- node: tree
1372
- }];
1373
- let sentinel = 0;
1374
- while (queue.length) {
1375
- if (sentinel++ === LOOP_SENTINEL) {
1376
- throw new Error('Detected infinite loop in findPackageRecursively');
1377
- }
1378
- const {
1379
- node: currentNode
1380
- } = queue.pop();
1381
- const node = currentNode.children.get(packageName);
1382
- if (node) {
1383
- // Found package.
1384
- return node;
1437
+ async function reify(...args) {
1438
+ const needInfoOn = await walk(this.diff);
1439
+ if (!needInfoOn.length || needInfoOn.findIndex(c => c.repository_url === NPM_REGISTRY_URL) === -1) {
1440
+ // Nothing to check, hmmm already installed or all private?
1441
+ return await this[kRiskyReify](...args);
1442
+ }
1443
+ // Lazily access constants.IPC.
1444
+ const {
1445
+ [SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]: bypassConfirms,
1446
+ [SOCKET_CLI_UPDATE_OVERRIDES_IN_PACKAGE_LOCK_FILE]: bypassAlerts
1447
+ } = constants.IPC;
1448
+ const {
1449
+ stderr: output,
1450
+ stdin: input
1451
+ } = process;
1452
+ let alerts;
1453
+ const proceed = bypassAlerts || (await (async () => {
1454
+ alerts = await getPackagesAlerts(this, needInfoOn, {
1455
+ output
1456
+ });
1457
+ if (bypassConfirms || !alerts.length) {
1458
+ return true;
1385
1459
  }
1386
- const children = [...currentNode.children.values()];
1387
- for (let i = children.length - 1; i >= 0; i -= 1) {
1388
- queue.push({
1389
- node: children[i]
1390
- });
1460
+ return await prompts.confirm({
1461
+ message: 'Accept risks of installing these packages?',
1462
+ default: false
1463
+ }, {
1464
+ input,
1465
+ output,
1466
+ signal: abortSignal
1467
+ });
1468
+ })());
1469
+ if (proceed) {
1470
+ const fix = !!alerts?.length && bypassConfirms; /*||
1471
+ (await confirm(
1472
+ {
1473
+ message: 'Try to fix alerts?',
1474
+ default: true
1475
+ },
1476
+ {
1477
+ input,
1478
+ output,
1479
+ signal: abortSignal
1480
+ }
1481
+ ))*/
1482
+ if (fix) {
1483
+ let ret;
1484
+ const prev = new Set(alerts?.map(a => a.key));
1485
+ /* eslint-disable no-await-in-loop */
1486
+ while (alerts.length > 0) {
1487
+ await updateAdvisoryDependencies(this, alerts);
1488
+ ret = await this[kRiskyReify](...args);
1489
+ await this.loadActual();
1490
+ await this.buildIdealTree();
1491
+ alerts = await getPackagesAlerts(this, await walk(this.diff, {
1492
+ fix: true
1493
+ }), {
1494
+ fixable: true
1495
+ });
1496
+ alerts = alerts.filter(a => {
1497
+ const {
1498
+ key
1499
+ } = a;
1500
+ if (prev.has(key)) {
1501
+ return false;
1502
+ }
1503
+ prev.add(key);
1504
+ return true;
1505
+ });
1506
+ }
1507
+ /* eslint-enable no-await-in-loop */
1508
+ return ret;
1391
1509
  }
1510
+ return await this[kRiskyReify](...args);
1511
+ } else {
1512
+ throw new Error('Socket npm exiting due to risks');
1392
1513
  }
1393
- return null;
1394
1514
  }
1395
- function findBestPatchVersion(name, availableVersions, currentMajorVersion, vulnerableRange) {
1396
- const manifestVersion = registry.getManifestData(NPM, name)?.version;
1397
- // Filter versions that are within the current major version and are not in the vulnerable range
1398
- const eligibleVersions = availableVersions.filter(version => {
1399
- const isSameMajor = semver.major(version) === currentMajorVersion;
1400
- const isNotVulnerable = !semver.satisfies(version, vulnerableRange);
1401
- if (isSameMajor && isNotVulnerable) {
1402
- return true;
1515
+
1516
+ const Arborist = require(arboristClassPath);
1517
+ const kCtorArgs = Symbol('ctorArgs');
1518
+ const kRiskyReify = Symbol('riskyReify');
1519
+
1520
+ // Implementation code not related to our custom behavior is based on
1521
+ // https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/index.js:
1522
+ class SafeArborist extends Arborist {
1523
+ constructor(...ctorArgs) {
1524
+ super({
1525
+ ...ctorArgs[0],
1526
+ audit: true,
1527
+ dryRun: true,
1528
+ ignoreScripts: true,
1529
+ save: false,
1530
+ saveBundle: false,
1531
+ // progress: false,
1532
+ fund: false
1533
+ }, ...ctorArgs.slice(1));
1534
+ this[kCtorArgs] = ctorArgs;
1535
+ }
1536
+ async [kRiskyReify](...args) {
1537
+ // SafeArborist has suffered side effects and must be rebuilt from scratch.
1538
+ const arb = new Arborist(...this[kCtorArgs]);
1539
+ arb.idealTree = this.idealTree;
1540
+ const ret = await arb.reify(...args);
1541
+ Object.assign(this, arb);
1542
+ return ret;
1543
+ }
1544
+
1545
+ // @ts-ignore Incorrectly typed.
1546
+ async reify(...args) {
1547
+ const options = args[0] ? {
1548
+ ...args[0]
1549
+ } : {};
1550
+ if (options.dryRun) {
1551
+ return await this[kRiskyReify](...args);
1403
1552
  }
1404
- return !!manifestVersion;
1405
- });
1406
- if (eligibleVersions.length === 0) {
1407
- return null;
1553
+ const old = {
1554
+ ...options,
1555
+ dryRun: false,
1556
+ save: Boolean(options.save ?? true),
1557
+ saveBundle: Boolean(options.saveBundle ?? false)
1558
+ };
1559
+ args[0] = options;
1560
+ options.dryRun = true;
1561
+ options.save = false;
1562
+ options.saveBundle = false;
1563
+ // TODO: Make this deal with any refactor to private fields by punching the
1564
+ // class itself.
1565
+ await super.reify(...args);
1566
+ options.dryRun = old.dryRun;
1567
+ options.save = old.save;
1568
+ options.saveBundle = old.saveBundle;
1569
+ return await Reflect.apply(reify, this, args);
1408
1570
  }
1409
- // Use semver to find the max satisfying version.
1410
- return semver.maxSatisfying(eligibleVersions, '*');
1411
1571
  }
1572
+
1412
1573
  function installSafeArborist() {
1413
1574
  const cache = require.cache;
1414
1575
  cache[arboristClassPath] = {
@@ -1420,82 +1581,9 @@ function installSafeArborist() {
1420
1581
  cache[arboristNodeClassPath] = {
1421
1582
  exports: SafeNode
1422
1583
  };
1423
- cache[arboristOverrideSetClassPatch] = {
1584
+ cache[arboristOverrideSetClassPath] = {
1424
1585
  exports: SafeOverrideSet
1425
1586
  };
1426
1587
  }
1427
- void (async () => {
1428
- const {
1429
- orgs,
1430
- settings
1431
- } = await (async () => {
1432
- try {
1433
- const socketSdk = await sdk.setupSdk(pubToken);
1434
- const orgResult = await socketSdk.getOrganizations();
1435
- if (!orgResult.success) {
1436
- throw new Error(`Failed to fetch Socket organization info: ${orgResult.error.message}`);
1437
- }
1438
- const orgs = [];
1439
- for (const org of Object.values(orgResult.data.organizations)) {
1440
- if (org) {
1441
- orgs.push(org);
1442
- }
1443
- }
1444
- const result = await socketSdk.postSettings(orgs.map(org => ({
1445
- organization: org.id
1446
- })));
1447
- if (!result.success) {
1448
- throw new Error(`Failed to fetch API key settings: ${result.error.message}`);
1449
- }
1450
- return {
1451
- orgs,
1452
- settings: result.data
1453
- };
1454
- } catch (e) {
1455
- if (objects.isObject(e) && 'cause' in e) {
1456
- const {
1457
- cause
1458
- } = e;
1459
- if (sdk.isErrnoException(cause)) {
1460
- if (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED') {
1461
- throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
1462
- cause: e
1463
- });
1464
- }
1465
- }
1466
- }
1467
- throw e;
1468
- }
1469
- })();
1470
-
1471
- // Remove any organizations not being enforced.
1472
- const enforcedOrgs = sdk.getSetting('enforcedOrgs') ?? [];
1473
- for (const {
1474
- 0: i,
1475
- 1: org
1476
- } of orgs.entries()) {
1477
- if (!enforcedOrgs.includes(org.id)) {
1478
- settings.entries.splice(i, 1);
1479
- }
1480
- }
1481
- const socketYml = findSocketYmlSync();
1482
- if (socketYml) {
1483
- settings.entries.push({
1484
- start: socketYml.path,
1485
- settings: {
1486
- [socketYml.path]: {
1487
- deferTo: null,
1488
- // TODO: TypeScript complains about the type not matching. We should
1489
- // figure out why are providing
1490
- // issueRules: { [issueName: string]: boolean }
1491
- // but expecting
1492
- // issueRules: { [issueName: string]: { action: 'defer' | 'error' | 'ignore' | 'monitor' | 'warn' } }
1493
- issueRules: socketYml.parsed.issueRules
1494
- }
1495
- }
1496
- });
1497
- }
1498
- _uxLookup = createAlertUXLookup(settings);
1499
- })();
1500
1588
 
1501
1589
  installSafeArborist();