socket 0.14.39 → 0.14.40-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,210 +15,18 @@ var https = require('node:https');
15
15
  var path = require('node:path');
16
16
  var readline = require('node:readline');
17
17
  var promises = require('node:timers/promises');
18
- var prompts = require('@socketsecurity/registry/lib/prompts');
19
18
  var yoctoSpinner = require('@socketregistry/yocto-spinner');
20
- var vendor = require('./vendor.js');
21
- var npa = _socketInterop(require('npm-package-arg'));
22
- var semver = _socketInterop(require('semver'));
23
19
  var config = require('@socketsecurity/config');
20
+ var registry = require('@socketsecurity/registry');
24
21
  var objects = require('@socketsecurity/registry/lib/objects');
25
22
  var packages = require('@socketsecurity/registry/lib/packages');
26
- var net = require('node:net');
27
- var os = require('node:os');
28
- var node_stream = require('node:stream');
29
- var sdk = require('./sdk.js');
23
+ var prompts = require('@socketsecurity/registry/lib/prompts');
24
+ var npa = _socketInterop(require('npm-package-arg'));
25
+ var semver = _socketInterop(require('semver'));
30
26
  var constants = require('./constants.js');
27
+ var sdk = require('./sdk.js');
31
28
  var pathResolve = require('./path-resolve.js');
32
29
 
