@socketsecurity/cli-with-sentry 0.14.56 → 0.14.58

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.
Files changed (39) hide show
  1. package/bin/cli.js +10 -10
  2. package/bin/npm-cli.js +1 -1
  3. package/bin/npx-cli.js +3 -1
  4. package/dist/constants.d.ts +20 -8
  5. package/dist/constants.js +54 -25
  6. package/dist/constants.js.map +1 -1
  7. package/dist/instrument-with-sentry.js +3 -3
  8. package/dist/instrument-with-sentry.js.map +1 -1
  9. package/dist/module-sync/artifact.d.ts +75 -0
  10. package/dist/module-sync/cli.js +1372 -1062
  11. package/dist/module-sync/cli.js.map +1 -1
  12. package/dist/module-sync/edge.d.ts +1 -1
  13. package/dist/module-sync/index.d.ts +5 -173
  14. package/dist/module-sync/node.d.ts +1 -1
  15. package/dist/module-sync/override-set.d.ts +37 -0
  16. package/dist/module-sync/shadow-bin.js +10 -8
  17. package/dist/module-sync/shadow-bin.js.map +1 -1
  18. package/dist/module-sync/{index.js → shadow-npm-inject.js} +1436 -1302
  19. package/dist/module-sync/shadow-npm-inject.js.map +1 -0
  20. package/dist/module-sync/{npm-paths.js → shadow-npm-paths.js} +4 -4
  21. package/dist/module-sync/shadow-npm-paths.js.map +1 -0
  22. package/dist/module-sync/socket-package-alert.d.ts +46 -0
  23. package/dist/module-sync/types.d.ts +11 -3
  24. package/dist/require/cli.js +1372 -1062
  25. package/dist/require/cli.js.map +1 -1
  26. package/dist/require/shadow-npm-inject.js +3 -0
  27. package/dist/require/shadow-npm-paths.js +3 -0
  28. package/package.json +14 -11
  29. package/dist/module-sync/index.js.map +0 -1
  30. package/dist/module-sync/npm-injection.js +0 -26
  31. package/dist/module-sync/npm-injection.js.map +0 -1
  32. package/dist/module-sync/npm-paths.js.map +0 -1
  33. package/dist/module-sync/proc-log.d.ts +0 -3
  34. package/dist/module-sync/reify.d.ts +0 -1020
  35. package/dist/require/index.js +0 -3
  36. package/dist/require/npm-injection.js +0 -3
  37. package/dist/require/npm-paths.js +0 -3
  38. /package/dist/module-sync/{npm-injection.d.ts → shadow-npm-inject.d.ts} +0 -0
  39. /package/dist/module-sync/{npm-paths.d.ts → shadow-npm-paths.d.ts} +0 -0
@@ -9,18 +9,18 @@ function _socketInterop(e) {
9
9
  return c ? e.default : e
10
10
  }
11
11
 
12
- var process = require('node:process');
13
- var path = require('node:path');
12
+ var process$1 = require('node:process');
13
+ var logger = require('@socketsecurity/registry/lib/logger');
14
+ var prompts = require('@socketsecurity/registry/lib/prompts');
15
+ var constants = require('./constants.js');
14
16
  var semver = _socketInterop(require('semver'));
15
17
  var packageurlJs = require('@socketregistry/packageurl-js');
16
18
  var registry = require('@socketsecurity/registry');
17
19
  var arrays = require('@socketsecurity/registry/lib/arrays');
20
+ var debug = require('@socketsecurity/registry/lib/debug');
18
21
  var objects = require('@socketsecurity/registry/lib/objects');
19
- var packages = require('@socketsecurity/registry/lib/packages');
20
- var prompts = require('@socketsecurity/registry/lib/prompts');
21
- var sorts = require('@socketsecurity/registry/lib/sorts');
22
- var spinner = require('@socketsecurity/registry/lib/spinner');
23
- var constants = require('./constants.js');
22
+ var shadowNpmPaths = require('./shadow-npm-paths.js');
23
+ var npa = _socketInterop(require('npm-package-arg'));
24
24
  var events = require('node:events');
25
25
  var https = require('node:https');
26
26
  var readline = require('node:readline');
@@ -30,16 +30,15 @@ var registryConstants = require('@socketsecurity/registry/lib/constants');
30
30
  var strings = require('@socketsecurity/registry/lib/strings');
31
31
  var sdk = require('@socketsecurity/sdk');
32
32
  var promises = require('node:timers/promises');
33
- var debug = require('@socketsecurity/registry/lib/debug');
34
33
  var fs = require('node:fs');
35
34
  var os = require('node:os');
35
+ var path = require('node:path');
36
36
  var config = require('@socketsecurity/config');
37
- var logger = require('@socketsecurity/registry/lib/logger');
37
+ var packages = require('@socketsecurity/registry/lib/packages');
38
+ var sorts = require('@socketsecurity/registry/lib/sorts');
38
39
  var terminalLink = _socketInterop(require('terminal-link'));
39
40
  var colors = _socketInterop(require('yoctocolors-cjs'));
40
41
  var indentString = require('@socketregistry/indent-string/index.cjs');
41
- var npmPaths = require('./npm-paths.js');
42
- var npa = _socketInterop(require('npm-package-arg'));
43
42
 