33
- var version = "0.14.39";
34
-
35
- const NEWLINE_CHAR_CODE = 10; /*'\n'*/
36
-
37
- const TTY_IPC = process.env['SOCKET_SECURITY_TTY_IPC'];
38
- const sock = path.join(os.tmpdir(), `socket-security-tty-${process.pid}.sock`);
39
- process.env['SOCKET_SECURITY_TTY_IPC'] = sock;
40
- function createNonStandardTTYServer() {
41
- return {
42
- async captureTTY(mutexFn) {
43
- return await new Promise((resolve, reject) => {
44
- const conn = net.createConnection({
45
- path: TTY_IPC
46
- }).on('error', reject);
47
- let captured = false;
48
- const buffs = [];
49
- conn.on('data', function awaitCapture(chunk) {
50
- buffs.push(chunk);
51
- let lineBuff = Buffer.concat(buffs);
52
- if (captured) return;
53
- try {
54
- const eolIndex = lineBuff.indexOf(NEWLINE_CHAR_CODE);
55
- if (eolIndex !== -1) {
56
- conn.removeListener('data', awaitCapture);
57
- conn.push(lineBuff.slice(eolIndex + 1));
58
- const {
59
- capabilities: {
60
- input: hasInput,
61
- output: hasOutput
62
- },
63
- ipc_version: remote_ipc_version
64
- } = JSON.parse(lineBuff.subarray(0, eolIndex).toString('utf8'));
65
- lineBuff = null;
66
- captured = true;
67
- if (remote_ipc_version !== version) {
68
- throw new Error('Mismatched STDIO tunnel IPC version, ensure you only have 1 version of socket CLI being called.');
69
- }
70
- const input = hasInput ? new node_stream.PassThrough() : null;
71
- input?.pause();
72
- if (input) conn.pipe(input);
73
- const output = hasOutput ? new node_stream.PassThrough() : null;
74
- if (output) {
75
- output.pipe(conn)
76
- // Make ora happy
77
- ;
78
- output.isTTY = true;
79
- output.cursorTo = function cursorTo(x, y, callback) {
80
- readline.cursorTo(this, x, y, callback);
81
- };
82
- output.clearLine = function clearLine(dir, callback) {
83
- readline.clearLine(this, dir, callback);
84
- };
85
- }
86
- mutexFn(hasInput ? input : undefined, hasOutput ? output : undefined).then(resolve, reject).finally(() => {
87
- conn.unref();
88
- conn.end();
89
- input?.end();
90
- output?.end();
91
- // process.exit(13)
92
- });
93
- }
94
- } catch (e) {
95
- reject(e);
96
- }
97
- });
98
- });
99
- }
100
- };
101
- }
102
- function createIPCServer(captureState, npmlog) {
103
- const input = process.stdin;
104
- const output = process.stderr;
105
- return new Promise((resolve, reject) => {
106
- const server = net
107
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
108
- .createServer(async conn => {
109
- if (captureState.captured) {
110
- await new Promise(resolve => {
111
- captureState.pendingCaptures.push({
112
- resolve() {
113
- resolve();
114
- }
115
- });
116
- });
117
- } else {
118
- captureState.captured = true;
119
- }
120
- const wasProgressEnabled = npmlog.progressEnabled;
121
- npmlog.pause();
122
- if (wasProgressEnabled) {
123
- npmlog.disableProgress();
124
- }
125
- conn.write(`${JSON.stringify({
126
- ipc_version: version,
127
- capabilities: {
128
- input: Boolean(input),
129
- output: true
130
- }
131
- })}\n`);
132
- conn.on('data', data => {
133
- output.write(data);
134
- }).on('error', e => {
135
- output.write(`there was an error prompting from a sub shell (${e?.message}), socket npm closing`);
136
- process.exit(1);
137
- });
138
- input.on('data', data => {
139
- conn.write(data);
140
- }).on('end', () => {
141
- conn.unref();
142
- conn.end();
143
- if (wasProgressEnabled) {
144
- npmlog.enableProgress();
145
- }
146
- npmlog.resume();
147
- captureState.nextCapture();
148
- });
149
- }).listen(sock, () => resolve(server)).on('error', reject).unref();
150
- process.on('exit', () => {
151
- server.close();
152
- tryUnlinkSync(sock);
153
- });
154
- resolve(server);
155
- });
156
- }
157
- function createStandardTTYServer(isInteractive, npmlog) {
158
- const captureState = {
159
- captured: false,
160
- nextCapture: () => {
161
- if (captureState.pendingCaptures.length > 0) {
162
- const pendingCapture = captureState.pendingCaptures.shift();
163
- pendingCapture?.resolve();
164
- } else {
165
- captureState.captured = false;
166
- }
167
- },
168
- pendingCaptures: []
169
- };
170
- tryUnlinkSync(sock);
171
- const input = isInteractive ? process.stdin : undefined;
172
- const output = process.stderr;
173
- let ipcServerPromise;
174
- if (input) {
175
- ipcServerPromise = createIPCServer(captureState, npmlog);
176
- }
177
- return {
178
- async captureTTY(mutexFn) {
179
- await ipcServerPromise;
180
- if (captureState.captured) {
181
- const captured = new Promise(resolve => {
182
- captureState.pendingCaptures.push({
183
- resolve() {
184
- resolve();
185
- }
186
- });
187
- });
188
- await captured;
189
- } else {
190
- captureState.captured = true;
191
- }
192
- const wasProgressEnabled = npmlog.progressEnabled;
193
- try {
194
- npmlog.pause();
195
- if (wasProgressEnabled) {
196
- npmlog.disableProgress();
197
- }
198
- return await mutexFn(input, output);
199
- } finally {
200
- if (wasProgressEnabled) {
201
- npmlog.enableProgress();
202
- }
203
- npmlog.resume();
204
- captureState.nextCapture();
205
- }
206
- }
207
- };
208
- }
209
- function tryUnlinkSync(filepath) {
210
- try {
211
- fs.unlinkSync(filepath);
212
- } catch (e) {
213
- if (sdk.isErrnoException(e) && e.code !== 'ENOENT') {
214
- throw e;
215
- }
216
- }
217
- }
218
- function createTTYServer(isInteractive, npmlog) {
219
- return !isInteractive && TTY_IPC ? createNonStandardTTYServer() : createStandardTTYServer(isInteractive, npmlog);
220
- }
221
-
222
30
  //#region UX Constants
223
31
 