44
43
  const {
45
44
  kInternalsSymbol: kInternalsSymbol$1,
@@ -76,7 +75,7 @@ function isErrnoException(value) {
76
75
  }
77
76
 
78
77
  async function findUp(name, {
79
- cwd = process.cwd()
78
+ cwd = process$1.cwd()
80
79
  }) {
81
80
  let dir = path.resolve(cwd);
82
81
  const {
@@ -110,9 +109,9 @@ async function readFileUtf8(filepath, options) {
110
109
  encoding: 'utf8'
111
110
  });
112
111
  }
113
- function safeReadFile(...args) {
112
+ async function safeReadFile(...args) {
114
113
  try {
115
- return fs.promises.readFile(...args);
114
+ return await fs.promises.readFile(...args);
116
115
  } catch {}
117
116
  return undefined;
118
117
  }
@@ -155,7 +154,7 @@ function getSettingsPath() {
155
154
  const {
156
155
  WIN32
157
156
  } = constants;
158
- let dataHome = WIN32 ? process.env[LOCALAPPDATA] : process.env['XDG_DATA_HOME'];
157
+ let dataHome = WIN32 ? process$1.env[LOCALAPPDATA] : process$1.env['XDG_DATA_HOME'];
159
158
  if (!dataHome) {
160
159
  if (WIN32) {
161
160
  if (!_warnedSettingPathWin32Missing) {
@@ -163,7 +162,7 @@ function getSettingsPath() {
163
162
  logger.logger.warn(`Missing %${LOCALAPPDATA}%`);
164
163
  }
165
164
  } else {
166
- dataHome = path.join(os.homedir(), ...(process.platform === 'darwin' ? ['Library', 'Application Support'] : ['.local', 'share']));
165
+ dataHome = path.join(os.homedir(), ...(process$1.platform === 'darwin' ? ['Library', 'Application Support'] : ['.local', 'share']));
167
166
  }
168
167
  }
169
168
  _settingsPath = dataHome ? path.join(dataHome, 'socket/settings') : undefined;
@@ -179,7 +178,7 @@ function normalizeSettingsKey(key) {
179
178
  }
180
179
  function findSocketYmlSync() {
181
180
  let prevDir = null;
182
- let dir = process.cwd();
181
+ let dir = process$1.cwd();
183
182
  while (dir !== prevDir) {
184
183
  let ymlPath = path.join(dir, 'socket.yml');
185
184
  let yml = safeReadFileSync(ymlPath, 'utf8');
@@ -211,7 +210,7 @@ function updateSetting(key, value) {
211
210
  settings[normalizeSettingsKey(key)] = value;
212
211
  if (!pendingSave) {
213
212
  pendingSave = true;
214
- process.nextTick(() => {
213
+ process$1.nextTick(() => {
215
214
  pendingSave = false;
216
215
  const settingsPath = getSettingsPath();
217
216
  if (settingsPath) {
@@ -227,13 +226,13 @@ const {
227
226
 
228
227
  // The API server that should be used for operations.
229
228
  function getDefaultApiBaseUrl() {
230
- const baseUrl = process.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl');
229
+ const baseUrl = process$1.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl');
231
230
  return strings.isNonEmptyString(baseUrl) ? baseUrl : undefined;
232
231
  }
233
232
 
234
233
  // The API server that should be used for operations.
235
234
  function getDefaultHttpProxy() {
236
- const apiProxy = process.env['SOCKET_SECURITY_API_PROXY'] || getSetting('apiProxy');
235
+ const apiProxy = process$1.env['SOCKET_SECURITY_API_PROXY'] || getSetting('apiProxy');
237
236
  return strings.isNonEmptyString(apiProxy) ? apiProxy : undefined;
238
237
  }
239
238
 
@@ -244,10 +243,10 @@ function getDefaultToken() {
244
243
  if (constants.ENV[SOCKET_CLI_NO_API_TOKEN]) {
245
244
  _defaultToken = undefined;
246
245
  } else {
247
- const key = process.env['SOCKET_SECURITY_API_TOKEN'] ||
246
+ const key = process$1.env['SOCKET_SECURITY_API_TOKEN'] ||
248
247
  // Keep 'SOCKET_SECURITY_API_KEY' as an alias of 'SOCKET_SECURITY_API_TOKEN'.
249
248
  // TODO: Remove 'SOCKET_SECURITY_API_KEY' alias.
250
- process.env['SOCKET_SECURITY_API_KEY'] || getSetting('apiToken') || _defaultToken;
249
+ process$1.env['SOCKET_SECURITY_API_KEY'] || getSetting('apiToken') || _defaultToken;
251
250
  _defaultToken = strings.isNonEmptyString(key) ? key : undefined;
252
251
  }
253
252
  return _defaultToken;
@@ -280,1187 +279,1443 @@ async function setupSdk(apiToken = getDefaultToken(), apiBaseUrl = getDefaultApi
280
279
  });
281
280
  }
282
281
 
282
+ let DiffAction = /*#__PURE__*/function (DiffAction) {
283
+ DiffAction["add"] = "ADD";
284
+ DiffAction["change"] = "CHANGE";
285
+ DiffAction["remove"] = "REMOVE";
286
+ return DiffAction;
287
+ }({});
288
+
289
+ const depValid = require(shadowNpmPaths.getArboristDepValidPath());
290
+
283
291
  const {
284
- LOOP_SENTINEL: LOOP_SENTINEL$1,
285
- NPM_REGISTRY_URL: NPM_REGISTRY_URL$1
292
+ UNDEFINED_TOKEN
286
293
  } = constants;
287
- function getUrlOrigin(input) {
288
- try {
289
- return URL.parse(input)?.origin ?? '';
290
- } catch {}
291
- return '';
294
+ function tryRequire(req, ...ids) {
295
+ for (const data of ids) {
296
+ let id;
297
+ let transformer;
298
+ if (Array.isArray(data)) {
299
+ id = data[0];
300
+ transformer = data[1];
301
+ } else {
302
+ id = data;
303
+ transformer = mod => mod;
304
+ }
305
+ try {
306
+ // Check that the transformed value isn't `undefined` because older
307
+ // versions of packages like 'proc-log' may not export a `log` method.
308
+ const exported = transformer(req(id));
309
+ if (exported !== undefined) {
310
+ return exported;
311
+ }
312
+ } catch {}
313
+ }
314
+ return undefined;
292
315
  }
293
- function getPackagesToQueryFromDiff(diff_, options) {
294
- const {
295
- includeUnchanged = false,
296
- includeUnknownOrigin = false
297
- } = {
298
- __proto__: null,
299
- ...options
300
- };
301
- const details = [];
302
- // `diff_` is `null` when `npm install --package-lock-only` is passed.
303
- if (!diff_) {
304
- return details;
316
+ let _log = UNDEFINED_TOKEN;
317
+ function getLogger() {
318
+ if (_log === UNDEFINED_TOKEN) {
319
+ _log = tryRequire(shadowNpmPaths.getNpmRequire(), ['proc-log/lib/index.js',
320
+ // The proc-log DefinitelyTyped definition is incorrect. The type definition
321
+ // is really that of its export log.
322
+ mod => mod.log], 'npmlog/lib/log.js');
305
323
  }
306
- const queue = [...diff_.children];
307
- let pos = 0;
308
- let {
309
- length: queueLength
310
- } = queue;
311
- while (pos < queueLength) {
312
- if (pos === LOOP_SENTINEL$1) {
313
- throw new Error('Detected infinite loop while walking Arborist diff');
324
+ return _log;
325
+ }
326
+
327
+ const OverrideSet = require(shadowNpmPaths.getArboristOverrideSetClassPath());
328
+
329
+ // Implementation code not related to patch https://github.com/npm/cli/pull/8089
330
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/override-set.js:
331
+ class SafeOverrideSet extends OverrideSet {
332
+ // Patch adding doOverrideSetsConflict is based on
333
+ // https://github.com/npm/cli/pull/8089.
334
+ static doOverrideSetsConflict(first, second) {
335
+ // If override sets contain one another then we can try to use the more
336
+ // specific one. If neither one is more specific, then we consider them to
337
+ // be in conflict.
338
+ return this.findSpecificOverrideSet(first, second) === undefined;
339
+ }
340
+
341
+ // Patch adding findSpecificOverrideSet is based on
342
+ // https://github.com/npm/cli/pull/8089.
343
+ static findSpecificOverrideSet(first, second) {
344
+ for (let overrideSet = second; overrideSet; overrideSet = overrideSet.parent) {
345
+ if (overrideSet.isEqual(first)) {
346
+ return second;
347
+ }
314
348
  }
315
- const diff = queue[pos++];
316
- const {
317
- action
318
- } = diff;
319
- if (action) {
320
- // The `pkgNode`, i.e. the `ideal` node, will be `undefined` if the diff
321
- // action is 'REMOVE'
322
- // The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff
323
- // action is 'ADD'.
324
- const {
325
- actual: oldNode,
326
- ideal: pkgNode
327
- } = diff;
328
- let existing;
329
- let keep = false;
330
- if (action === 'CHANGE') {
331
- if (pkgNode?.package.version !== oldNode?.package.version) {
332
- keep = true;
333
- if (oldNode?.package.name && oldNode.package.name === pkgNode?.package.name) {
334
- existing = oldNode;
335
- }
349
+ for (let overrideSet = first; overrideSet; overrideSet = overrideSet.parent) {
350
+ if (overrideSet.isEqual(second)) {
351
+ return first;
352
+ }
353
+ }
354
+ // The override sets are incomparable. Neither one contains the other.
355
+ const log = getLogger();
356
+ log?.silly('Conflicting override sets', first, second);
357
+ return undefined;
358
+ }
359
+
360
+ // Patch adding childrenAreEqual is based on
361
+ // https://github.com/npm/cli/pull/8089.
362
+ childrenAreEqual(otherOverrideSet) {
363
+ if (this.children.size !== otherOverrideSet.children.size) {
364
+ return false;
365
+ }
366
+ for (const {
367
+ 0: key,
368
+ 1: childOverrideSet
369
+ } of this.children) {
370
+ const otherChildOverrideSet = otherOverrideSet.children.get(key);
371
+ if (!otherChildOverrideSet) {
372
+ return false;
373
+ }
374
+ if (childOverrideSet.value !== otherChildOverrideSet.value) {
375
+ return false;
376
+ }
377
+ if (!childOverrideSet.childrenAreEqual(otherChildOverrideSet)) {
378
+ return false;
379
+ }
380
+ }
381
+ return true;
382
+ }
383
+ getEdgeRule(edge) {
384
+ for (const rule of this.ruleset.values()) {
385
+ if (rule.name !== edge.name) {
386
+ continue;
387
+ }
388
+ // If keySpec is * we found our override.
389
+ if (rule.keySpec === '*') {
390
+ return rule;
391
+ }
392
+ // Patch replacing
393
+ // let spec = npa(`${edge.name}@${edge.spec}`)
394
+ // is based on https://github.com/npm/cli/pull/8089.
395
+ //
396
+ // We need to use the rawSpec here, because the spec has the overrides
397
+ // applied to it already. The rawSpec can be undefined, so we need to use
398
+ // the fallback value of spec if it is.
399
+ let spec = npa(`${edge.name}@${edge.rawSpec || edge.spec}`);
400
+ if (spec.type === 'alias') {
401
+ spec = spec.subSpec;
402
+ }
403
+ if (spec.type === 'git') {
404
+ if (spec.gitRange && semver.intersects(spec.gitRange, rule.keySpec)) {
405
+ return rule;
336
406
  }
337
- } else {
338
- keep = action !== 'REMOVE';
407
+ continue;
339
408
  }
340
- if (keep && pkgNode?.resolved && (!oldNode || oldNode.resolved)) {
341
- if (includeUnknownOrigin || getUrlOrigin(pkgNode.resolved) === NPM_REGISTRY_URL$1) {
342
- details.push({
343
- node: pkgNode,
344
- existing
345
- });
409
+ if (spec.type === 'range' || spec.type === 'version') {
410
+ if (semver.intersects(spec.fetchSpec, rule.keySpec)) {
411
+ return rule;
346
412
  }
413
+ continue;
347
414
  }
415
+ // If we got this far, the spec type is one of tag, directory or file
416
+ // which means we have no real way to make version comparisons, so we
417
+ // just accept the override.
418
+ return rule;
348
419
  }
349
- for (const child of diff.children) {
350
- queue[queueLength++] = child;
351
- }
420
+ return this;
352
421
  }
353
- if (includeUnchanged) {
354
- const {
355
- unchanged
356
- } = diff_;
357
- for (let i = 0, {
358
- length
359
- } = unchanged; i < length; i += 1) {
360
- const pkgNode = unchanged[i];
361
- if (includeUnknownOrigin || getUrlOrigin(pkgNode.resolved) === NPM_REGISTRY_URL$1) {
362
- details.push({
363
- node: pkgNode,
364
- existing: pkgNode
365
- });
366
- }
422
+
423
+ // Patch adding isEqual is based on
424
+ // https://github.com/npm/cli/pull/8089.
425
+ isEqual(otherOverrideSet) {
426
+ if (this === otherOverrideSet) {
427
+ return true;
428
+ }
429
+ if (!otherOverrideSet) {
430
+ return false;
431
+ }
432
+ if (this.key !== otherOverrideSet.key || this.value !== otherOverrideSet.value) {
433
+ return false;
434
+ }
435
+ if (!this.childrenAreEqual(otherOverrideSet)) {
436
+ return false;
437
+ }
438
+ if (!this.parent) {
439
+ return !otherOverrideSet.parent;
367
440
  }
441
+ return this.parent.isEqual(otherOverrideSet.parent);
368
442
  }
369
- return details;
370
443
  }
371
444
 
372
- const {
373
- ALERT_TYPE_CRITICAL_CVE,
374
- ALERT_TYPE_CVE,
375
- ALERT_TYPE_MEDIUM_CVE,
376
- ALERT_TYPE_MILD_CVE,
377
- ALERT_TYPE_SOCKET_UPGRADE_AVAILABLE,
378
- CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER: CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER$1,
379
- CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE,
380
- abortSignal: abortSignal$1
381
- } = constants;
382
- async function* createBatchGenerator(chunk) {
383
- // Adds the first 'abort' listener to abortSignal.
384
- const req = https
385
- // Lazily access constants.BATCH_PURL_ENDPOINT.
386
- .request(constants.BATCH_PURL_ENDPOINT, {
387
- method: 'POST',
388
- headers: {
389
- Authorization: `Basic ${btoa(`${getPublicToken()}:`)}`
445
+ const Node = require(shadowNpmPaths.getArboristNodeClassPath());
446
+
447
+ // Implementation code not related to patch https://github.com/npm/cli/pull/8089
448
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/node.js:
449
+ class SafeNode extends Node {
450
+ // Return true if it's safe to remove this node, because anything that is
451
+ // depending on it would be fine with the thing that they would resolve to if
452
+ // it was removed, or nothing is depending on it in the first place.
453
+ canDedupe(preferDedupe = false) {
454
+ // Not allowed to mess with shrinkwraps or bundles.
455
+ if (this.inDepBundle || this.inShrinkwrap) {
456
+ return false;
390
457
  }
391
- // TODO: Fix to not abort process on network abort.
392
- // signal: abortSignal
393
- }).end(JSON.stringify({
394
- components: chunk.map(id => ({
395
- purl: `pkg:npm/${id}`
396
- }))
397
- }));
398
- // Adds the second 'abort' listener to abortSignal.
399
- const {
400
- 0: res
401
- } = await events.once(req, 'response', {
402
- signal: abortSignal$1
403
- });
404
- const ok = res.statusCode >= 200 && res.statusCode <= 299;
405
- if (!ok) {
406
- throw new Error(`Socket API Error: ${res.statusCode}`);
407
- }
408
- const rli = readline.createInterface({
409
- input: res,
410
- crlfDelay: Infinity,
411
- signal: abortSignal$1
412
- });
413
- for await (const line of rli) {
414
- yield JSON.parse(line);
415
- }
416
- }
417
- async function* batchScan(pkgIds, concurrencyLimit = 50) {
418
- // The createBatchGenerator method will add 2 'abort' event listeners to
419
- // abortSignal so we multiply the concurrencyLimit by 2.
420
- const neededMaxListeners = concurrencyLimit * 2;
421
- // Increase abortSignal max listeners count to avoid Node's MaxListenersExceededWarning.
422
- const oldAbortSignalMaxListeners = events.getMaxListeners(abortSignal$1);
423
- let abortSignalMaxListeners = oldAbortSignalMaxListeners;
424
- if (oldAbortSignalMaxListeners < neededMaxListeners) {
425
- abortSignalMaxListeners = oldAbortSignalMaxListeners + neededMaxListeners;
426
- events.setMaxListeners(abortSignalMaxListeners, abortSignal$1);
427
- }
428
- const {
429
- length: pkgIdsCount
430
- } = pkgIds;
431
- const running = [];
432
- let index = 0;
433
- const enqueueGen = () => {
434
- if (index >= pkgIdsCount) {
435
- // No more work to do.
436
- return;
458
+ // It's a top level pkg, or a dep of one.
459
+ if (!this.resolveParent?.resolveParent) {
460
+ return false;
437
461
  }
438
- const chunk = pkgIds.slice(index, index + 25);
439
- index += 25;
440
- const generator = createBatchGenerator(chunk);
441
- continueGen(generator);
442
- };
443
- const continueGen = generator => {
444
- let resolveFn;
445
- running.push({
446
- generator,
447
- promise: new Promise(resolve => resolveFn = resolve)
448
- });
449
- void generator.next().then(res => resolveFn({
450
- generator,
451
- iteratorResult: res
452
- }));
453
- };
454
- // Start initial batch of generators.
455
- while (running.length < concurrencyLimit && index < pkgIdsCount) {
456
- enqueueGen();
457
- }
458
- while (running.length > 0) {
459
- // eslint-disable-next-line no-await-in-loop
460
- const {
461
- generator,
462
- iteratorResult
463
- } = await Promise.race(running.map(entry => entry.promise));
464
- // Remove generator.
465
- running.splice(running.findIndex(entry => entry.generator === generator), 1);
466
- if (iteratorResult.done) {
467
- // Start a new generator if available.
468
- enqueueGen();
469
- } else {
470
- yield iteratorResult.value;
471
- // Keep fetching values from this generator.
472
- continueGen(generator);
462
+ // No one wants it, remove it.
463
+ if (this.edgesIn.size === 0) {
464
+ return true;
473
465
  }
474
- }
475
- // Reset abortSignal max listeners count.
476
- if (abortSignalMaxListeners > oldAbortSignalMaxListeners) {
477
- events.setMaxListeners(oldAbortSignalMaxListeners, abortSignal$1);
478
- }
479
- }
480
- function isArtifactAlertCveFixable(alert) {
481
- const {
482
- type
483
- } = alert;
484
- return (type === ALERT_TYPE_CVE || type === ALERT_TYPE_MEDIUM_CVE || type === ALERT_TYPE_MILD_CVE || type === ALERT_TYPE_CRITICAL_CVE) && !!alert.props?.[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER$1] && !!alert.props?.[CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE];
485
- }
486
- function isArtifactAlertUpgradeFixable(alert) {
487
- return alert.type === ALERT_TYPE_SOCKET_UPGRADE_AVAILABLE;
488
- }
489
-
490
- const {
491
- abortSignal
492
- } = constants;
493
- const ERROR_UX = {
494
- block: true,
495
- display: true
496
- };
497
- const IGNORE_UX = {
498
- block: false,
499
- display: false
500
- };
501
- const WARN_UX = {
502
- block: false,
503
- display: true
504
- };
505
-
506
- // Iterates over all entries with ordered issue rule for deferral. Iterates over
507
- // all issue rules and finds the first defined value that does not defer otherwise
508
- // uses the defaultValue. Takes the value and converts into a UX workflow.
509
- function resolveAlertRuleUX(orderedRulesCollection, defaultValue) {
510
- if (defaultValue === true || defaultValue === null || defaultValue === undefined) {
511
- defaultValue = {
512
- action: 'error'
513
- };
514
- } else if (defaultValue === false) {
515
- defaultValue = {
516
- action: 'ignore'
517
- };
518
- }
519
- let block = false;
520
- let display = false;
521
- let needDefault = true;
522
- iterate_entries: for (const rules of orderedRulesCollection) {
523
- for (const rule of rules) {
524
- if (ruleValueDoesNotDefer(rule)) {
525
- needDefault = false;
526
- const narrowingFilter = uxForDefinedNonDeferValue(rule);
527
- block = block || narrowingFilter.block;
528
- display = display || narrowingFilter.display;
529
- continue iterate_entries;
530
- }
466
+ const other = this.resolveParent.resolveParent.resolve(this.name);
467
+ // Nothing else, need this one.
468
+ if (!other) {
469
+ return false;
531
470
  }
532
- const narrowingFilter = uxForDefinedNonDeferValue(defaultValue);
533
- block = block || narrowingFilter.block;
534
- display = display || narrowingFilter.display;
535
- }
536
- if (needDefault) {
537
- const narrowingFilter = uxForDefinedNonDeferValue(defaultValue);
538
- block = block || narrowingFilter.block;
539
- display = display || narrowingFilter.display;
540
- }
541
- return {
542
- block,
543
- display
544
- };
545
- }
546
-
547
- // Negative form because it is narrowing the type.
548
- function ruleValueDoesNotDefer(rule) {
549
- if (rule === undefined) {
550
- return false;
551
- }
552
- if (objects.isObject(rule)) {
553
- const {
554
- action
555
- } = rule;
556
- if (action === undefined || action === 'defer') {
471
+ // If it's the same thing, then always fine to remove.
472
+ if (other.matches(this)) {
473
+ return true;
474
+ }
475
+ // If the other thing can't replace this, then skip it.
476
+ if (!other.canReplace(this)) {
557
477
  return false;
558
478
  }
479
+ // Patch replacing
480
+ // if (preferDedupe || semver.gte(other.version, this.version)) {
481
+ // return true
482
+ // }
483
+ // is based on https://github.com/npm/cli/pull/8089.
484
+ //
485
+ // If we prefer dedupe, or if the version is equal, take the other.
486
+ if (preferDedupe || semver.eq(other.version, this.version)) {
487
+ return true;
488
+ }
489
+ // If our current version isn't the result of an override, then prefer to
490
+ // take the greater version.
491
+ if (!this.overridden && semver.gt(other.version, this.version)) {
492
+ return true;
493
+ }
494
+ return false;
559
495
  }
560
- return true;
561
- }
562
496
 
563
- // Handles booleans for backwards compatibility.
564
- function uxForDefinedNonDeferValue(ruleValue) {
565
- if (typeof ruleValue === 'boolean') {
566
- return ruleValue ? ERROR_UX : IGNORE_UX;
567
- }
568
- const {
569
- action
570
- } = ruleValue;
571
- if (action === 'warn') {
572
- return WARN_UX;
573
- } else if (action === 'ignore') {
574
- return IGNORE_UX;
575
- }
576
- return ERROR_UX;
577
- }
578
- function createAlertUXLookup(settings) {
579
- const cachedUX = new Map();
580
- return context => {
581
- const {
582
- type
583
- } = context.alert;
584
- let ux = cachedUX.get(type);
585
- if (ux) {
586
- return ux;
497
+ // Is it safe to replace one node with another? check the edges to
498
+ // make sure no one will get upset. Note that the node might end up
499
+ // having its own unmet dependencies, if the new node has new deps.
500
+ // Note that there are cases where Arborist will opt to insert a node
501
+ // into the tree even though this function returns false! This is
502
+ // necessary when a root dependency is added or updated, or when a
503
+ // root dependency brings peer deps along with it. In that case, we
504
+ // will go ahead and create the invalid state, and then try to resolve
505
+ // it with more tree construction, because it's a user request.
506
+ canReplaceWith(node, ignorePeers) {
507
+ if (this.name !== node.name || this.packageName !== node.packageName) {
508
+ return false;
587
509
  }
588
- const orderedRulesCollection = [];
589
- for (const settingsEntry of settings.entries) {
590
- const orderedRules = [];
591
- let target = settingsEntry.start;
592
- while (target !== null) {
593
- const resolvedTarget = settingsEntry.settings[target];
594
- if (!resolvedTarget) {
595
- break;
510
+ // Patch replacing
511
+ // if (node.overrides !== this.overrides) {
512
+ // return false
513
+ // }
514
+ // is based on https://github.com/npm/cli/pull/8089.
515
+ //
516
+ // If this node has no dependencies, then it's irrelevant to check the
517
+ // override rules of the replacement node.
518
+ if (this.edgesOut.size) {
519
+ // XXX need to check for two root nodes?
520
+ if (node.overrides) {
521
+ if (!node.overrides.isEqual(this.overrides)) {
522
+ return false;
596
523
  }
597
- const issueRuleValue = resolvedTarget.issueRules?.[type];
598
- if (typeof issueRuleValue !== 'undefined') {
599
- orderedRules.push(issueRuleValue);
524
+ } else {
525
+ if (this.overrides) {
526
+ return false;
600
527
  }
601
- target = resolvedTarget.deferTo ?? null;
602
528
  }
603
- orderedRulesCollection.push(orderedRules);
604
529
  }
605
- const defaultValue = settings.defaults.issueRules[type];
606
- let resolvedDefaultValue = {
607
- action: 'error'
608
- };
609
- if (defaultValue === false) {
610
- resolvedDefaultValue = {
611
- action: 'ignore'
612
- };
613
- } else if (defaultValue && defaultValue !== true) {
614
- resolvedDefaultValue = {
615
- action: defaultValue.action ?? 'error'
616
- };
530
+ // To satisfy the patch we ensure `node.overrides === this.overrides`
531
+ // so that the condition we want to replace,
532
+ // if (this.overrides !== node.overrides) {
533
+ // , is not hit.`
534
+ const oldOverrideSet = this.overrides;
535
+ let result = true;
536
+ if (oldOverrideSet !== node.overrides) {
537
+ this.overrides = node.overrides;
617
538
  }
618
- ux = resolveAlertRuleUX(orderedRulesCollection, resolvedDefaultValue);
619
- cachedUX.set(type, ux);
620
- return ux;
621
- };
622
- }
623
- let _uxLookup;
624
- async function uxLookup(settings) {
625
- while (_uxLookup === undefined) {
626
- // eslint-disable-next-line no-await-in-loop
627
- await promises.setTimeout(1, {
628
- signal: abortSignal
629
- });
630
- }
631
- return _uxLookup(settings);
632
- }
633
-
634
- // Start initializing the AlertUxLookupResult immediately.
635
- void (async () => {
636
- const {
637
- orgs,
638
- settings
639
- } = await (async () => {
640
539
  try {
641
- const sockSdk = await setupSdk(getPublicToken());
642
- const orgResult = await sockSdk.getOrganizations();
643
- if (!orgResult.success) {
644
- throw new Error(`Failed to fetch Socket organization info: ${orgResult.error.message}`);
645
- }
646
- const orgs = [];
647
- for (const org of Object.values(orgResult.data.organizations)) {
648
- if (org) {
649
- orgs.push(org);
650
- }
651
- }
652
- const result = await sockSdk.postSettings(orgs.map(org => ({
653
- organization: org.id
654
- })));
655
- if (!result.success) {
656
- throw new Error(`Failed to fetch API key settings: ${result.error.message}`);
657
- }
658
- return {
659
- orgs,
660
- settings: result.data
661
- };
540
+ result = super.canReplaceWith(node, ignorePeers);
541
+ this.overrides = oldOverrideSet;
662
542
  } catch (e) {
663
- const cause = objects.isObject(e) && 'cause' in e ? e['cause'] : undefined;
664
- if (isErrnoException(cause) && (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED')) {
665
- throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
666
- cause: e
667
- });
668
- }
543
+ this.overrides = oldOverrideSet;
669
544
  throw e;
670
545
  }
671
- })();
672
-
673
- // Remove any organizations not being enforced.
674
- const enforcedOrgs = getSetting('enforcedOrgs') ?? [];
675
- for (const {
676
- 0: i,
677
- 1: org
678
- } of orgs.entries()) {
679
- if (!enforcedOrgs.includes(org.id)) {
680
- settings.entries.splice(i, 1);
681
- }
682
- }
683
- const socketYml = findSocketYmlSync();
684
- if (socketYml) {
685
- settings.entries.push({
686
- start: socketYml.path,
687
- settings: {
688
- [socketYml.path]: {
689
- deferTo: null,
690
- // TODO: TypeScript complains about the type not matching. We should
691
- // figure out why are providing
692
- // issueRules: { [issueName: string]: boolean }
693
- // but expecting
694
- // issueRules: { [issueName: string]: { action: 'defer' | 'error' | 'ignore' | 'monitor' | 'warn' } }
695
- issueRules: socketYml.parsed.issueRules
696
- }
697
- }
698
- });
546
+ return result;
699
547
  }
700
- _uxLookup = createAlertUXLookup(settings);
701
- })();
702
548
 
703
- const markdownLogSymbols = Object.freeze({
704
- __proto__: null,
705
- info: ':information_source:',
706
- error: ':stop_sign:',
707
- success: ':white_check_mark:',
708
- warning: ':warning:'
709
- });
710
- class ColorOrMarkdown {
711
- constructor(useMarkdown) {
712
- this.useMarkdown = !!useMarkdown;
713
- }
714
- bold(text) {
715
- return this.useMarkdown ? `**${text}**` : colors.bold(`${text}`);
716
- }
717
- header(text, level = 1) {
718
- return this.useMarkdown ? `\n${''.padStart(level, '#')} ${text}\n` : colors.underline(`\n${level === 1 ? colors.bold(text) : text}\n`);
719
- }
720
- hyperlink(text, url, {
721
- fallback = true,
722
- fallbackToUrl
723
- } = {}) {
724
- if (url) {
725
- return this.useMarkdown ? `[${text}](${url})` : terminalLink(text, url, {
726
- fallback: fallbackToUrl ? (_text, url) => url : fallback
727
- });
549
+ // Patch adding deleteEdgeIn is based on https://github.com/npm/cli/pull/8089.
550
+ deleteEdgeIn(edge) {
551
+ this.edgesIn.delete(edge);
552
+ const {
553
+ overrides
554
+ } = edge;
555
+ if (overrides) {
556
+ this.updateOverridesEdgeInRemoved(overrides);
728
557
  }
729
- return text;
730
- }
731
- indent(...args) {
732
- return indentString(...args);
733
- }
734
- italic(text) {
735
- return this.useMarkdown ? `_${text}_` : colors.italic(`${text}`);
736
- }
737
- json(value) {
738
- return this.useMarkdown ? '```json\n' + JSON.stringify(value) + '\n```' : JSON.stringify(value);
739
- }
740
- list(items) {
741
- const indentedContent = items.map(item => this.indent(item).trimStart());
742
- return this.useMarkdown ? `* ${indentedContent.join('\n* ')}\n` : `${indentedContent.join('\n')}\n`;
743
558
  }
744
- get logSymbols() {
745
- return this.useMarkdown ? markdownLogSymbols : logger.Logger.LOG_SYMBOLS;
559
+ addEdgeIn(edge) {
560
+ // Patch replacing
561
+ // if (edge.overrides) {
562
+ // this.overrides = edge.overrides
563
+ // }
564
+ // is based on https://github.com/npm/cli/pull/8089.
565
+ //
566
+ // We need to handle the case where the new edge in has an overrides field
567
+ // which is different from the current value.
568
+ if (!this.overrides || !this.overrides.isEqual(edge.overrides)) {
569
+ this.updateOverridesEdgeInAdded(edge.overrides);
570
+ }
571
+ this.edgesIn.add(edge);
572
+ // Try to get metadata from the yarn.lock file.
573
+ this.root.meta?.addEdge(edge);
746
574
  }
747
- }
748
-
749
- function getSocketDevAlertUrl(alertType) {
750
- return `https://socket.dev/alerts/${alertType}`;
751
- }
752
- function getSocketDevPackageOverviewUrl(eco, name, version) {
753
- return `https://socket.dev/${eco}/package/${name}${version ? `/overview/${version}` : ''}`;
754
- }
755
-
756
- const depValid = require(npmPaths.getArboristDepValidPath());
757
575
 
758
- const {
759
- UNDEFINED_TOKEN
760
- } = constants;
761
- function tryRequire(req, ...ids) {
762
- for (const data of ids) {
763
- let id;
764
- let transformer;
765
- if (Array.isArray(data)) {
766
- id = data[0];
767
- transformer = data[1];
768
- } else {
769
- id = data;
770
- transformer = mod => mod;
576
+ // @ts-ignore: Incorrectly typed as a property instead of an accessor.
577
+ get overridden() {
578
+ // Patch replacing
579
+ // return !!(this.overrides && this.overrides.value && this.overrides.name === this.name)
580
+ // is based on https://github.com/npm/cli/pull/8089.
581
+ if (!this.overrides || !this.overrides.value || this.overrides.name !== this.name) {
582
+ return false;
771
583
  }
772
- try {
773
- // Check that the transformed value isn't `undefined` because older
774
- // versions of packages like 'proc-log' may not export a `log` method.
775
- const exported = transformer(req(id));
776
- if (exported !== undefined) {
777
- return exported;
584
+ // The overrides rule is for a package with this name, but some override
585
+ // rules only apply to specific versions. To make sure this package was
586
+ // actually overridden, we check whether any edge going in had the rule
587
+ // applied to it, in which case its overrides set is different than its
588
+ // source node.
589
+ for (const edge of this.edgesIn) {
590
+ if (edge.overrides && edge.overrides.name === this.name && edge.overrides.value === this.version) {
591
+ if (!edge.overrides.isEqual(edge.from?.overrides)) {
592
+ return true;
593
+ }
778
594
  }
779
- } catch {}
595
+ }
596
+ return false;
780
597
  }
781
- return undefined;
782
- }
783
- let _log = UNDEFINED_TOKEN;
784
- function getLogger() {
785
- if (_log === UNDEFINED_TOKEN) {
786
- _log = tryRequire(npmPaths.getNpmRequire(), ['proc-log/lib/index.js',
787
- // The proc-log DefinitelyTyped definition is incorrect. The type definition
788
- // is really that of its export log.
789
- mod => mod.log], 'npmlog/lib/log.js');
598
+ set parent(newParent) {
599
+ // Patch removing
600
+ // if (parent.overrides) {
601
+ // this.overrides = parent.overrides.getNodeRule(this)
602
+ // }
603
+ // is based on https://github.com/npm/cli/pull/8089.
604
+ //
605
+ // The "parent" setter is a really large and complex function. To satisfy
606
+ // the patch we hold on to the old overrides value and set `this.overrides`
607
+ // to `undefined` so that the condition we want to remove is not hit.
608
+ const {
609
+ overrides
610
+ } = this;
611
+ if (overrides) {
612
+ this.overrides = undefined;
613
+ }
614
+ try {
615
+ super.parent = newParent;
616
+ this.overrides = overrides;
617
+ } catch (e) {
618
+ this.overrides = overrides;
619
+ throw e;
620
+ }
790
621
  }
791
- return _log;
792
- }
793
-
794
- const OverrideSet = require(npmPaths.getArboristOverrideSetClassPath());
795
622
 
796
- // Implementation code not related to patch https://github.com/npm/cli/pull/8089
797
- // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/override-set.js:
798
- class SafeOverrideSet extends OverrideSet {
799
- // Patch adding doOverrideSetsConflict is based on
623
+ // Patch adding recalculateOutEdgesOverrides is based on
800
624
  // https://github.com/npm/cli/pull/8089.
801
- static doOverrideSetsConflict(first, second) {
802
- // If override sets contain one another then we can try to use the more
803
- // specific one. If neither one is more specific, then we consider them to
804
- // be in conflict.
805
- return this.findSpecificOverrideSet(first, second) === undefined;
625
+ recalculateOutEdgesOverrides() {
626
+ // For each edge out propagate the new overrides through.
627
+ for (const edge of this.edgesOut.values()) {
628
+ edge.reload(true);
629
+ if (edge.to) {
630
+ edge.to.updateOverridesEdgeInAdded(edge.overrides);
631
+ }
632
+ }
806
633
  }
807
634
 
808
- // Patch adding findSpecificOverrideSet is based on
809
- // https://github.com/npm/cli/pull/8089.
810
- static findSpecificOverrideSet(first, second) {
811
- for (let overrideSet = second; overrideSet; overrideSet = overrideSet.parent) {
812
- if (overrideSet.isEqual(first)) {
813
- return second;
814
- }
635
+ // @ts-ignore: Incorrectly typed to accept null.
636
+ set root(newRoot) {
637
+ // Patch removing
638
+ // if (!this.overrides && this.parent && this.parent.overrides) {
639
+ // this.overrides = this.parent.overrides.getNodeRule(this)
640
+ // }
641
+ // is based on https://github.com/npm/cli/pull/8089.
642
+ //
643
+ // The "root" setter is a really large and complex function. To satisfy the
644
+ // patch we add a dummy value to `this.overrides` so that the condition we
645
+ // want to remove is not hit.
646
+ if (!this.overrides) {
647
+ this.overrides = new SafeOverrideSet({
648
+ overrides: ''
649
+ });
815
650
  }
816
- for (let overrideSet = first; overrideSet; overrideSet = overrideSet.parent) {
817
- if (overrideSet.isEqual(second)) {
818
- return first;
819
- }
651
+ try {
652
+ super.root = newRoot;
653
+ this.overrides = undefined;
654
+ } catch (e) {
655
+ this.overrides = undefined;
656
+ throw e;
820
657
  }
821
- // The override sets are incomparable. Neither one contains the other.
822
- const log = getLogger();
823
- log?.silly('Conflicting override sets', first, second);
824
- return undefined;
825
658
  }
826
659
 
827
- // Patch adding childrenAreEqual is based on
828
- // https://github.com/npm/cli/pull/8089.
829
- childrenAreEqual(otherOverrideSet) {
830
- if (this.children.size !== otherOverrideSet.children.size) {
660
+ // Patch adding updateOverridesEdgeInAdded is based on
661
+ // https://github.com/npm/cli/pull/7025.
662
+ //
663
+ // This logic isn't perfect either. When we have two edges in that have
664
+ // different override sets, then we have to decide which set is correct. This
665
+ // function assumes the more specific override set is applicable, so if we have
666
+ // dependencies A->B->C and A->C and an override set that specifies what happens
667
+ // for C under A->B, this will work even if the new A->C edge comes along and
668
+ // tries to change the override set. The strictly correct logic is not to allow
669
+ // two edges with different overrides to point to the same node, because even
670
+ // if this node can satisfy both, one of its dependencies might need to be
671
+ // different depending on the edge leading to it. However, this might cause a
672
+ // lot of duplication, because the conflict in the dependencies might never
673
+ // actually happen.
674
+ updateOverridesEdgeInAdded(otherOverrideSet) {
675
+ if (!otherOverrideSet) {
676
+ // Assuming there are any overrides at all, the overrides field is never
677
+ // undefined for any node at the end state of the tree. So if the new edge's
678
+ // overrides is undefined it will be updated later. So we can wait with
679
+ // updating the node's overrides field.
831
680
  return false;
832
681
  }
833
- for (const {
834
- 0: key,
835
- 1: childOverrideSet
836
- } of this.children) {
837
- const otherChildOverrideSet = otherOverrideSet.children.get(key);
838
- if (!otherChildOverrideSet) {
839
- return false;
840
- }
841
- if (childOverrideSet.value !== otherChildOverrideSet.value) {
682
+ if (!this.overrides) {
683
+ this.overrides = otherOverrideSet;
684
+ this.recalculateOutEdgesOverrides();
685
+ return true;
686
+ }
687
+ if (this.overrides.isEqual(otherOverrideSet)) {
688
+ return false;
689
+ }
690
+ const newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(this.overrides, otherOverrideSet);
691
+ if (newOverrideSet) {
692
+ if (this.overrides.isEqual(newOverrideSet)) {
842
693
  return false;
843
694
  }
844
- if (!childOverrideSet.childrenAreEqual(otherChildOverrideSet)) {
845
- return false;
695
+ this.overrides = newOverrideSet;
696
+ this.recalculateOutEdgesOverrides();
697
+ return true;
698
+ }
699
+ // This is an error condition. We can only get here if the new override set
700
+ // is in conflict with the existing.
701
+ const log = getLogger();
702
+ log?.silly('Conflicting override sets', this.name);
703
+ return false;
704
+ }
705
+
706
+ // Patch adding updateOverridesEdgeInRemoved is based on
707
+ // https://github.com/npm/cli/pull/7025.
708
+ updateOverridesEdgeInRemoved(otherOverrideSet) {
709
+ // If this edge's overrides isn't equal to this node's overrides,
710
+ // then removing it won't change newOverrideSet later.
711
+ if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) {
712
+ return false;
713
+ }
714
+ let newOverrideSet;
715
+ for (const edge of this.edgesIn) {
716
+ const {
717
+ overrides: edgeOverrides
718
+ } = edge;
719
+ if (newOverrideSet && edgeOverrides) {
720
+ newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(edgeOverrides, newOverrideSet);
721
+ } else {
722
+ newOverrideSet = edgeOverrides;
846
723
  }
847
724
  }
725
+ if (this.overrides.isEqual(newOverrideSet)) {
726
+ return false;
727
+ }
728
+ this.overrides = newOverrideSet;
729
+ if (newOverrideSet) {
730
+ // Optimization: If there's any override set at all, then no non-extraneous
731
+ // node has an empty override set. So if we temporarily have no override set
732
+ // (for example, we removed all the edges in), there's no use updating all
733
+ // the edges out right now. Let's just wait until we have an actual override
734
+ // set later.
735
+ this.recalculateOutEdgesOverrides();
736
+ }
848
737
  return true;
849
738
  }
850
- getEdgeRule(edge) {
851
- for (const rule of this.ruleset.values()) {
852
- if (rule.name !== edge.name) {
853
- continue;
854
- }
855
- // If keySpec is * we found our override.
856
- if (rule.keySpec === '*') {
857
- return rule;
858
- }
859
- // Patch replacing
860
- // let spec = npa(`${edge.name}@${edge.spec}`)
861
- // is based on https://github.com/npm/cli/pull/8089.
862
- //
863
- // We need to use the rawSpec here, because the spec has the overrides
864
- // applied to it already. The rawSpec can be undefined, so we need to use
865
- // the fallback value of spec if it is.
866
- let spec = npa(`${edge.name}@${edge.rawSpec || edge.spec}`);
867
- if (spec.type === 'alias') {
868
- spec = spec.subSpec;
869
- }
870
- if (spec.type === 'git') {
871
- if (spec.gitRange && semver.intersects(spec.gitRange, rule.keySpec)) {
872
- return rule;
873
- }
874
- continue;
875
- }
876
- if (spec.type === 'range' || spec.type === 'version') {
877
- if (semver.intersects(spec.fetchSpec, rule.keySpec)) {
878
- return rule;
739
+ }
740
+
741
+ const Edge = require(shadowNpmPaths.getArboristEdgeClassPath());
742
+
743
+ // The Edge class makes heavy use of private properties which subclasses do NOT
744
+ // have access to. So we have to recreate any functionality that relies on those
745
+ // private properties and use our own "safe" prefixed non-conflicting private
746
+ // properties. Implementation code not related to patch https://github.com/npm/cli/pull/8089
747
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/edge.js.
748
+ //
749
+ // The npm application
750
+ // Copyright (c) npm, Inc. and Contributors
751
+ // Licensed on the terms of The Artistic License 2.0
752
+ //
753
+ // An edge in the dependency graph.
754
+ // Represents a dependency relationship of some kind.
755
+ class SafeEdge extends Edge {
756
+ #safeError;
757
+ #safeExplanation;
758
+ #safeFrom;
759
+ #safeTo;
760
+ constructor(options) {
761
+ const {
762
+ from
763
+ } = options;
764
+ // Defer to supper to validate options and assign non-private values.
765
+ super(options);
766
+ if (from.constructor !== SafeNode) {
767
+ Reflect.setPrototypeOf(from, SafeNode.prototype);
768
+ }
769
+ this.#safeError = null;
770
+ this.#safeExplanation = null;
771
+ this.#safeFrom = from;
772
+ this.#safeTo = null;
773
+ this.reload(true);
774
+ }
775
+ get bundled() {
776
+ return !!this.#safeFrom?.package?.bundleDependencies?.includes(this.name);
777
+ }
778
+ get error() {
779
+ if (!this.#safeError) {
780
+ if (!this.#safeTo) {
781
+ if (this.optional) {
782
+ this.#safeError = null;
783
+ } else {
784
+ this.#safeError = 'MISSING';
879
785
  }
880
- continue;
786
+ } else if (this.peer && this.#safeFrom === this.#safeTo.parent &&
787
+ // Patch adding "?." use based on
788
+ // https://github.com/npm/cli/pull/8089.
789
+ !this.#safeFrom?.isTop) {
790
+ this.#safeError = 'PEER LOCAL';
791
+ } else if (!this.satisfiedBy(this.#safeTo)) {
792
+ this.#safeError = 'INVALID';
793
+ }
794
+ // Patch adding "else if" condition is based on
795
+ // https://github.com/npm/cli/pull/8089.
796
+ else if (this.overrides && this.#safeTo.edgesOut.size && SafeOverrideSet.doOverrideSetsConflict(this.overrides, this.#safeTo.overrides)) {
797
+ // Any inconsistency between the edge's override set and the target's
798
+ // override set is potentially problematic. But we only say the edge is
799
+ // in error if the override sets are plainly conflicting. Note that if
800
+ // the target doesn't have any dependencies of their own, then this
801
+ // inconsistency is irrelevant.
802
+ this.#safeError = 'INVALID';
803
+ } else {
804
+ this.#safeError = 'OK';
881
805
  }
882
- // If we got this far, the spec type is one of tag, directory or file
883
- // which means we have no real way to make version comparisons, so we
884
- // just accept the override.
885
- return rule;
886
- }
887
- return this;
888
- }
889
-
890
- // Patch adding isEqual is based on
891
- // https://github.com/npm/cli/pull/8089.
892
- isEqual(otherOverrideSet) {
893
- if (this === otherOverrideSet) {
894
- return true;
895
- }
896
- if (!otherOverrideSet) {
897
- return false;
898
- }
899
- if (this.key !== otherOverrideSet.key || this.value !== otherOverrideSet.value) {
900
- return false;
901
- }
902
- if (!this.childrenAreEqual(otherOverrideSet)) {
903
- return false;
904
806
  }
905
- if (!this.parent) {
906
- return !otherOverrideSet.parent;
807
+ if (this.#safeError === 'OK') {
808
+ return null;
907
809
  }
908
- return this.parent.isEqual(otherOverrideSet.parent);
810
+ return this.#safeError;
909
811
  }
910
- }
911
812
 
912
- const Node = require(npmPaths.getArboristNodeClassPath());
813
+ // @ts-ignore: Incorrectly typed as a property instead of an accessor.
814
+ get from() {
815
+ return this.#safeFrom;
816
+ }
913
817
 
914
- // Implementation code not related to patch https://github.com/npm/cli/pull/8089
915
- // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/node.js:
916
- class SafeNode extends Node {
917
- // Return true if it's safe to remove this node, because anything that is
918
- // depending on it would be fine with the thing that they would resolve to if
919
- // it was removed, or nothing is depending on it in the first place.
920
- canDedupe(preferDedupe = false) {
921
- // Not allowed to mess with shrinkwraps or bundles.
922
- if (this.inDepBundle || this.inShrinkwrap) {
923
- return false;
924
- }
925
- // It's a top level pkg, or a dep of one.
926
- if (!this.resolveParent?.resolveParent) {
927
- return false;
928
- }
929
- // No one wants it, remove it.
930
- if (this.edgesIn.size === 0) {
931
- return true;
932
- }
933
- const other = this.resolveParent.resolveParent.resolve(this.name);
934
- // Nothing else, need this one.
935
- if (!other) {
936
- return false;
937
- }
938
- // If it's the same thing, then always fine to remove.
939
- if (other.matches(this)) {
940
- return true;
941
- }
942
- // If the other thing can't replace this, then skip it.
943
- if (!other.canReplace(this)) {
944
- return false;
818
+ // @ts-ignore: Incorrectly typed as a property instead of an accessor.
819
+ get spec() {
820
+ if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.name) {
821
+ if (this.overrides.value.startsWith('$')) {
822
+ const ref = this.overrides.value.slice(1);
823
+ // We may be a virtual root, if we are we want to resolve reference
824
+ // overrides from the real root, not the virtual one.
825
+ //
826
+ // Patch adding "?." use based on
827
+ // https://github.com/npm/cli/pull/8089.
828
+ const pkg = this.#safeFrom?.sourceReference ? this.#safeFrom?.sourceReference.root.package : this.#safeFrom?.root?.package;
829
+ if (pkg?.devDependencies?.[ref]) {
830
+ return pkg.devDependencies[ref];
831
+ }
832
+ if (pkg?.optionalDependencies?.[ref]) {
833
+ return pkg.optionalDependencies[ref];
834
+ }
835
+ if (pkg?.dependencies?.[ref]) {
836
+ return pkg.dependencies[ref];
837
+ }
838
+ if (pkg?.peerDependencies?.[ref]) {
839
+ return pkg.peerDependencies[ref];
840
+ }
841
+ throw new Error(`Unable to resolve reference ${this.overrides.value}`);
842
+ }
843
+ return this.overrides.value;
945
844
  }
845
+ return this.rawSpec;
846
+ }
847
+
848
+ // @ts-ignore: Incorrectly typed as a property instead of an accessor.
849
+ get to() {
850
+ return this.#safeTo;
851
+ }
852
+ detach() {
853
+ this.#safeExplanation = null;
946
854
  // Patch replacing
947
- // if (preferDedupe || semver.gte(other.version, this.version)) {
948
- // return true
855
+ // if (this.#to) {
856
+ // this.#to.edgesIn.delete(this)
949
857
  // }
858
+ // this.#from.edgesOut.delete(this.#name)
950
859
  // is based on https://github.com/npm/cli/pull/8089.
951
- //
952
- // If we prefer dedupe, or if the version is equal, take the other.
953
- if (preferDedupe || semver.eq(other.version, this.version)) {
954
- return true;
955
- }
956
- // If our current version isn't the result of an override, then prefer to
957
- // take the greater version.
958
- if (!this.overridden && semver.gt(other.version, this.version)) {
959
- return true;
960
- }
961
- return false;
860
+ this.#safeTo?.deleteEdgeIn(this);
861
+ this.#safeFrom?.edgesOut.delete(this.name);
862
+ this.#safeTo = null;
863
+ this.#safeError = 'DETACHED';
864
+ this.#safeFrom = null;
962
865
  }
963
866
 
964
- // Is it safe to replace one node with another? check the edges to
965
- // make sure no one will get upset. Note that the node might end up
966
- // having its own unmet dependencies, if the new node has new deps.
967
- // Note that there are cases where Arborist will opt to insert a node
968
- // into the tree even though this function returns false! This is
969
- // necessary when a root dependency is added or updated, or when a
970
- // root dependency brings peer deps along with it. In that case, we
971
- // will go ahead and create the invalid state, and then try to resolve
972
- // it with more tree construction, because it's a user request.
973
- canReplaceWith(node, ignorePeers) {
974
- if (this.name !== node.name || this.packageName !== node.packageName) {
975
- return false;
867
+ // Return the edge data, and an explanation of how that edge came to be here.
868
+ // @ts-ignore: Edge#explain is defined with an unused `seen = []` param.
869
+ explain() {
870
+ if (!this.#safeExplanation) {
871
+ const explanation = {
872
+ type: this.type,
873
+ name: this.name,
874
+ spec: this.spec,
875
+ bundled: false,
876
+ overridden: false,
877
+ error: undefined,
878
+ from: undefined,
879
+ rawSpec: undefined
880
+ };
881
+ if (this.rawSpec !== this.spec) {
882
+ explanation.rawSpec = this.rawSpec;
883
+ explanation.overridden = true;
884
+ }
885
+ if (this.bundled) {
886
+ explanation.bundled = this.bundled;
887
+ }
888
+ if (this.error) {
889
+ explanation.error = this.error;
890
+ }
891
+ if (this.#safeFrom) {
892
+ explanation.from = this.#safeFrom.explain();
893
+ }
894
+ this.#safeExplanation = explanation;
976
895
  }
896
+ return this.#safeExplanation;
897
+ }
898
+ reload(hard = false) {
899
+ this.#safeExplanation = null;
977
900
  // Patch replacing
978
- // if (node.overrides !== this.overrides) {
979
- // return false
980
- // }
901
+ // if (this.#from.overrides) {
981
902
  // is based on https://github.com/npm/cli/pull/8089.
982
- //
983
- // If this node has no dependencies, then it's irrelevant to check the
984
- // override rules of the replacement node.
985
- if (this.edgesOut.size) {
986
- // XXX need to check for two root nodes?
987
- if (node.overrides) {
988
- if (!node.overrides.isEqual(this.overrides)) {
989
- return false;
990
- }
991
- } else {
992
- if (this.overrides) {
993
- return false;
994
- }
903
+ let needToUpdateOverrideSet = false;
904
+ let newOverrideSet;
905
+ let oldOverrideSet;
906
+ if (this.#safeFrom?.overrides) {
907
+ newOverrideSet = this.#safeFrom.overrides.getEdgeRule(this);
908
+ if (newOverrideSet && !newOverrideSet.isEqual(this.overrides)) {
909
+ // If there's a new different override set we need to propagate it to
910
+ // the nodes. If we're deleting the override set then there's no point
911
+ // propagating it right now since it will be filled with another value
912
+ // later.
913
+ needToUpdateOverrideSet = true;
914
+ oldOverrideSet = this.overrides;
915
+ this.overrides = newOverrideSet;
995
916
  }
917
+ } else {
918
+ this.overrides = undefined;
996
919
  }
997
- // To satisfy the patch we ensure `node.overrides === this.overrides`
998
- // so that the condition we want to replace,
999
- // if (this.overrides !== node.overrides) {
1000
- // , is not hit.`
1001
- const oldOverrideSet = this.overrides;
1002
- let result = true;
1003
- if (oldOverrideSet !== node.overrides) {
1004
- this.overrides = node.overrides;
1005
- }
1006
- try {
1007
- result = super.canReplaceWith(node, ignorePeers);
1008
- this.overrides = oldOverrideSet;
1009
- } catch (e) {
1010
- this.overrides = oldOverrideSet;
1011
- throw e;
1012
- }
1013
- return result;
1014
- }
1015
-
1016
- // Patch adding deleteEdgeIn is based on https://github.com/npm/cli/pull/8089.
1017
- deleteEdgeIn(edge) {
1018
- this.edgesIn.delete(edge);
1019
- const {
1020
- overrides
1021
- } = edge;
1022
- if (overrides) {
1023
- this.updateOverridesEdgeInRemoved(overrides);
920
+ // Patch adding "?." use based on
921
+ // https://github.com/npm/cli/pull/8089.
922
+ const newTo = this.#safeFrom?.resolve(this.name);
923
+ if (newTo !== this.#safeTo) {
924
+ // Patch replacing
925
+ // this.#to.edgesIn.delete(this)
926
+ // is based on https://github.com/npm/cli/pull/8089.
927
+ this.#safeTo?.deleteEdgeIn(this);
928
+ this.#safeTo = newTo ?? null;
929
+ this.#safeError = null;
930
+ this.#safeTo?.addEdgeIn(this);
931
+ } else if (hard) {
932
+ this.#safeError = null;
933
+ }
934
+ // Patch adding "else if" condition based on
935
+ // https://github.com/npm/cli/pull/8089.
936
+ else if (needToUpdateOverrideSet && this.#safeTo) {
937
+ // Propagate the new override set to the target node.
938
+ this.#safeTo.updateOverridesEdgeInRemoved(oldOverrideSet);
939
+ this.#safeTo.updateOverridesEdgeInAdded(newOverrideSet);
1024
940
  }
1025
941
  }
1026
- addEdgeIn(edge) {
942
+ satisfiedBy(node) {
1027
943
  // Patch replacing
1028
- // if (edge.overrides) {
1029
- // this.overrides = edge.overrides
944
+ // if (node.name !== this.#name) {
945
+ // return false
1030
946
  // }
1031
947
  // is based on https://github.com/npm/cli/pull/8089.
1032
- //
1033
- // We need to handle the case where the new edge in has an overrides field
1034
- // which is different from the current value.
1035
- if (!this.overrides || !this.overrides.isEqual(edge.overrides)) {
1036
- this.updateOverridesEdgeInAdded(edge.overrides);
948
+ if (node.name !== this.name || !this.#safeFrom) {
949
+ return false;
950
+ }
951
+ // NOTE: this condition means we explicitly do not support overriding
952
+ // bundled or shrinkwrapped dependencies
953
+ if (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle) {
954
+ return depValid(node, this.rawSpec, this.accept, this.#safeFrom);
1037
955
  }
1038
- this.edgesIn.add(edge);
1039
- // Try to get metadata from the yarn.lock file.
1040
- this.root.meta?.addEdge(edge);
1041
- }
1042
-
1043
- // @ts-ignore: Incorrectly typed as a property instead of an accessor.
1044
- get overridden() {
1045
956
  // Patch replacing
1046
- // return !!(this.overrides && this.overrides.value && this.overrides.name === this.name)
957
+ // return depValid(node, this.spec, this.#accept, this.#from)
1047
958
  // is based on https://github.com/npm/cli/pull/8089.
1048
- if (!this.overrides || !this.overrides.value || this.overrides.name !== this.name) {
959
+ //
960
+ // If there's no override we just use the spec.
961
+ if (!this.overrides?.keySpec) {
962
+ return depValid(node, this.spec, this.accept, this.#safeFrom);
963
+ }
964
+ // There's some override. If the target node satisfies the overriding spec
965
+ // then it's okay.
966
+ if (depValid(node, this.spec, this.accept, this.#safeFrom)) {
967
+ return true;
968
+ }
969
+ // If it doesn't, then it should at least satisfy the original spec.
970
+ if (!depValid(node, this.rawSpec, this.accept, this.#safeFrom)) {
1049
971
  return false;
1050
972
  }
1051
- // The overrides rule is for a package with this name, but some override
1052
- // rules only apply to specific versions. To make sure this package was
1053
- // actually overridden, we check whether any edge going in had the rule
1054
- // applied to it, in which case its overrides set is different than its
1055
- // source node.
1056
- for (const edge of this.edgesIn) {
1057
- if (edge.overrides && edge.overrides.name === this.name && edge.overrides.value === this.version) {
1058
- if (!edge.overrides.isEqual(edge.from?.overrides)) {
1059
- return true;
1060
- }
1061
- }
973
+ // It satisfies the original spec, not the overriding spec. We need to make
974
+ // sure it doesn't use the overridden spec.
975
+ // For example:
976
+ // we might have an ^8.0.0 rawSpec, and an override that makes
977
+ // keySpec=8.23.0 and the override value spec=9.0.0.
978
+ // If the node is 9.0.0, then it's okay because it's consistent with spec.
979
+ // If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.
980
+ // If the node is 8.23.0, then it's not okay because even though it's consistent
981
+ // with the rawSpec, it's also consistent with the keySpec.
982
+ // So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.
983
+ return !depValid(node, this.overrides.keySpec, this.accept, this.#safeFrom);
984
+ }
985
+ }
986
+
987
+ const {
988
+ ALERT_TYPE_CRITICAL_CVE,
989
+ ALERT_TYPE_CVE,
990
+ ALERT_TYPE_MEDIUM_CVE,
991
+ ALERT_TYPE_MILD_CVE,
992
+ ALERT_TYPE_SOCKET_UPGRADE_AVAILABLE,
993
+ CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER: CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER$1,
994
+ CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE,
995
+ abortSignal: abortSignal$1
996
+ } = constants;
997
+ async function* createBatchGenerator(chunk) {
998
+ // Adds the first 'abort' listener to abortSignal.
999
+ const req = https
1000
+ // Lazily access constants.BATCH_PURL_ENDPOINT.
1001
+ .request(constants.BATCH_PURL_ENDPOINT, {
1002
+ method: 'POST',
1003
+ headers: {
1004
+ Authorization: `Basic ${btoa(`${getPublicToken()}:`)}`
1005
+ }
1006
+ // TODO: Fix to not abort process on network abort.
1007
+ // signal: abortSignal
1008
+ }).end(JSON.stringify({
1009
+ components: chunk.map(id => ({
1010
+ purl: `pkg:npm/${id}`
1011
+ }))
1012
+ }));
1013
+ // Adds the second 'abort' listener to abortSignal.
1014
+ const {
1015
+ 0: res
1016
+ } = await events.once(req, 'response', {
1017
+ signal: abortSignal$1
1018
+ });
1019
+ const ok = res.statusCode >= 200 && res.statusCode <= 299;
1020
+ if (!ok) {
1021
+ throw new Error(`Socket API Error: ${res.statusCode}`);
1022
+ }
1023
+ const rli = readline.createInterface({
1024
+ input: res,
1025
+ crlfDelay: Infinity,
1026
+ signal: abortSignal$1
1027
+ });
1028
+ for await (const line of rli) {
1029
+ yield JSON.parse(line);
1030
+ }
1031
+ }
1032
+ async function* batchScan(pkgIds, concurrencyLimit = 50) {
1033
+ // The createBatchGenerator method will add 2 'abort' event listeners to
1034
+ // abortSignal so we multiply the concurrencyLimit by 2.
1035
+ const neededMaxListeners = concurrencyLimit * 2;
1036
+ // Increase abortSignal max listeners count to avoid Node's MaxListenersExceededWarning.
1037
+ const oldAbortSignalMaxListeners = events.getMaxListeners(abortSignal$1);
1038
+ let abortSignalMaxListeners = oldAbortSignalMaxListeners;
1039
+ if (oldAbortSignalMaxListeners < neededMaxListeners) {
1040
+ abortSignalMaxListeners = oldAbortSignalMaxListeners + neededMaxListeners;
1041
+ events.setMaxListeners(abortSignalMaxListeners, abortSignal$1);
1042
+ }
1043
+ const {
1044
+ length: pkgIdsCount
1045
+ } = pkgIds;
1046
+ const running = [];
1047
+ let index = 0;
1048
+ const enqueueGen = () => {
1049
+ if (index >= pkgIdsCount) {
1050
+ // No more work to do.
1051
+ return;
1052
+ }
1053
+ const chunk = pkgIds.slice(index, index + 25);
1054
+ index += 25;
1055
+ const generator = createBatchGenerator(chunk);
1056
+ continueGen(generator);
1057
+ };
1058
+ const continueGen = generator => {
1059
+ let resolveFn;
1060
+ running.push({
1061
+ generator,
1062
+ promise: new Promise(resolve => resolveFn = resolve)
1063
+ });
1064
+ void generator.next().then(res => resolveFn({
1065
+ generator,
1066
+ iteratorResult: res
1067
+ }));
1068
+ };
1069
+ // Start initial batch of generators.
1070
+ while (running.length < concurrencyLimit && index < pkgIdsCount) {
1071
+ enqueueGen();
1072
+ }
1073
+ while (running.length > 0) {
1074
+ // eslint-disable-next-line no-await-in-loop
1075
+ const {
1076
+ generator,
1077
+ iteratorResult
1078
+ } = await Promise.race(running.map(entry => entry.promise));
1079
+ // Remove generator.
1080
+ running.splice(running.findIndex(entry => entry.generator === generator), 1);
1081
+ if (iteratorResult.done) {
1082
+ // Start a new generator if available.
1083
+ enqueueGen();
1084
+ } else {
1085
+ yield iteratorResult.value;
1086
+ // Keep fetching values from this generator.
1087
+ continueGen(generator);
1062
1088
  }
1089
+ }
1090
+ // Reset abortSignal max listeners count.
1091
+ if (abortSignalMaxListeners > oldAbortSignalMaxListeners) {
1092
+ events.setMaxListeners(oldAbortSignalMaxListeners, abortSignal$1);
1093
+ }
1094
+ }
1095
+ function isArtifactAlertCve(alert) {
1096
+ const {
1097
+ type
1098
+ } = alert;
1099
+ return type === ALERT_TYPE_CVE || type === ALERT_TYPE_MEDIUM_CVE || type === ALERT_TYPE_MILD_CVE || type === ALERT_TYPE_CRITICAL_CVE;
1100
+ }
1101
+ function isArtifactAlertCveFixable(alert) {
1102
+ if (!isArtifactAlertCve(alert)) {
1063
1103
  return false;
1064
1104
  }
1065
- set parent(newParent) {
1066
- // Patch removing
1067
- // if (parent.overrides) {
1068
- // this.overrides = parent.overrides.getNodeRule(this)
1069
- // }
1070
- // is based on https://github.com/npm/cli/pull/8089.
1071
- //
1072
- // The "parent" setter is a really large and complex function. To satisfy
1073
- // the patch we hold on to the old overrides value and set `this.overrides`
1074
- // to `undefined` so that the condition we want to remove is not hit.
1105
+ const {
1106
+ props
1107
+ } = alert;
1108
+ return !!props?.[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER$1] && !!props?.[CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE];
1109
+ }
1110
+ function isArtifactAlertUpgrade(alert) {
1111
+ return alert.type === ALERT_TYPE_SOCKET_UPGRADE_AVAILABLE;
1112
+ }
1113
+
1114
+ const {
1115
+ abortSignal
1116
+ } = constants;
1117
+ const ERROR_UX = {
1118
+ block: true,
1119
+ display: true
1120
+ };
1121
+ const IGNORE_UX = {
1122
+ block: false,
1123
+ display: false
1124
+ };
1125
+ const WARN_UX = {
1126
+ block: false,
1127
+ display: true
1128
+ };
1129
+
1130
+ // Iterates over all entries with ordered issue rule for deferral. Iterates over
1131
+ // all issue rules and finds the first defined value that does not defer otherwise
1132
+ // uses the defaultValue. Takes the value and converts into a UX workflow.
1133
+ function resolveAlertRuleUX(orderedRulesCollection, defaultValue) {
1134
+ if (defaultValue === true || defaultValue === null || defaultValue === undefined) {
1135
+ defaultValue = {
1136
+ action: 'error'
1137
+ };
1138
+ } else if (defaultValue === false) {
1139
+ defaultValue = {
1140
+ action: 'ignore'
1141
+ };
1142
+ }
1143
+ let block = false;
1144
+ let display = false;
1145
+ let needDefault = true;
1146
+ iterate_entries: for (const rules of orderedRulesCollection) {
1147
+ for (const rule of rules) {
1148
+ if (ruleValueDoesNotDefer(rule)) {
1149
+ needDefault = false;
1150
+ const narrowingFilter = uxForDefinedNonDeferValue(rule);
1151
+ block = block || narrowingFilter.block;
1152
+ display = display || narrowingFilter.display;
1153
+ continue iterate_entries;
1154
+ }
1155
+ }
1156
+ const narrowingFilter = uxForDefinedNonDeferValue(defaultValue);
1157
+ block = block || narrowingFilter.block;
1158
+ display = display || narrowingFilter.display;
1159
+ }
1160
+ if (needDefault) {
1161
+ const narrowingFilter = uxForDefinedNonDeferValue(defaultValue);
1162
+ block = block || narrowingFilter.block;
1163
+ display = display || narrowingFilter.display;
1164
+ }
1165
+ return {
1166
+ block,
1167
+ display
1168
+ };
1169
+ }
1170
+
1171
+ // Negative form because it is narrowing the type.
1172
+ function ruleValueDoesNotDefer(rule) {
1173
+ if (rule === undefined) {
1174
+ return false;
1175
+ }
1176
+ if (objects.isObject(rule)) {
1075
1177
  const {
1076
- overrides
1077
- } = this;
1078
- if (overrides) {
1079
- this.overrides = undefined;
1080
- }
1081
- try {
1082
- super.parent = newParent;
1083
- this.overrides = overrides;
1084
- } catch (e) {
1085
- this.overrides = overrides;
1086
- throw e;
1178
+ action
1179
+ } = rule;
1180
+ if (action === undefined || action === 'defer') {
1181
+ return false;
1087
1182
  }
1088
1183
  }
1184
+ return true;
1185
+ }
1089
1186
 
1090
- // Patch adding recalculateOutEdgesOverrides is based on
1091
- // https://github.com/npm/cli/pull/8089.
1092
- recalculateOutEdgesOverrides() {
1093
- // For each edge out propagate the new overrides through.
1094
- for (const edge of this.edgesOut.values()) {
1095
- edge.reload(true);
1096
- if (edge.to) {
1097
- edge.to.updateOverridesEdgeInAdded(edge.overrides);
1187
+ // Handles booleans for backwards compatibility.
1188
+ function uxForDefinedNonDeferValue(ruleValue) {
1189
+ if (typeof ruleValue === 'boolean') {
1190
+ return ruleValue ? ERROR_UX : IGNORE_UX;
1191
+ }
1192
+ const {
1193
+ action
1194
+ } = ruleValue;
1195
+ if (action === 'warn') {
1196
+ return WARN_UX;
1197
+ } else if (action === 'ignore') {
1198
+ return IGNORE_UX;
1199
+ }
1200
+ return ERROR_UX;
1201
+ }
1202
+ function createAlertUXLookup(settings) {
1203
+ const cachedUX = new Map();
1204
+ return context => {
1205
+ const {
1206
+ type
1207
+ } = context.alert;
1208
+ let ux = cachedUX.get(type);
1209
+ if (ux) {
1210
+ return ux;
1211
+ }
1212
+ const orderedRulesCollection = [];
1213
+ for (const settingsEntry of settings.entries) {
1214
+ const orderedRules = [];
1215
+ let target = settingsEntry.start;
1216
+ while (target !== null) {
1217
+ const resolvedTarget = settingsEntry.settings[target];
1218
+ if (!resolvedTarget) {
1219
+ break;
1220
+ }
1221
+ const issueRuleValue = resolvedTarget.issueRules?.[type];
1222
+ if (typeof issueRuleValue !== 'undefined') {
1223
+ orderedRules.push(issueRuleValue);
1224
+ }
1225
+ target = resolvedTarget.deferTo ?? null;
1098
1226
  }
1227
+ orderedRulesCollection.push(orderedRules);
1228
+ }
1229
+ const defaultValue = settings.defaults.issueRules[type];
1230
+ let resolvedDefaultValue = {
1231
+ action: 'error'
1232
+ };
1233
+ if (defaultValue === false) {
1234
+ resolvedDefaultValue = {
1235
+ action: 'ignore'
1236
+ };
1237
+ } else if (defaultValue && defaultValue !== true) {
1238
+ resolvedDefaultValue = {
1239
+ action: defaultValue.action ?? 'error'
1240
+ };
1099
1241
  }
1242
+ ux = resolveAlertRuleUX(orderedRulesCollection, resolvedDefaultValue);
1243
+ cachedUX.set(type, ux);
1244
+ return ux;
1245
+ };
1246
+ }
1247
+ let _uxLookup;
1248
+ async function uxLookup(settings) {
1249
+ while (_uxLookup === undefined) {
1250
+ // eslint-disable-next-line no-await-in-loop
1251
+ await promises.setTimeout(1, {
1252
+ signal: abortSignal
1253
+ });
1100
1254
  }
1255
+ return _uxLookup(settings);
1256
+ }
1101
1257
 
1102
- // @ts-ignore: Incorrectly typed to accept null.
1103
- set root(newRoot) {
1104
- // Patch removing
1105
- // if (!this.overrides && this.parent && this.parent.overrides) {
1106
- // this.overrides = this.parent.overrides.getNodeRule(this)
1107
- // }
1108
- // is based on https://github.com/npm/cli/pull/8089.
1109
- //
1110
- // The "root" setter is a really large and complex function. To satisfy the
1111
- // patch we add a dummy value to `this.overrides` so that the condition we
1112
- // want to remove is not hit.
1113
- if (!this.overrides) {
1114
- this.overrides = new SafeOverrideSet({
1115
- overrides: ''
1116
- });
1117
- }
1258
+ // Start initializing the AlertUxLookupResult immediately.
1259
+ void (async () => {
1260
+ const {
1261
+ orgs,
1262
+ settings
1263
+ } = await (async () => {
1118
1264
  try {
1119
- super.root = newRoot;
1120
- this.overrides = undefined;
1265
+ const sockSdk = await setupSdk(getPublicToken());
1266
+ const orgResult = await sockSdk.getOrganizations();
1267
+ if (!orgResult.success) {
1268
+ throw new Error(`Failed to fetch Socket organization info: ${orgResult.error.message}`);
1269
+ }
1270
+ const orgs = [];
1271
+ for (const org of Object.values(orgResult.data.organizations)) {
1272
+ if (org) {
1273
+ orgs.push(org);
1274
+ }
1275
+ }
1276
+ const result = await sockSdk.postSettings(orgs.map(org => ({
1277
+ organization: org.id
1278
+ })));
1279
+ if (!result.success) {
1280
+ throw new Error(`Failed to fetch API key settings: ${result.error.message}`);
1281
+ }
1282
+ return {
1283
+ orgs,
1284
+ settings: result.data
1285
+ };
1121
1286
  } catch (e) {
1122
- this.overrides = undefined;
1287
+ const cause = objects.isObject(e) && 'cause' in e ? e['cause'] : undefined;
1288
+ if (isErrnoException(cause) && (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED')) {
1289
+ throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
1290
+ cause: e
1291
+ });
1292
+ }
1123
1293
  throw e;
1124
1294
  }
1125
- }
1295
+ })();
1126
1296
 
1127
- // Patch adding updateOverridesEdgeInAdded is based on
1128
- // https://github.com/npm/cli/pull/7025.
1129
- //
1130
- // This logic isn't perfect either. When we have two edges in that have
1131
- // different override sets, then we have to decide which set is correct. This
1132
- // function assumes the more specific override set is applicable, so if we have
1133
- // dependencies A->B->C and A->C and an override set that specifies what happens
1134
- // for C under A->B, this will work even if the new A->C edge comes along and
1135
- // tries to change the override set. The strictly correct logic is not to allow
1136
- // two edges with different overrides to point to the same node, because even
1137
- // if this node can satisfy both, one of its dependencies might need to be
1138
- // different depending on the edge leading to it. However, this might cause a
1139
- // lot of duplication, because the conflict in the dependencies might never
1140
- // actually happen.
1141
- updateOverridesEdgeInAdded(otherOverrideSet) {
1142
- if (!otherOverrideSet) {
1143
- // Assuming there are any overrides at all, the overrides field is never
1144
- // undefined for any node at the end state of the tree. So if the new edge's
1145
- // overrides is undefined it will be updated later. So we can wait with
1146
- // updating the node's overrides field.
1147
- return false;
1148
- }
1149
- if (!this.overrides) {
1150
- this.overrides = otherOverrideSet;
1151
- this.recalculateOutEdgesOverrides();
1152
- return true;
1153
- }
1154
- if (this.overrides.isEqual(otherOverrideSet)) {
1155
- return false;
1156
- }
1157
- const newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(this.overrides, otherOverrideSet);
1158
- if (newOverrideSet) {
1159
- if (this.overrides.isEqual(newOverrideSet)) {
1160
- return false;
1161
- }
1162
- this.overrides = newOverrideSet;
1163
- this.recalculateOutEdgesOverrides();
1164
- return true;
1297
+ // Remove any organizations not being enforced.
1298
+ const enforcedOrgs = getSetting('enforcedOrgs') ?? [];
1299
+ for (const {
1300
+ 0: i,
1301
+ 1: org
1302
+ } of orgs.entries()) {
1303
+ if (!enforcedOrgs.includes(org.id)) {
1304
+ settings.entries.splice(i, 1);
1165
1305
  }
1166
- // This is an error condition. We can only get here if the new override set
1167
- // is in conflict with the existing.
1168
- const log = getLogger();
1169
- log?.silly('Conflicting override sets', this.name);
1170
- return false;
1171
1306
  }
1172
-
1173
- // Patch adding updateOverridesEdgeInRemoved is based on
1174
- // https://github.com/npm/cli/pull/7025.
1175
- updateOverridesEdgeInRemoved(otherOverrideSet) {
1176
- // If this edge's overrides isn't equal to this node's overrides,
1177
- // then removing it won't change newOverrideSet later.
1178
- if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) {
1179
- return false;
1180
- }
1181
- let newOverrideSet;
1182
- for (const edge of this.edgesIn) {
1183
- const {
1184
- overrides: edgeOverrides
1185
- } = edge;
1186
- if (newOverrideSet && edgeOverrides) {
1187
- newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(edgeOverrides, newOverrideSet);
1188
- } else {
1189
- newOverrideSet = edgeOverrides;
1307
+ const socketYml = findSocketYmlSync();
1308
+ if (socketYml) {
1309
+ settings.entries.push({
1310
+ start: socketYml.path,
1311
+ settings: {
1312
+ [socketYml.path]: {
1313
+ deferTo: null,
1314
+ // TODO: TypeScript complains about the type not matching. We should
1315
+ // figure out why are providing
1316
+ // issueRules: { [issueName: string]: boolean }
1317
+ // but expecting
1318
+ // issueRules: { [issueName: string]: { action: 'defer' | 'error' | 'ignore' | 'monitor' | 'warn' } }
1319
+ issueRules: socketYml.parsed.issueRules
1320
+ }
1190
1321
  }
1191
- }
1192
- if (this.overrides.isEqual(newOverrideSet)) {
1193
- return false;
1194
- }
1195
- this.overrides = newOverrideSet;
1196
- if (newOverrideSet) {
1197
- // Optimization: If there's any override set at all, then no non-extraneous
1198
- // node has an empty override set. So if we temporarily have no override set
1199
- // (for example, we removed all the edges in), there's no use updating all
1200
- // the edges out right now. Let's just wait until we have an actual override
1201
- // set later.
1202
- this.recalculateOutEdgesOverrides();
1203
- }
1204
- return true;
1322
+ });
1323
+ }
1324
+ _uxLookup = createAlertUXLookup(settings);
1325
+ })();
1326
+
1327
+ function pick(input, keys) {
1328
+ const result = {};
1329
+ for (const key of keys) {
1330
+ result[key] = input[key];
1205
1331
  }
1332
+ return result;
1206
1333
  }
1207
1334
 
1208
- const Edge = require(npmPaths.getArboristEdgeClassPath());
1335
+ function stringJoinWithSeparateFinalSeparator(list, separator = ' and ') {
1336
+ const values = list.filter(Boolean);
1337
+ const {
1338
+ length
1339
+ } = values;
1340
+ if (!length) {
1341
+ return '';
1342
+ }
1343
+ if (length === 1) {
1344
+ return values[0];
1345
+ }
1346
+ const finalValue = values.pop();
1347
+ return `${values.join(', ')}${separator}${finalValue}`;
1348
+ }
1209
1349
 
1210
- // The Edge class makes heavy use of private properties which subclasses do NOT
1211
- // have access to. So we have to recreate any functionality that relies on those
1212
- // private properties and use our own "safe" prefixed non-conflicting private
1213
- // properties. Implementation code not related to patch https://github.com/npm/cli/pull/8089
1214
- // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/edge.js.
1215
- //
1216
- // The npm application
1217
- // Copyright (c) npm, Inc. and Contributors
1218
- // Licensed on the terms of The Artistic License 2.0
1219
- //
1220
- // An edge in the dependency graph.
1221
- // Represents a dependency relationship of some kind.
1222
- class SafeEdge extends Edge {
1223
- #safeError;
1224
- #safeExplanation;
1225
- #safeFrom;
1226
- #safeTo;
1227
- constructor(options) {
1228
- const {
1229
- from
1230
- } = options;
1231
- // Defer to supper to validate options and assign non-private values.
1232
- super(options);
1233
- if (from.constructor !== SafeNode) {
1234
- Reflect.setPrototypeOf(from, SafeNode.prototype);
1350
+ let SEVERITY = /*#__PURE__*/function (SEVERITY) {
1351
+ SEVERITY["critical"] = "critical";
1352
+ SEVERITY["high"] = "high";
1353
+ SEVERITY["middle"] = "middle";
1354
+ SEVERITY["low"] = "low";
1355
+ return SEVERITY;
1356
+ }({});
1357
+
1358
+ // Ordered from most severe to least.
1359
+ const SEVERITIES_BY_ORDER = ['critical', 'high', 'middle', 'low'];
1360
+ function getDesiredSeverities(lowestToInclude) {
1361
+ const result = [];
1362
+ for (const severity of SEVERITIES_BY_ORDER) {
1363
+ result.push(severity);
1364
+ if (severity === lowestToInclude) {
1365
+ break;
1235
1366
  }
1236
- this.#safeError = null;
1237
- this.#safeExplanation = null;
1238
- this.#safeFrom = from;
1239
- this.#safeTo = null;
1240
- this.reload(true);
1241
1367
  }
1242
- get bundled() {
1243
- return !!this.#safeFrom?.package?.bundleDependencies?.includes(this.name);
1368
+ return result;
1369
+ }
1370
+ function formatSeverityCount(severityCount) {
1371
+ const summary = [];
1372
+ for (const severity of SEVERITIES_BY_ORDER) {
1373
+ if (severityCount[severity]) {
1374
+ summary.push(`${severityCount[severity]} ${severity}`);
1375
+ }
1244
1376
  }
1245
- get error() {
1246
- if (!this.#safeError) {
1247
- if (!this.#safeTo) {
1248
- if (this.optional) {
1249
- this.#safeError = null;
1250
- } else {
1251
- this.#safeError = 'MISSING';
1252
- }
1253
- } else if (this.peer && this.#safeFrom === this.#safeTo.parent &&
1254
- // Patch adding "?." use based on
1255
- // https://github.com/npm/cli/pull/8089.
1256
- !this.#safeFrom?.isTop) {
1257
- this.#safeError = 'PEER LOCAL';
1258
- } else if (!this.satisfiedBy(this.#safeTo)) {
1259
- this.#safeError = 'INVALID';
1260
- }
1261
- // Patch adding "else if" condition is based on
1262
- // https://github.com/npm/cli/pull/8089.
1263
- else if (this.overrides && this.#safeTo.edgesOut.size && SafeOverrideSet.doOverrideSetsConflict(this.overrides, this.#safeTo.overrides)) {
1264
- // Any inconsistency between the edge's override set and the target's
1265
- // override set is potentially problematic. But we only say the edge is
1266
- // in error if the override sets are plainly conflicting. Note that if
1267
- // the target doesn't have any dependencies of their own, then this
1268
- // inconsistency is irrelevant.
1269
- this.#safeError = 'INVALID';
1270
- } else {
1271
- this.#safeError = 'OK';
1272
- }
1377
+ return stringJoinWithSeparateFinalSeparator(summary);
1378
+ }
1379
+ function getSeverityCount(issues, lowestToInclude) {
1380
+ const severityCount = pick({
1381
+ low: 0,
1382
+ middle: 0,
1383
+ high: 0,
1384
+ critical: 0
1385
+ }, getDesiredSeverities(lowestToInclude));
1386
+ for (const issue of issues) {
1387
+ const {
1388
+ value
1389
+ } = issue;
1390
+ if (!value) {
1391
+ continue;
1273
1392
  }
1274
- if (this.#safeError === 'OK') {
1275
- return null;
1393
+ if (severityCount[value.severity] !== undefined) {
1394
+ severityCount[value.severity] += 1;
1276
1395
  }
1277
- return this.#safeError;
1278
1396
  }
1397
+ return severityCount;
1398
+ }
1279
1399
 
1280
- // @ts-ignore: Incorrectly typed as a property instead of an accessor.
1281
- get from() {
1282
- return this.#safeFrom;
1400
+ class ColorOrMarkdown {
1401
+ constructor(useMarkdown) {
1402
+ this.useMarkdown = !!useMarkdown;
1283
1403
  }
1284
-
1285
- // @ts-ignore: Incorrectly typed as a property instead of an accessor.
1286
- get spec() {
1287
- if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.name) {
1288
- if (this.overrides.value.startsWith('$')) {
1289
- const ref = this.overrides.value.slice(1);
1290
- // We may be a virtual root, if we are we want to resolve reference
1291
- // overrides from the real root, not the virtual one.
1292
- //
1293
- // Patch adding "?." use based on
1294
- // https://github.com/npm/cli/pull/8089.
1295
- const pkg = this.#safeFrom?.sourceReference ? this.#safeFrom?.sourceReference.root.package : this.#safeFrom?.root?.package;
1296
- if (pkg?.devDependencies?.[ref]) {
1297
- return pkg.devDependencies[ref];
1298
- }
1299
- if (pkg?.optionalDependencies?.[ref]) {
1300
- return pkg.optionalDependencies[ref];
1301
- }
1302
- if (pkg?.dependencies?.[ref]) {
1303
- return pkg.dependencies[ref];
1304
- }
1305
- if (pkg?.peerDependencies?.[ref]) {
1306
- return pkg.peerDependencies[ref];
1307
- }
1308
- throw new Error(`Unable to resolve reference ${this.overrides.value}`);
1309
- }
1310
- return this.overrides.value;
1404
+ bold(text) {
1405
+ return this.useMarkdown ? `**${text}**` : colors.bold(`${text}`);
1406
+ }
1407
+ header(text, level = 1) {
1408
+ return this.useMarkdown ? `\n${''.padStart(level, '#')} ${text}\n` : colors.underline(`\n${level === 1 ? colors.bold(text) : text}\n`);
1409
+ }
1410
+ hyperlink(text, url, {
1411
+ fallback = true,
1412
+ fallbackToUrl
1413
+ } = {}) {
1414
+ if (url) {
1415
+ return this.useMarkdown ? `[${text}](${url})` : terminalLink(text, url, {
1416
+ fallback: fallbackToUrl ? (_text, url) => url : fallback
1417
+ });
1311
1418
  }
1312
- return this.rawSpec;
1419
+ return text;
1313
1420
  }
1314
-
1315
- // @ts-ignore: Incorrectly typed as a property instead of an accessor.
1316
- get to() {
1317
- return this.#safeTo;
1421
+ indent(...args) {
1422
+ return indentString(...args);
1318
1423
  }
1319
- detach() {
1320
- this.#safeExplanation = null;
1321
- // Patch replacing
1322
- // if (this.#to) {
1323
- // this.#to.edgesIn.delete(this)
1324
- // }
1325
- // this.#from.edgesOut.delete(this.#name)
1326
- // is based on https://github.com/npm/cli/pull/8089.
1327
- this.#safeTo?.deleteEdgeIn(this);
1328
- this.#safeFrom?.edgesOut.delete(this.name);
1329
- this.#safeTo = null;
1330
- this.#safeError = 'DETACHED';
1331
- this.#safeFrom = null;
1424
+ italic(text) {
1425
+ return this.useMarkdown ? `_${text}_` : colors.italic(`${text}`);
1426
+ }
1427
+ json(value) {
1428
+ return this.useMarkdown ? '```json\n' + JSON.stringify(value) + '\n```' : JSON.stringify(value);
1429
+ }
1430
+ list(items) {
1431
+ const indentedContent = items.map(item => this.indent(item).trimStart());
1432
+ return this.useMarkdown ? `* ${indentedContent.join('\n* ')}\n` : `${indentedContent.join('\n')}\n`;
1332
1433
  }
1434
+ }
1333
1435
 
1334
- // Return the edge data, and an explanation of how that edge came to be here.
1335
- // @ts-ignore: Edge#explain is defined with an unused `seen = []` param.
1336
- explain() {
1337
- if (!this.#safeExplanation) {
1338
- const explanation = {
1339
- type: this.type,
1340
- name: this.name,
1341
- spec: this.spec,
1342
- bundled: false,
1343
- overridden: false,
1344
- error: undefined,
1345
- from: undefined,
1346
- rawSpec: undefined
1347
- };
1348
- if (this.rawSpec !== this.spec) {
1349
- explanation.rawSpec = this.rawSpec;
1350
- explanation.overridden = true;
1351
- }
1352
- if (this.bundled) {
1353
- explanation.bundled = this.bundled;
1354
- }
1355
- if (this.error) {
1356
- explanation.error = this.error;
1357
- }
1358
- if (this.#safeFrom) {
1359
- explanation.from = this.#safeFrom.explain();
1360
- }
1361
- this.#safeExplanation = explanation;
1362
- }
1363
- return this.#safeExplanation;
1436
+ function getSocketDevAlertUrl(alertType) {
1437
+ return `https://socket.dev/alerts/${alertType}`;
1438
+ }
1439
+ function getSocketDevPackageOverviewUrl(eco, name, version) {
1440
+ return `https://socket.dev/${eco}/package/${name}${version ? `/overview/${version}` : ''}`;
1441
+ }
1442
+
1443
+ let _translations;
1444
+ function getTranslations() {
1445
+ if (_translations === undefined) {
1446
+ _translations = require(
1447
+ // Lazily access constants.rootPath.
1448
+ path.join(constants.rootPath, 'translations.json'));
1364
1449
  }
1365
- reload(hard = false) {
1366
- this.#safeExplanation = null;
1367
- // Patch replacing
1368
- // if (this.#from.overrides) {
1369
- // is based on https://github.com/npm/cli/pull/8089.
1370
- let needToUpdateOverrideSet = false;
1371
- let newOverrideSet;
1372
- let oldOverrideSet;
1373
- if (this.#safeFrom?.overrides) {
1374
- newOverrideSet = this.#safeFrom.overrides.getEdgeRule(this);
1375
- if (newOverrideSet && !newOverrideSet.isEqual(this.overrides)) {
1376
- // If there's a new different override set we need to propagate it to
1377
- // the nodes. If we're deleting the override set then there's no point
1378
- // propagating it right now since it will be filled with another value
1379
- // later.
1380
- needToUpdateOverrideSet = true;
1381
- oldOverrideSet = this.overrides;
1382
- this.overrides = newOverrideSet;
1383
- }
1384
- } else {
1385
- this.overrides = undefined;
1386
- }
1387
- // Patch adding "?." use based on
1388
- // https://github.com/npm/cli/pull/8089.
1389
- const newTo = this.#safeFrom?.resolve(this.name);
1390
- if (newTo !== this.#safeTo) {
1391
- // Patch replacing
1392
- // this.#to.edgesIn.delete(this)
1393
- // is based on https://github.com/npm/cli/pull/8089.
1394
- this.#safeTo?.deleteEdgeIn(this);
1395
- this.#safeTo = newTo ?? null;
1396
- this.#safeError = null;
1397
- this.#safeTo?.addEdgeIn(this);
1398
- } else if (hard) {
1399
- this.#safeError = null;
1400
- }
1401
- // Patch adding "else if" condition based on
1402
- // https://github.com/npm/cli/pull/8089.
1403
- else if (needToUpdateOverrideSet && this.#safeTo) {
1404
- // Propagate the new override set to the target node.
1405
- this.#safeTo.updateOverridesEdgeInRemoved(oldOverrideSet);
1406
- this.#safeTo.updateOverridesEdgeInAdded(newOverrideSet);
1450
+ return _translations;
1451
+ }
1452
+
1453
+ const {
1454
+ CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER,
1455
+ NPM: NPM$1
1456
+ } = constants;
1457
+ const format = new ColorOrMarkdown(false);
1458
+ async function addArtifactToAlertsMap(artifact, alertsByPkgId, options) {
1459
+ // Make TypeScript happy.
1460
+ if (!artifact.name || !artifact.version || !artifact.alerts?.length) {
1461
+ return;
1462
+ }
1463
+ const {
1464
+ consolidate = false,
1465
+ include: _include,
1466
+ overrides
1467
+ } = {
1468
+ __proto__: null,
1469
+ ...options
1470
+ };
1471
+ const include = {
1472
+ __proto__: null,
1473
+ critical: true,
1474
+ cve: true,
1475
+ unfixable: true,
1476
+ upgrade: false,
1477
+ ..._include
1478
+ };
1479
+ const name = packages.resolvePackageName(artifact);
1480
+ const {
1481
+ version
1482
+ } = artifact;
1483
+ const pkgId = `${name}@${version}`;
1484
+ const major = semver.major(version);
1485
+ let sockPkgAlerts = [];
1486
+ for (const alert of artifact.alerts) {
1487
+ // eslint-disable-next-line no-await-in-loop
1488
+ const ux = await uxLookup({
1489
+ package: {
1490
+ name,
1491
+ version
1492
+ },
1493
+ alert: {
1494
+ type: alert.type
1495
+ }
1496
+ });
1497
+ const critical = alert.severity === SEVERITY.critical;
1498
+ const cve = isArtifactAlertCve(alert);
1499
+ const fixableCve = isArtifactAlertCveFixable(alert);
1500
+ const fixableUpgrade = isArtifactAlertUpgrade(alert);
1501
+ const fixable = fixableCve || fixableUpgrade;
1502
+ const upgrade = fixableUpgrade && !objects.hasOwn(overrides, name);
1503
+ if (include.cve && cve || include.unfixable && !fixable || include.critical && critical || include.upgrade && upgrade) {
1504
+ sockPkgAlerts.push({
1505
+ name,
1506
+ version,
1507
+ key: alert.key,
1508
+ type: alert.type,
1509
+ block: ux.block,
1510
+ critical,
1511
+ display: ux.display,
1512
+ fixable,
1513
+ raw: alert,
1514
+ upgrade
1515
+ });
1407
1516
  }
1408
1517
  }
1409
- satisfiedBy(node) {
1410
- // Patch replacing
1411
- // if (node.name !== this.#name) {
1412
- // return false
1413
- // }
1414
- // is based on https://github.com/npm/cli/pull/8089.
1415
- if (node.name !== this.name || !this.#safeFrom) {
1416
- return false;
1417
- }
1418
- // NOTE: this condition means we explicitly do not support overriding
1419
- // bundled or shrinkwrapped dependencies
1420
- if (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle) {
1421
- return depValid(node, this.rawSpec, this.accept, this.#safeFrom);
1422
- }
1423
- // Patch replacing
1424
- // return depValid(node, this.spec, this.#accept, this.#from)
1425
- // is based on https://github.com/npm/cli/pull/8089.
1426
- //
1427
- // If there's no override we just use the spec.
1428
- if (!this.overrides?.keySpec) {
1429
- return depValid(node, this.spec, this.accept, this.#safeFrom);
1518
+ if (!sockPkgAlerts.length) {
1519
+ return;
1520
+ }
1521
+ if (consolidate) {
1522
+ const highestForCve = new Map();
1523
+ const highestForUpgrade = new Map();
1524
+ const unfixableAlerts = [];
1525
+ for (const sockPkgAlert of sockPkgAlerts) {
1526
+ if (isArtifactAlertCveFixable(sockPkgAlert.raw)) {
1527
+ const patchedVersion = sockPkgAlert.raw.props[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER];
1528
+ const patchedMajor = semver.major(patchedVersion);
1529
+ const oldHighest = highestForCve.get(patchedMajor);
1530
+ const highest = oldHighest?.version ?? '0.0.0';
1531
+ if (semver.gt(patchedVersion, highest)) {
1532
+ highestForCve.set(patchedMajor, {
1533
+ alert: sockPkgAlert,
1534
+ version: patchedVersion
1535
+ });
1536
+ }
1537
+ } else if (isArtifactAlertUpgrade(sockPkgAlert.raw)) {
1538
+ const oldHighest = highestForUpgrade.get(major);
1539
+ const highest = oldHighest?.version ?? '0.0.0';
1540
+ if (semver.gt(version, highest)) {
1541
+ highestForUpgrade.set(major, {
1542
+ alert: sockPkgAlert,
1543
+ version
1544
+ });
1545
+ }
1546
+ } else {
1547
+ unfixableAlerts.push(sockPkgAlert);
1548
+ }
1430
1549
  }
1431
- // There's some override. If the target node satisfies the overriding spec
1432
- // then it's okay.
1433
- if (depValid(node, this.spec, this.accept, this.#safeFrom)) {
1434
- return true;
1550
+ sockPkgAlerts = [...unfixableAlerts, ...[...highestForCve.values()].map(d => d.alert), ...[...highestForUpgrade.values()].map(d => d.alert)];
1551
+ }
1552
+ if (!sockPkgAlerts.length) {
1553
+ return;
1554
+ }
1555
+ sockPkgAlerts.sort((a, b) => sorts.naturalCompare(a.type, b.type));
1556
+ alertsByPkgId.set(pkgId, sockPkgAlerts);
1557
+ }
1558
+ function getCveInfoByAlertsMap(alertsMap, options) {
1559
+ const exclude = {
1560
+ upgrade: true,
1561
+ ...{
1562
+ __proto__: null,
1563
+ ...options
1564
+ }.exclude
1565
+ };
1566
+ let infoByPkg = null;
1567
+ for (const [pkgId, alerts] of alertsMap) {
1568
+ const purlObj = packageurlJs.PackageURL.fromString(`pkg:npm/${pkgId}`);
1569
+ const name = packages.resolvePackageName(purlObj);
1570
+ for (const alert of alerts) {
1571
+ if (!isArtifactAlertCveFixable(alert.raw) || exclude.upgrade && registry.getManifestData(NPM$1, name)) {
1572
+ continue;
1573
+ }
1574
+ if (!infoByPkg) {
1575
+ infoByPkg = new Map();
1576
+ }
1577
+ let infos = infoByPkg.get(name);
1578
+ if (!infos) {
1579
+ infos = [];
1580
+ infoByPkg.set(name, infos);
1581
+ }
1582
+ const {
1583
+ firstPatchedVersionIdentifier,
1584
+ vulnerableVersionRange
1585
+ } = alert.raw.props;
1586
+ infos.push({
1587
+ firstPatchedVersionIdentifier,
1588
+ vulnerableVersionRange: new semver.Range(vulnerableVersionRange).format()
1589
+ });
1435
1590
  }
1436
- // If it doesn't, then it should at least satisfy the original spec.
1437
- if (!depValid(node, this.rawSpec, this.accept, this.#safeFrom)) {
1438
- return false;
1591
+ }
1592
+ return infoByPkg;
1593
+ }
1594
+ function logAlertsMap(alertsMap, options) {
1595
+ const {
1596
+ output = process.stderr
1597
+ } = {
1598
+ __proto__: null,
1599
+ ...options
1600
+ };
1601
+ const translations = getTranslations();
1602
+ for (const [pkgId, alerts] of alertsMap) {
1603
+ const purlObj = packageurlJs.PackageURL.fromString(`pkg:npm/${pkgId}`);
1604
+ const lines = new Set();
1605
+ for (const alert of alerts) {
1606
+ const {
1607
+ type
1608
+ } = alert;
1609
+ const attributes = [...(alert.fixable ? ['fixable'] : []), ...(alert.block ? [] : ['non-blocking'])];
1610
+ const maybeAttributes = attributes.length ? ` (${attributes.join('; ')})` : '';
1611
+ // Based data from { pageProps: { alertTypes } } of:
1612
+ // https://socket.dev/_next/data/94666139314b6437ee4491a0864e72b264547585/en-US.json
1613
+ const info = translations.alerts[type];
1614
+ const title = info?.title ?? type;
1615
+ const maybeDesc = info?.description ? ` - ${info.description}` : '';
1616
+ // TODO: emoji seems to mis-align terminals sometimes
1617
+ lines.add(` ${title}${maybeAttributes}${maybeDesc}`);
1618
+ }
1619
+ output.write(`(socket) ${format.hyperlink(pkgId, getSocketDevPackageOverviewUrl(NPM$1, packages.resolvePackageName(purlObj), purlObj.version))} contains risks:\n`);
1620
+ for (const line of lines) {
1621
+ output.write(`${line}\n`);
1439
1622
  }
1440
- // It satisfies the original spec, not the overriding spec. We need to make
1441
- // sure it doesn't use the overridden spec.
1442
- // For example:
1443
- // we might have an ^8.0.0 rawSpec, and an override that makes
1444
- // keySpec=8.23.0 and the override value spec=9.0.0.
1445
- // If the node is 9.0.0, then it's okay because it's consistent with spec.
1446
- // If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.
1447
- // If the node is 8.23.0, then it's not okay because even though it's consistent
1448
- // with the rawSpec, it's also consistent with the keySpec.
1449
- // So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.
1450
- return !depValid(node, this.overrides.keySpec, this.accept, this.#safeFrom);
1451
1623
  }
1452
1624
  }
1453
1625
 
1454
1626
  const {
1455
- CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER,
1456
1627
  LOOP_SENTINEL,
1457
1628
  NPM,
1458
- NPM_REGISTRY_URL,
1459
- OVERRIDES,
1460
- PNPM,
1461
- RESOLUTIONS
1629
+ NPM_REGISTRY_URL
1462
1630
  } = constants;
1463
- const formatter = new ColorOrMarkdown(false);
1631
+ function getDetailsFromDiff(diff_, options) {
1632
+ const details = [];
1633
+ // `diff_` is `null` when `npm install --package-lock-only` is passed.
1634
+ if (!diff_) {
1635
+ return details;
1636
+ }
1637
+ const include = {
1638
+ __proto__: null,
1639
+ unchanged: false,
1640
+ unknownOrigin: false,
1641
+ ...{
1642
+ __proto__: null,
1643
+ ...options
1644
+ }.include
1645
+ };
1646
+ const queue = [...diff_.children];
1647
+ let pos = 0;
1648
+ let {
1649
+ length: queueLength
1650
+ } = queue;
1651
+ while (pos < queueLength) {
1652
+ if (pos === LOOP_SENTINEL) {
1653
+ throw new Error('Detected infinite loop while walking Arborist diff');
1654
+ }
1655
+ const diff = queue[pos++];
1656
+ const {
1657
+ action
1658
+ } = diff;
1659
+ if (action) {
1660
+ // The `pkgNode`, i.e. the `ideal` node, will be `undefined` if the diff
1661
+ // action is 'REMOVE'
1662
+ // The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff
1663
+ // action is 'ADD'.
1664
+ const {
1665
+ actual: oldNode,
1666
+ ideal: pkgNode
1667
+ } = diff;
1668
+ let existing;
1669
+ let keep = false;
1670
+ if (action === DiffAction.change) {
1671
+ if (pkgNode?.package.version !== oldNode?.package.version) {
1672
+ keep = true;
1673
+ if (oldNode?.package.name && oldNode.package.name === pkgNode?.package.name) {
1674
+ existing = oldNode;
1675
+ }
1676
+ } else {
1677
+ debug.debugLog('SKIPPING META CHANGE ON', diff);
1678
+ }
1679
+ } else {
1680
+ keep = action !== DiffAction.remove;
1681
+ }
1682
+ if (keep && pkgNode?.resolved && (!oldNode || oldNode.resolved)) {
1683
+ if (include.unknownOrigin || getUrlOrigin(pkgNode.resolved) === NPM_REGISTRY_URL) {
1684
+ details.push({
1685
+ node: pkgNode,
1686
+ existing
1687
+ });
1688
+ }
1689
+ }
1690
+ }
1691
+ for (const child of diff.children) {
1692
+ queue[queueLength++] = child;
1693
+ }
1694
+ }
1695
+ if (include.unchanged) {
1696
+ const {
1697
+ unchanged
1698
+ } = diff_;
1699
+ for (let i = 0, {
1700
+ length
1701
+ } = unchanged; i < length; i += 1) {
1702
+ const pkgNode = unchanged[i];
1703
+ if (include.unknownOrigin || getUrlOrigin(pkgNode.resolved) === NPM_REGISTRY_URL) {
1704
+ details.push({
1705
+ node: pkgNode,
1706
+ existing: pkgNode
1707
+ });
1708
+ }
1709
+ }
1710
+ }
1711
+ return details;
1712
+ }
1713
+ function getUrlOrigin(input) {
1714
+ try {
1715
+ return URL.parse(input)?.origin ?? '';
1716
+ } catch {}
1717
+ return '';
1718
+ }
1464
1719
  function findBestPatchVersion(node, availableVersions, vulnerableVersionRange, _firstPatchedVersionIdentifier) {
1465
1720
  const manifestData = registry.getManifestData(NPM, node.name);
1466
1721
  let eligibleVersions;
@@ -1470,8 +1725,8 @@ function findBestPatchVersion(node, availableVersions, vulnerableVersionRange, _
1470
1725
  } else {
1471
1726
  const major = semver.major(node.version);
1472
1727
  eligibleVersions = availableVersions.filter(v =>
1473
- // Filter for versions that are within the current major version
1474
- // and are not in the vulnerable range
1728
+ // Filter for versions that are within the current major version and
1729
+ // are NOT in the vulnerable range.
1475
1730
  semver.major(v) === major && (!vulnerableVersionRange || !semver.satisfies(v, vulnerableVersionRange)));
1476
1731
  }
1477
1732
  return semver.maxSatisfying(eligibleVersions, '*');
@@ -1502,17 +1757,55 @@ function findPackageNodes(tree, packageName) {
1502
1757
  }
1503
1758
  return matches;
1504
1759
  }
1505
- let _translations;
1506
- function getTranslations() {
1507
- if (_translations === undefined) {
1508
- _translations = require(
1509
- // Lazily access constants.rootPath.
1510
- path.join(constants.rootPath, 'translations.json'));
1760
+ async function getAlertsMapFromArborist(arb, options) {
1761
+ const {
1762
+ include: _include,
1763
+ spinner
1764
+ } = {
1765
+ __proto__: null,
1766
+ ...options
1767
+ };
1768
+ const include = {
1769
+ __proto__: null,
1770
+ existing: false,
1771
+ ..._include
1772
+ };
1773
+ const needInfoOn = getDetailsFromDiff(arb.diff, {
1774
+ include: {
1775
+ unchanged: include.existing
1776
+ }
1777
+ });
1778
+ const pkgIds = arrays.arrayUnique(needInfoOn.map(d => d.node.pkgid));
1779
+ let {
1780
+ length: remaining
1781
+ } = pkgIds;
1782
+ const alertsByPkgId = new Map();
1783
+ if (!remaining) {
1784
+ return alertsByPkgId;
1511
1785
  }
1512
- return _translations;
1513
- }
1514
- function hasOverride(pkgJson, name) {
1515
- return !!(pkgJson?.[OVERRIDES]?.[name] || pkgJson?.[RESOLUTIONS]?.[name] || pkgJson?.[PNPM]?.[OVERRIDES]?.[name]);
1786
+ const getText = () => `Looking up data for ${remaining} packages`;
1787
+ spinner?.start(getText());
1788
+ let overrides;
1789
+ const overridesMap = (arb.actualTree ?? arb.idealTree ?? (await arb.loadActual()))?.overrides?.children;
1790
+ if (overridesMap) {
1791
+ overrides = Object.fromEntries([...overridesMap.entries()].map(([key, overrideSet]) => {
1792
+ return [key, overrideSet.value];
1793
+ }));
1794
+ }
1795
+ const toAlertsMapOptions = {
1796
+ overrides,
1797
+ ...options
1798
+ };
1799
+ for await (const artifact of batchScan(pkgIds)) {
1800
+ await addArtifactToAlertsMap(artifact, alertsByPkgId, toAlertsMapOptions);
1801
+ remaining -= 1;
1802
+ if (spinner && remaining > 0) {
1803
+ spinner.start();
1804
+ spinner.setText(getText());
1805
+ }
1806
+ }
1807
+ spinner?.stop();
1808
+ return alertsByPkgId;
1516
1809
  }
1517
1810
  function updateNode(node, packument, vulnerableVersionRange, firstPatchedVersionIdentifier) {
1518
1811
  const availableVersions = Object.keys(packument.versions);
@@ -1573,208 +1866,6 @@ function updateNode(node, packument, vulnerableVersionRange, firstPatchedVersion
1573
1866
  }
1574
1867
  return true;
1575
1868
  }
1576
- async function getPackagesAlerts(arb, options) {
1577
- const {
1578
- consolidate = false,
1579
- includeExisting = false,
1580
- includeUnfixable = true,
1581
- includeUpgrades = false,
1582
- output
1583
- } = {
1584
- __proto__: null,
1585
- ...options
1586
- };
1587
- const needInfoOn = getPackagesToQueryFromDiff(arb.diff, {
1588
- includeUnchanged: includeExisting
1589
- });
1590
- const purls = arrays.arrayUnique(needInfoOn.map(d => d.node.pkgid));
1591
- let {
1592
- length: remaining
1593
- } = purls;
1594
- const results = [];
1595
- if (!remaining) {
1596
- return results;
1597
- }
1598
- const pkgJson = (arb.actualTree ?? arb.idealTree).package;
1599
- const spinner$1 = output ? new spinner.Spinner({
1600
- stream: output
1601
- }) : undefined;
1602
- const getText = () => `Looking up data for ${remaining} packages`;
1603
- const decrementRemaining = () => {
1604
- remaining -= 1;
1605
- if (spinner$1 && remaining > 0) {
1606
- spinner$1.start();
1607
- spinner$1.setText(getText());
1608
- }
1609
- };
1610
- spinner$1?.start(getText());
1611
- for await (const artifact of batchScan(purls)) {
1612
- if (!artifact.name || !artifact.version || !artifact.alerts?.length) {
1613
- decrementRemaining();
1614
- continue;
1615
- }
1616
- const name = packages.resolvePackageName(artifact);
1617
- const {
1618
- version
1619
- } = artifact;
1620
- let displayWarning = false;
1621
- let sockPkgAlerts = [];
1622
- for (const alert of artifact.alerts) {
1623
- // eslint-disable-next-line no-await-in-loop
1624
- const ux = await uxLookup({
1625
- package: {
1626
- name,
1627
- version
1628
- },
1629
- alert: {
1630
- type: alert.type
1631
- }
1632
- });
1633
- if (ux.display) {
1634
- displayWarning = !!output;
1635
- }
1636
- const fixableCve = isArtifactAlertCveFixable(alert);
1637
- const fixableUpgrade = isArtifactAlertUpgradeFixable(alert);
1638
- if (includeUnfixable || fixableCve || includeUpgrades && fixableUpgrade && !hasOverride(pkgJson, name)) {
1639
- sockPkgAlerts.push({
1640
- name,
1641
- version,
1642
- key: alert.key,
1643
- type: alert.type,
1644
- block: ux.block,
1645
- raw: alert,
1646
- fixable: fixableCve || fixableUpgrade
1647
- });
1648
- }
1649
- }
1650
- if (!includeExisting && sockPkgAlerts.length) {
1651
- // Before we ask about problematic issues, check to see if they
1652
- // already existed in the old version if they did, be quiet.
1653
- const allExisting = needInfoOn.filter(d => d.existing?.pkgid.startsWith(`${name}@`));
1654
- for (const {
1655
- existing
1656
- } of allExisting) {
1657
- const oldAlerts =
1658
- // eslint-disable-next-line no-await-in-loop
1659
- (await batchScan([existing.pkgid]).next()).value?.alerts;
1660
- if (oldAlerts?.length) {
1661
- // SocketArtifactAlert and SocketPackageAlert both have the 'key' property.
1662
- sockPkgAlerts = sockPkgAlerts.filter(({
1663
- key
1664
- }) => !oldAlerts.find(a => a.key === key));
1665
- }
1666
- }
1667
- }
1668
- if (consolidate && sockPkgAlerts.length) {
1669
- const highestForCve = new Map();
1670
- const highestForUpgrade = new Map();
1671
- const unfixableAlerts = [];
1672
- for (const sockPkgAlert of sockPkgAlerts) {
1673
- if (isArtifactAlertCveFixable(sockPkgAlert.raw)) {
1674
- const version = sockPkgAlert.raw.props[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER];
1675
- const major = semver.major(version);
1676
- const highest = highestForCve.get(major)?.raw[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER] ?? '0.0.0';
1677
- if (semver.gt(version, highest)) {
1678
- highestForCve.set(major, sockPkgAlert);
1679
- }
1680
- } else if (isArtifactAlertUpgradeFixable(sockPkgAlert.raw)) {
1681
- const {
1682
- version
1683
- } = sockPkgAlert;
1684
- const major = semver.major(version);
1685
- const highest = highestForUpgrade.get(major)?.version ?? '0.0.0';
1686
- if (semver.gt(version, highest)) {
1687
- highestForUpgrade.set(major, sockPkgAlert);
1688
- }
1689
- } else {
1690
- unfixableAlerts.push(sockPkgAlert);
1691
- }
1692
- }
1693
- sockPkgAlerts = [...unfixableAlerts, ...highestForCve.values(), ...highestForUpgrade.values()];
1694
- }
1695
- sockPkgAlerts.sort((a, b) => sorts.naturalCompare(a.type, b.type));
1696
- spinner$1?.stop();
1697
- if (displayWarning && sockPkgAlerts.length) {
1698
- const lines = new Set();
1699
- const translations = getTranslations();
1700
- for (const sockPkgAlert of sockPkgAlerts) {
1701
- const attributes = [...(sockPkgAlert.fixable ? ['fixable'] : []), ...(sockPkgAlert.block ? [] : ['non-blocking'])];
1702
- const maybeAttributes = attributes.length ? ` (${attributes.join('; ')})` : '';
1703
- // Based data from { pageProps: { alertTypes } } of:
1704
- // https://socket.dev/_next/data/94666139314b6437ee4491a0864e72b264547585/en-US.json
1705
- const info = translations.alerts[sockPkgAlert.type];
1706
- const title = info?.title ?? sockPkgAlert.type;
1707
- const maybeDesc = info?.description ? ` - ${info.description}` : '';
1708
- // TODO: emoji seems to mis-align terminals sometimes
1709
- lines.add(` ${title}${maybeAttributes}${maybeDesc}\n`);
1710
- }
1711
- output?.write(`(socket) ${formatter.hyperlink(`${name}@${version}`, getSocketDevPackageOverviewUrl(NPM, name, version))} contains risks:\n`);
1712
- for (const line of lines) {
1713
- output?.write(line);
1714
- }
1715
- }
1716
- results.push(...sockPkgAlerts);
1717
- decrementRemaining();
1718
- }
1719
- spinner$1?.stop();
1720
- return results;
1721
- }
1722
- function getCveInfoByPackage(alerts, options) {
1723
- const {
1724
- excludeUpgrades
1725
- } = {
1726
- __proto__: null,
1727
- ...options
1728
- };
1729
- let infoByPkg = null;
1730
- for (const alert of alerts) {
1731
- if (!isArtifactAlertCveFixable(alert.raw) || excludeUpgrades && registry.getManifestData(NPM, alert.name)) {
1732
- continue;
1733
- }
1734
- if (!infoByPkg) {
1735
- infoByPkg = new Map();
1736
- }
1737
- const {
1738
- name
1739
- } = alert;
1740
- let infos = infoByPkg.get(name);
1741
- if (!infos) {
1742
- infos = [];
1743
- infoByPkg.set(name, infos);
1744
- }
1745
- const {
1746
- firstPatchedVersionIdentifier,
1747
- vulnerableVersionRange
1748
- } = alert.raw.props;
1749
- infos.push({
1750
- firstPatchedVersionIdentifier,
1751
- vulnerableVersionRange
1752
- });
1753
- }
1754
- return infoByPkg;
1755
- }
1756
- const kCtorArgs = Symbol('ctorArgs');
1757
- const kRiskyReify = Symbol('riskyReify');
1758
- async function reify(arb, args, level = 1) {
1759
- const {
1760
- stderr: output,
1761
- stdin: input
1762
- } = process;
1763
- const alerts = await getPackagesAlerts(arb, {
1764
- output,
1765
- includeUnfixable: level < 2
1766
- });
1767
- if (alerts.length && !(await prompts.confirm({
1768
- message: 'Accept risks of installing these packages?',
1769
- default: false
1770
- }, {
1771
- input,
1772
- output
1773
- }))) {
1774
- throw new Error('Socket npm exiting due to risks');
1775
- }
1776
- return await arb[kRiskyReify](...args);
1777
- }
1778
1869
 
1779
1870
  const {
1780
1871
  SOCKET_CLI_SAFE_WRAPPER,
@@ -1783,7 +1874,6 @@ const {
1783
1874
  getIPC
1784
1875
  }
1785
1876
  } = constants;
1786
- const Arborist = require(npmPaths.getArboristClassPath());
1787
1877
  const SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = {
1788
1878
  __proto__: null,
1789
1879
  audit: false,
@@ -1795,13 +1885,16 @@ const SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = {
1795
1885
  saveBundle: false,
1796
1886
  silent: true
1797
1887
  };
1888
+ const kCtorArgs = Symbol('ctorArgs');
1889
+ const kRiskyReify = Symbol('riskyReify');
1890
+ const Arborist = require(shadowNpmPaths.getArboristClassPath());
1798
1891
 
1799
1892
  // Implementation code not related to our custom behavior is based on
1800
1893
  // https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/index.js:
1801
1894
  class SafeArborist extends Arborist {
1802
1895
  constructor(...ctorArgs) {
1803
1896
  super({
1804
- path: (ctorArgs.length ? ctorArgs[0]?.path : undefined) ?? process.cwd(),
1897
+ path: (ctorArgs.length ? ctorArgs[0]?.path : undefined) ?? process$1.cwd(),
1805
1898
  ...(ctorArgs.length ? ctorArgs[0] : undefined),
1806
1899
  ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES
1807
1900
  }, ...ctorArgs.slice(1));
@@ -1827,43 +1920,84 @@ class SafeArborist extends Arborist {
1827
1920
  __proto__: null,
1828
1921
  ...(args.length ? args[0] : undefined)
1829
1922
  };
1830
- if (options.dryRun) {
1831
- return await this[kRiskyReify](...args);
1832
- }
1833
- const level = await getIPC(SOCKET_CLI_SAFE_WRAPPER);
1923
+ const level = options.dryRun ? 0 : await getIPC(SOCKET_CLI_SAFE_WRAPPER);
1834
1924
  if (!level) {
1835
1925
  return await this[kRiskyReify](...args);
1836
1926
  }
1837
- const safeArgs = [{
1927
+ // Lazily access constants.spinner.
1928
+ const {
1929
+ spinner
1930
+ } = constants;
1931
+ await super.reify({
1838
1932
  ...options,
1933
+ ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES,
1839
1934
  progress: false
1840
- }, ...args.slice(1)];
1841
- Object.assign(options, SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES);
1842
- const old = args[0];
1843
- args[0] = options;
1844
- await super.reify(...safeArgs);
1845
- args[0] = old;
1846
- return await reify(this, args, level);
1935
+ },
1936
+ // @ts-ignore: TS gets grumpy about rest parameters.
1937
+ ...args.slice(1));
1938
+ const alertsMap = await getAlertsMapFromArborist(this, {
1939
+ spinner,
1940
+ include: {
1941
+ unfixable: level < 2
1942
+ }
1943
+ });
1944
+ if (alertsMap.size) {
1945
+ logAlertsMap(alertsMap, {
1946
+ output: process$1.stderr
1947
+ });
1948
+ if (!(await prompts.confirm({
1949
+ message: 'Accept risks of installing these packages?',
1950
+ default: false
1951
+ }))) {
1952
+ throw new Error('Socket npm exiting due to risks');
1953
+ }
1954
+ } else {
1955
+ logger.logger.success('Socket npm found no risks!');
1956
+ }
1957
+ return await this[kRiskyReify](...args);
1847
1958
  }
1848
1959
  }
1849
1960
 
1961
+ function installSafeArborist() {
1962
+ // Override '@npmcli/arborist' module exports with patched variants based on
1963
+ // https://github.com/npm/cli/pull/8089.
1964
+ const cache = require.cache;
1965
+ cache[shadowNpmPaths.getArboristClassPath()] = {
1966
+ exports: SafeArborist
1967
+ };
1968
+ cache[shadowNpmPaths.getArboristEdgeClassPath()] = {
1969
+ exports: SafeEdge
1970
+ };
1971
+ cache[shadowNpmPaths.getArboristNodeClassPath()] = {
1972
+ exports: SafeNode
1973
+ };
1974
+ cache[shadowNpmPaths.getArboristOverrideSetClassPath()] = {
1975
+ exports: SafeOverrideSet
1976
+ };
1977
+ }
1978
+
1979
+ installSafeArborist();
1980
+
1850
1981
  exports.Arborist = Arborist;
1851
1982
  exports.AuthError = AuthError;
1852
1983
  exports.ColorOrMarkdown = ColorOrMarkdown;
1853
1984
  exports.InputError = InputError;
1854
1985
  exports.SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES;
1986
+ exports.SEVERITY = SEVERITY;
1855
1987
  exports.SafeArborist = SafeArborist;
1856
- exports.SafeEdge = SafeEdge;
1857
- exports.SafeNode = SafeNode;
1858
- exports.SafeOverrideSet = SafeOverrideSet;
1988
+ exports.addArtifactToAlertsMap = addArtifactToAlertsMap;
1989
+ exports.batchScan = batchScan;
1859
1990
  exports.captureException = captureException;
1991
+ exports.findBestPatchVersion = findBestPatchVersion;
1860
1992
  exports.findPackageNodes = findPackageNodes;
1861
1993
  exports.findUp = findUp;
1862
- exports.getCveInfoByPackage = getCveInfoByPackage;
1994
+ exports.formatSeverityCount = formatSeverityCount;
1995
+ exports.getAlertsMapFromArborist = getAlertsMapFromArborist;
1996
+ exports.getCveInfoByAlertsMap = getCveInfoByAlertsMap;
1863
1997
  exports.getDefaultToken = getDefaultToken;
1864
- exports.getPackagesAlerts = getPackagesAlerts;
1865
1998
  exports.getPublicToken = getPublicToken;
1866
1999
  exports.getSetting = getSetting;
2000
+ exports.getSeverityCount = getSeverityCount;
1867
2001
  exports.getSocketDevAlertUrl = getSocketDevAlertUrl;
1868
2002
  exports.getSocketDevPackageOverviewUrl = getSocketDevPackageOverviewUrl;
1869
2003
  exports.readFileBinary = readFileBinary;
@@ -1872,5 +2006,5 @@ exports.safeReadFile = safeReadFile;
1872
2006
  exports.setupSdk = setupSdk;
1873
2007
  exports.updateNode = updateNode;
1874
2008
  exports.updateSetting = updateSetting;
1875
- //# debugId=32696061-6e8b-4f74-95c9-9ae9ce6c9c1c
1876
- //# sourceMappingURL=index.js.map
2009
+ //# debugId=1b6e4e80-401e-49a9-9b56-3f777bfba08f
2010
+ //# sourceMappingURL=shadow-npm-inject.js.map