224
32
  const IGNORE_UX = {
@@ -239,10 +47,10 @@ const ERROR_UX = {
239
47
  /**
240
48
  * Iterates over all entries with ordered issue rule for deferral. Iterates over
241
49
  * all issue rules and finds the first defined value that does not defer otherwise
242
- * uses the defaultValue. Takes the value and converts into a UX workflow
50
+ * uses the defaultValue. Takes the value and converts into a UX workflow.
243
51
  */
244
52
  function resolveAlertRuleUX(orderedRulesCollection, defaultValue) {
245
- if (defaultValue === true || defaultValue == null) {
53
+ if (defaultValue === true || defaultValue === null || defaultValue === undefined) {
246
54
  defaultValue = {
247
55
  action: 'error'
248
56
  };
@@ -280,12 +88,13 @@ function resolveAlertRuleUX(orderedRulesCollection, defaultValue) {
280
88
  }
281
89
 
282
90
  /**
283
- * Negative form because it is narrowing the type
91
+ * Negative form because it is narrowing the type.
284
92
  */
285
93
  function ruleValueDoesNotDefer(rule) {
286
94
  if (rule === undefined) {
287
95
  return false;
288
- } else if (rule !== null && typeof rule === 'object') {
96
+ }
97
+ if (objects.isObject(rule)) {
289
98
  const {
290
99
  action
291
100
  } = rule;
@@ -297,7 +106,7 @@ function ruleValueDoesNotDefer(rule) {
297
106
  }
298
107
 
299
108
  /**
300
- * Handles booleans for backwards compatibility
109
+ * Handles booleans for backwards compatibility.
301
110
  */
302
111
  function uxForDefinedNonDeferValue(ruleValue) {
303
112
  if (typeof ruleValue === 'boolean') {
@@ -368,10 +177,12 @@ const {
368
177
  API_V0_URL,
369
178
  ENV,
370
179
  LOOP_SENTINEL,
180
+ NPM,
371
181
  NPM_REGISTRY_URL,
182
+ SOCKET_CLI_FIX_PACKAGE_LOCK_FILE,
372
183
  SOCKET_CLI_ISSUES_URL,
184
+ SOCKET_CLI_UPDATE_OVERRIDES_IN_PACKAGE_LOCK_FILE,
373
185
  SOCKET_PUBLIC_API_KEY,
374
- UPDATE_SOCKET_OVERRIDES_IN_PACKAGE_LOCK_FILE,
375
186
  abortSignal,
376
187
  rootPath
377
188
  } = constants;
@@ -417,16 +228,7 @@ const log = tryRequire([path.join(npmNmPath, 'proc-log/lib/index.js'),
417
228
  // The proc-log DefinitelyTyped definition is incorrect. The type definition
418
229
  // is really that of its export log.
419
230
  mod => mod.log], path.join(npmNmPath, 'npmlog/lib/log.js'));
420
- if (log === undefined) {
421
- console.error(`Unable to integrate with npm CLI logging infrastructure.\n\n${POTENTIAL_BUG_ERROR_MESSAGE}.`);
422
- // The exit code 127 indicates that the command or binary being executed
423
- // could not be found.
424
- process.exit(127);
425
- }
426
- const pacote = tryRequire(path.join(npmNmPath, 'pacote'), 'pacote');
427
- const {
428
- tarball
429
- } = pacote;
231
+ const pacote = require(path.join(npmNmPath, 'pacote'));
430
232
  const translations = require(path.join(rootPath, 'translations.json'));
431
233
  const Arborist = require(arboristClassPath);
432
234
  const depValid = require(arboristDepValidPath);
@@ -437,9 +239,6 @@ const kCtorArgs = Symbol('ctorArgs');
437
239
  const kRiskyReify = Symbol('riskyReify');
438
240
  const formatter = new sdk.ColorOrMarkdown(false);
439
241
  const pubToken = sdk.getDefaultKey() ?? SOCKET_PUBLIC_API_KEY;
440
- const ttyServer = createTTYServer(vendor.isInteractive({
441
- stream: process.stdin
442
- }), log);
443
242
  let _uxLookup;
444
243
  async function uxLookup(settings) {
445
244
  while (_uxLookup === undefined) {
@@ -450,6 +249,35 @@ async function uxLookup(settings) {
450
249
  }
451
250
  return _uxLookup(settings);
452
251
  }
252
+ function packageAlertsToReport(alerts) {
253
+ let report = null;
254
+ for (const alert of alerts) {
255
+ if (!isAlertFixableCve(alert.raw)) {
256
+ continue;
257
+ }
258
+ const {
259
+ name
260
+ } = alert;
261
+ if (!report) {
262
+ report = {};
263
+ }
264
+ if (!report[name]) {
265
+ report[name] = [];
266
+ }
267
+ const props = alert.raw?.props;
268
+ report[name].push({
269
+ id: -1,
270
+ url: props?.url,
271
+ title: props?.title,
272
+ severity: alert.raw?.severity?.toLowerCase(),
273
+ vulnerable_versions: props?.vulnerableVersionRange,
274
+ cwe: props?.cwes,
275
+ cvss: props?.csvs,
276
+ name
277
+ });
278
+ }
279
+ return report;
280
+ }
453
281
  async function* batchScan(pkgIds) {
454
282
  const req = https.request(`${API_V0_URL}/purl?alerts=true`, {
455
283
  method: 'POST',
@@ -527,17 +355,17 @@ function findSpecificOverrideSet(first, second) {
527
355
  overrideSet = overrideSet.parent;
528
356
  }
529
357
  // The override sets are incomparable. Neither one contains the other.
530
- log.silly('Conflicting override sets', first, second);
358
+ log?.silly('Conflicting override sets', first, second);
531
359
  return undefined;
532
360
  }
533
361
  function isAlertFixable(alert) {
362
+ return alert.type === 'socketUpgradeAvailable' || isAlertFixableCve(alert);
363
+ }
364
+ function isAlertFixableCve(alert) {
534
365
  const {
535
366
  type
536
367
  } = alert;
537
- if (type === 'cve' || type === 'mediumCVE' || type === 'mildCVE' || type === 'criticalCVE') {
538
- return !!alert.props?.['firstPatchedVersionIdentifier'];
539
- }
540
- return type === 'socketUpgradeAvailable';
368
+ return (type === 'cve' || type === 'mediumCVE' || type === 'mildCVE' || type === 'criticalCVE') && !!alert.props?.['firstPatchedVersionIdentifier'];
541
369
  }
542
370
  function maybeReadfileSync(filepath) {
543
371
  try {
@@ -545,7 +373,7 @@ function maybeReadfileSync(filepath) {
545
373
  } catch {}
546
374
  return undefined;
547
375
  }
548
- async function getPackagesAlerts(safeArb, _registry, pkgs, output) {
376
+ async function getPackagesAlerts(safeArb, pkgs, output) {
549
377
  const spinner = yoctoSpinner({
550
378
  stream: output
551
379
  });
@@ -598,17 +426,19 @@ async function getPackagesAlerts(safeArb, _registry, pkgs, output) {
598
426
  raw: alert,
599
427
  fixable: isAlertFixable(alert)
600
428
  });
601
- // Before we ask about problematic issues, check to see if they
602
- // already existed in the old version if they did, be quiet.
603
- const existing = pkgs.find(p => p.existing?.startsWith(`${name}@`))?.existing;
604
- if (existing) {
605
- const oldArtifact =
606
- // eslint-disable-next-line no-await-in-loop
607
- (await batchScan([existing]).next()).value;
608
- if (oldArtifact?.alerts?.length) {
609
- alerts = alerts.filter(({
610
- type
611
- }) => !oldArtifact.alerts?.find(a => a.type === type));
429
+ if (!ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
430
+ // Before we ask about problematic issues, check to see if they
431
+ // already existed in the old version if they did, be quiet.
432
+ const existing = pkgs.find(p => p.existing?.startsWith(`${name}@`))?.existing;
433
+ if (existing) {
434
+ const oldArtifact =
435
+ // eslint-disable-next-line no-await-in-loop
436
+ (await batchScan([existing]).next()).value;
437
+ if (oldArtifact?.alerts?.length) {
438
+ alerts = alerts.filter(({
439
+ type
440
+ }) => !oldArtifact.alerts?.find(a => a.type === type));
441
+ }
612
442
  }
613
443
  }
614
444
  }
@@ -616,7 +446,7 @@ async function getPackagesAlerts(safeArb, _registry, pkgs, output) {
616
446
  if (!blocked) {
617
447
  const pkg = pkgs.find(p => p.pkgid === id);
618
448
  if (pkg) {
619
- await tarball.stream(id, stream => {
449
+ await pacote.tarball.stream(id, stream => {
620
450
  stream.resume();
621
451
  return stream.promise();
622
452
  }, {
@@ -648,18 +478,20 @@ async function getPackagesAlerts(safeArb, _registry, pkgs, output) {
648
478
  spinner.text = remaining > 0 ? getText() : '';
649
479
  packageAlerts.push(...alerts);
650
480
  }
651
- } catch (e) {
652
- console.log('error', e);
653
481
  } finally {
654
482
  spinner.stop();
655
483
  }
656
484
  return packageAlerts;
657
485
  }
658
486
  function toRepoUrl(resolved) {
659
- return resolved.replace(/#[\s\S]*$/, '').replace(/\?[\s\S]*$/, '').replace(/\/[^/]*\/-\/[\s\S]*$/, '');
487
+ try {
488
+ return URL.parse(resolved)?.origin ?? '';
489
+ } catch {}
490
+ return '';
660
491
  }
661
- function walk(diff_, needInfoOn = []) {
662
- const queue = [diff_];
492
+ function walk(diff_) {
493
+ const needInfoOn = [];
494
+ const queue = [...diff_.children];
663
495
  let pos = 0;
664
496
  let {
665
497
  length: queueLength
@@ -669,9 +501,6 @@ function walk(diff_, needInfoOn = []) {
669
501
  throw new Error('Detected infinite loop while walking Arborist diff');
670
502
  }
671
503
  const diff = queue[pos++];
672
- if (!diff) {
673
- continue;
674
- }
675
504
  const {
676
505
  action
677
506
  } = diff;
@@ -700,10 +529,23 @@ function walk(diff_, needInfoOn = []) {
700
529
  });
701
530
  }
702
531
  }
703
- if (diff.children) {
704
- for (const child of diff.children) {
705
- queue[queueLength++] = child;
706
- }
532
+ for (const child of diff.children) {
533
+ queue[queueLength++] = child;
534
+ }
535
+ }
536
+ if (ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
537
+ const {
538
+ unchanged
539
+ } = diff_;
540
+ for (let i = 0, {
541
+ length
542
+ } = unchanged; i < length; i += 1) {
543
+ const pkgNode = unchanged[i];
544
+ needInfoOn.push({
545
+ existing: pkgNode.pkgid,
546
+ pkgid: pkgNode.pkgid,
547
+ repository_url: toRepoUrl(pkgNode.resolved)
548
+ });
707
549
  }
708
550
  }
709
551
  return needInfoOn;
@@ -713,7 +555,7 @@ function walk(diff_, needInfoOn = []) {
713
555
  // have access to. So we have to recreate any functionality that relies on those
714
556
  // private properties and use our own "safe" prefixed non-conflicting private
715
557
  // properties. Implementation code not related to patch https://github.com/npm/cli/pull/7025
716
- // is based on https://github.com/npm/cli/blob/v10.9.0/workspaces/arborist/lib/edge.js.
558
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/edge.js.
717
559
  //
718
560
  // The npm application
719
561
  // Copyright (c) npm, Inc. and Contributors
@@ -721,6 +563,7 @@ function walk(diff_, needInfoOn = []) {
721
563
  //
722
564
  // An edge in the dependency graph.
723
565
  // Represents a dependency relationship of some kind.
566
+ const initializedSafeEdges = new WeakSet();
724
567
  class SafeEdge extends Edge {
725
568
  #safeAccept;
726
569
  #safeError;
@@ -739,11 +582,15 @@ class SafeEdge extends Edge {
739
582
  if (accept !== undefined) {
740
583
  this.#safeAccept = accept || '*';
741
584
  }
585
+ if (from.constructor !== SafeNode) {
586
+ Reflect.setPrototypeOf(from, SafeNode.prototype);
587
+ }
742
588
  this.#safeError = null;
743
589
  this.#safeExplanation = null;
744
590
  this.#safeFrom = from;
745
591
  this.#safeName = name;
746
592
  this.#safeTo = null;
593
+ initializedSafeEdges.add(this);
747
594
  this.reload(true);
748
595
  }
749
596
  get accept() {
@@ -875,8 +722,11 @@ class SafeEdge extends Edge {
875
722
  return this.#safeExplanation;
876
723
  }
877
724
  reload(hard = false) {
725
+ if (!initializedSafeEdges.has(this)) {
726
+ // Skip if called during super constructor.
727
+ return;
728
+ }
878
729
  this.#safeExplanation = null;
879
-
880
730
  // Patch adding newOverrideSet and oldOverrideSet is based on
881
731
  // https://github.com/npm/cli/pull/7025.
882
732
  let newOverrideSet;
@@ -899,17 +749,15 @@ class SafeEdge extends Edge {
899
749
  }
900
750
  const newTo = this.#safeFrom?.resolve(this.name);
901
751
  if (newTo !== this.#safeTo) {
902
- if (this.#safeTo) {
903
- // Patch replacing
904
- // this.#safeTo.edgesIn.delete(this)
905
- // is based on https://github.com/npm/cli/pull/7025.
906
- this.#safeTo.deleteEdgeIn(this);
907
- }
752
+ // Patch replacing
753
+ // if (this.#safeTo) {
754
+ // this.#safeTo.edgesIn.delete(this)
755
+ // }
756
+ // is based on https://github.com/npm/cli/pull/7025.
757
+ this.#safeTo?.deleteEdgeIn(this);
908
758
  this.#safeTo = newTo ?? null;
909
759
  this.#safeError = null;
910
- if (this.#safeTo) {
911
- this.#safeTo.addEdgeIn(this);
912
- }
760
+ this.#safeTo?.addEdgeIn(this);
913
761
  } else if (hard) {
914
762
  this.#safeError = null;
915
763
  }
@@ -966,7 +814,7 @@ class SafeEdge extends Edge {
966
814
  }
967
815
 
968
816
  // Implementation code not related to patch https://github.com/npm/cli/pull/7025
969
- // is based on https://github.com/npm/cli/blob/v10.9.0/workspaces/arborist/lib/node.js:
817
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/node.js:
970
818
  class SafeNode extends Node {
971
819
  // Return true if it's safe to remove this node, because anything that is
972
820
  // depending on it would be fine with the thing that they would resolve to if
@@ -1066,6 +914,8 @@ class SafeNode extends Node {
1066
914
  }
1067
915
  return result;
1068
916
  }
917
+
918
+ // Patch adding deleteEdgeIn is based on https://github.com/npm/cli/pull/7025.
1069
919
  deleteEdgeIn(edge) {
1070
920
  this.edgesIn.delete(edge);
1071
921
  const {
@@ -1194,7 +1044,7 @@ class SafeNode extends Node {
1194
1044
  }
1195
1045
  // This is an error condition. We can only get here if the new override set
1196
1046
  // is in conflict with the existing.
1197
- log.silly('Conflicting override sets', this.name);
1047
+ log?.silly('Conflicting override sets', this.name);
1198
1048
  return false;
1199
1049
  }
1200
1050
 
@@ -1234,7 +1084,7 @@ class SafeNode extends Node {
1234
1084
  }
1235
1085
 
1236
1086
  // Implementation code not related to patch https://github.com/npm/cli/pull/7025
1237
- // is based on https://github.com/npm/cli/blob/v10.9.0/workspaces/arborist/lib/override-set.js:
1087
+ // is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/override-set.js:
1238
1088
  class SafeOverrideSet extends OverrideSet {
1239
1089
  // Patch adding childrenAreEqual is based on
1240
1090
  // https://github.com/npm/cli/pull/7025.
@@ -1337,7 +1187,7 @@ class SafeOverrideSet extends OverrideSet {
1337
1187
  }
1338
1188
 
1339
1189
  // Implementation code not related to our custom behavior is based on
1340
- // https://github.com/npm/cli/blob/v10.9.0/workspaces/arborist/lib/arborist/index.js:
1190
+ // https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/index.js:
1341
1191
  class SafeArborist extends Arborist {
1342
1192
  constructor(...ctorArgs) {
1343
1193
  const mutedArguments = [{
@@ -1356,6 +1206,7 @@ class SafeArborist extends Arborist {
1356
1206
  async [kRiskyReify](...args) {
1357
1207
  // SafeArborist has suffered side effects and must be rebuilt from scratch.
1358
1208
  const arb = new Arborist(...this[kCtorArgs]);
1209
+ arb.idealTree = this.idealTree;
1359
1210
  const ret = await arb.reify(...args);
1360
1211
  Object.assign(this, arb);
1361
1212
  return ret;
@@ -1379,46 +1230,179 @@ class SafeArborist extends Arborist {
1379
1230
  options.dryRun = true;
1380
1231
  options['save'] = false;
1381
1232
  options['saveBundle'] = false;
1382
- // TODO: Make this deal w/ any refactor to private fields by punching the
1233
+ // TODO: Make this deal with any refactor to private fields by punching the
1383
1234
  // class itself.
1384
1235
  await super.reify(...args);
1385
- const diff = walk(this['diff']);
1386
1236
  options.dryRun = old.dryRun;
1387
1237
  options['save'] = old.save;
1388
1238
  options['saveBundle'] = old.saveBundle;
1389
- // Nothing to check, mmm already installed or all private?
1390
- if (diff.findIndex(c => c.repository_url === NPM_REGISTRY_URL) === -1) {
1239
+ const needInfoOn = walk(this['diff']);
1240
+ if (needInfoOn.findIndex(c => c.repository_url === NPM_REGISTRY_URL) === -1) {
1241
+ // Nothing to check, hmmm already installed or all private?
1391
1242
  return await this[kRiskyReify](...args);
1392
1243
  }
1393
- let proceed = ENV[UPDATE_SOCKET_OVERRIDES_IN_PACKAGE_LOCK_FILE];
1394
- if (!proceed) {
1395
- proceed = await ttyServer.captureTTY(async (input, output) => {
1396
- if (input && output) {
1397
- const alerts = await getPackagesAlerts(this, this['registry'], diff, output);
1398
- if (!alerts.length) {
1399
- return true;
1400
- }
1401
- return await prompts.confirm({
1402
- message: 'Accept risks of installing these packages?',
1403
- default: false
1404
- }, {
1405
- input,
1406
- output,
1407
- signal: abortSignal
1408
- });
1409
- } else if ((await getPackagesAlerts(this, this['registry'], diff, output)).length > 0) {
1410
- throw new Error('Socket npm Unable to prompt to accept risk, need TTY to do so');
1411
- }
1244
+ const input = process.stdin;
1245
+ const output = process.stderr;
1246
+ let alerts;
1247
+ const proceed = ENV[SOCKET_CLI_UPDATE_OVERRIDES_IN_PACKAGE_LOCK_FILE] || (await (async () => {
1248
+ alerts = await getPackagesAlerts(this, needInfoOn, output);
1249
+ if (!alerts.length || ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE]) {
1412
1250
  return true;
1251
+ }
1252
+ return await prompts.confirm({
1253
+ message: 'Accept risks of installing these packages?',
1254
+ default: false
1255
+ }, {
1256
+ input,
1257
+ output,
1258
+ signal: abortSignal
1413
1259
  });
1414
- }
1260
+ })());
1415
1261
  if (proceed) {
1262
+ const fix = !!alerts?.length && (ENV[SOCKET_CLI_FIX_PACKAGE_LOCK_FILE] || (await prompts.confirm({
1263
+ message: 'Try to fix alerts?',
1264
+ default: true
1265
+ }, {
1266
+ input,
1267
+ output,
1268
+ signal: abortSignal
1269
+ })));
1270
+ if (fix) {
1271
+ await updateAdvisoryDependencies(this, alerts);
1272
+ }
1416
1273
  return await this[kRiskyReify](...args);
1417
1274
  } else {
1418
1275
  throw new Error('Socket npm exiting due to risks');
1419
1276
  }
1420
1277
  }
1421
1278
  }
1279
+ async function updateAdvisoryDependencies(arb, alerts) {
1280
+ const report = packageAlertsToReport(alerts);
1281
+ if (!report) {
1282
+ // No advisories to process.
1283
+ return;
1284
+ }
1285
+ await arb.buildIdealTree();
1286
+ const tree = arb.idealTree;
1287
+ for (const name of Object.keys(report)) {
1288
+ const advisories = report[name];
1289
+ const node = findPackageRecursively(tree, name);
1290
+ if (!node) {
1291
+ // Package not found in the tree.
1292
+ continue;
1293
+ }
1294
+ const {
1295
+ version
1296
+ } = node;
1297
+ const majorVerNum = semver.major(version);
1298
+
1299
+ // Fetch packument to get available versions.
1300
+ // eslint-disable-next-line no-await-in-loop
1301
+ const packument = await packages.fetchPackagePackument(name);
1302
+ const availableVersions = packument ? Object.keys(packument.versions) : [];
1303
+ for (const advisory of advisories) {
1304
+ const {
1305
+ vulnerable_versions
1306
+ } = advisory;
1307
+ // Find the highest non-vulnerable version within the same major range
1308
+ const targetVersion = findBestPatchVersion(name, availableVersions, majorVerNum, vulnerable_versions);
1309
+ const targetPackument = targetVersion ? packument.versions[targetVersion] : undefined;
1310
+ // Check !targetVersion to make TypeScript happy.
1311
+ if (!targetVersion || !targetPackument) {
1312
+ // No suitable patch version found.
1313
+ continue;
1314
+ }
1315
+
1316
+ // Use Object.defineProperty to override the version.
1317
+ Object.defineProperty(node, 'version', {
1318
+ configurable: true,
1319
+ enumerable: true,
1320
+ get: () => targetVersion
1321
+ });
1322
+ node.package.version = targetVersion;
1323
+ // Update resolved and clear integrity for the new version.
1324
+ node.resolved = `https://registry.npmjs.org/${name}/-/${name}-${targetVersion}.tgz`;
1325
+ if (node.integrity) {
1326
+ delete node.integrity;
1327
+ }
1328
+ if ('deprecated' in targetPackument) {
1329
+ node.package['deprecated'] = targetPackument.deprecated;
1330
+ } else {
1331
+ delete node.package['deprecated'];
1332
+ }
1333
+ const newDeps = {
1334
+ ...targetPackument.dependencies
1335
+ };
1336
+ const {
1337
+ dependencies: oldDeps
1338
+ } = node.package;
1339
+ node.package.dependencies = newDeps;
1340
+ if (oldDeps) {
1341
+ for (const oldDepName of Object.keys(oldDeps)) {
1342
+ if (!objects.hasOwn(newDeps, oldDepName)) {
1343
+ node.edgesOut.get(oldDepName)?.detach();
1344
+ }
1345
+ }
1346
+ }
1347
+ for (const newDepName of Object.keys(newDeps)) {
1348
+ if (!objects.hasOwn(oldDeps, newDepName)) {
1349
+ node.addEdgeOut(new Edge({
1350
+ from: node,
1351
+ name: newDepName,
1352
+ spec: newDeps[newDepName],
1353
+ type: 'prod'
1354
+ }));
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+ function findPackageRecursively(tree, packageName) {
1361
+ const queue = [{
1362
+ node: tree,
1363
+ depth: 0
1364
+ }];
1365
+ let sentinel = 0;
1366
+ while (queue.length) {
1367
+ if (sentinel++ === LOOP_SENTINEL) {
1368
+ throw new Error('Detected infinite loop in findPackageRecursively');
1369
+ }
1370
+ const {
1371
+ depth,
1372
+ node: currentNode
1373
+ } = queue.pop();
1374
+ const node = currentNode.children.get(packageName);
1375
+ if (node) {
1376
+ // Found package.
1377
+ return node;
1378
+ }
1379
+ const children = [...currentNode.children.values()];
1380
+ for (let i = children.length - 1; i >= 0; i -= 1) {
1381
+ queue.push({
1382
+ node: children[i],
1383
+ depth: depth + 1
1384
+ });
1385
+ }
1386
+ }
1387
+ return null;
1388
+ }
1389
+ function findBestPatchVersion(name, availableVersions, currentMajorVersion, vulnerableRange) {
1390
+ const manifestVersion = registry.getManifestData(NPM, name)?.version;
1391
+ // Filter versions that are within the current major version and are not in the vulnerable range
1392
+ const eligibleVersions = availableVersions.filter(version => {
1393
+ const isSameMajor = semver.major(version) === currentMajorVersion;
1394
+ const isNotVulnerable = !semver.satisfies(version, vulnerableRange);
1395
+ if (isSameMajor && isNotVulnerable) {
1396
+ return true;
1397
+ }
1398
+ return !!manifestVersion;
1399
+ });
1400
+ if (eligibleVersions.length === 0) {
1401
+ return null;
1402
+ }
1403
+ // Use semver to find the max satisfying version.
1404
+ return semver.maxSatisfying(eligibleVersions, '*');
1405
+ }
1422
1406
  function installSafeArborist() {
1423
1407
  const cache = require.cache;
1424
1408
  cache[arboristClassPath] = {
@@ -1435,7 +1419,10 @@ function installSafeArborist() {
1435
1419
  };
1436
1420
  }
1437
1421
  void (async () => {
1438
- const remoteSettings = await (async () => {
1422
+ const {
1423
+ orgs,
1424
+ settings
1425
+ } = await (async () => {
1439
1426
  try {
1440
1427
  const socketSdk = await sdk.setupSdk(pubToken);
1441
1428
  const orgResult = await socketSdk.getOrganizations();
@@ -1474,13 +1461,9 @@ void (async () => {
1474
1461
  throw e;
1475
1462
  }
1476
1463
  })();
1477
- const {
1478
- orgs,
1479
- settings
1480
- } = remoteSettings;
1481
- const enforcedOrgs = sdk.getSetting('enforcedOrgs') ?? [];
1482
1464
 
1483
1465
  // Remove any organizations not being enforced.
1466
+ const enforcedOrgs = sdk.getSetting('enforcedOrgs') ?? [];
1484
1467
  for (const {
1485
1468
  0: i,
1486
1469
  1: org