@socketsecurity/cli-with-sentry 1.1.18 → 1.1.20

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 (150) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/bin/npm-cli.js +2 -2
  3. package/bin/npx-cli.js +2 -2
  4. package/bin/pnpm-cli.js +2 -2
  5. package/bin/yarn-cli.js +2 -2
  6. package/dist/cli.js +285 -117
  7. package/dist/cli.js.map +1 -1
  8. package/dist/constants.js +8 -3
  9. package/dist/constants.js.map +1 -1
  10. package/dist/flags.js +3 -2
  11. package/dist/flags.js.map +1 -1
  12. package/dist/npm-cli.js +3 -4
  13. package/dist/npm-cli.js.map +1 -1
  14. package/dist/npx-cli.js +3 -3
  15. package/dist/npx-cli.js.map +1 -1
  16. package/dist/pnpm-cli.js +3 -3
  17. package/dist/pnpm-cli.js.map +1 -1
  18. package/dist/shadow-npm-bin.js +3 -106
  19. package/dist/shadow-npm-bin.js.map +1 -1
  20. package/dist/shadow-npm-bin2.js +125 -0
  21. package/dist/shadow-npm-bin2.js.map +1 -0
  22. package/dist/shadow-npx-bin.js +12 -0
  23. package/dist/shadow-npx-bin.js.map +1 -0
  24. package/dist/shadow-pnpm-bin.js +3 -228
  25. package/dist/shadow-pnpm-bin.js.map +1 -1
  26. package/dist/shadow-pnpm-bin2.js +323 -0
  27. package/dist/shadow-pnpm-bin2.js.map +1 -0
  28. package/dist/shadow-yarn-bin.js +62 -150
  29. package/dist/shadow-yarn-bin.js.map +1 -1
  30. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  31. package/dist/types/commands/analytics/cmd-analytics.d.mts.map +1 -1
  32. package/dist/types/commands/analytics/output-analytics.d.mts.map +1 -1
  33. package/dist/types/commands/audit-log/cmd-audit-log.d.mts.map +1 -1
  34. package/dist/types/commands/audit-log/output-audit-log.d.mts.map +1 -1
  35. package/dist/types/commands/ci/handle-ci.d.mts.map +1 -1
  36. package/dist/types/commands/config/cmd-config-auto.d.mts.map +1 -1
  37. package/dist/types/commands/config/cmd-config-get.d.mts.map +1 -1
  38. package/dist/types/commands/config/cmd-config-list.d.mts.map +1 -1
  39. package/dist/types/commands/config/cmd-config-set.d.mts.map +1 -1
  40. package/dist/types/commands/config/cmd-config-unset.d.mts.map +1 -1
  41. package/dist/types/commands/config/handle-config-set.d.mts.map +1 -1
  42. package/dist/types/commands/fix/cmd-fix.d.mts.map +1 -1
  43. package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
  44. package/dist/types/commands/fix/handle-fix.d.mts.map +1 -1
  45. package/dist/types/commands/fix/pull-request.d.mts.map +1 -1
  46. package/dist/types/commands/login/attempt-login.d.mts.map +1 -1
  47. package/dist/types/commands/manifest/cmd-manifest-conda.d.mts.map +1 -1
  48. package/dist/types/commands/npm/cmd-npm.d.mts.map +1 -1
  49. package/dist/types/commands/optimize/handle-optimize.d.mts.map +1 -1
  50. package/dist/types/commands/optimize/ls-by-agent.d.mts.map +1 -1
  51. package/dist/types/commands/organization/cmd-organization-dependencies.d.mts.map +1 -1
  52. package/dist/types/commands/organization/cmd-organization-list.d.mts.map +1 -1
  53. package/dist/types/commands/organization/handle-dependencies.d.mts.map +1 -1
  54. package/dist/types/commands/organization/handle-organization-list.d.mts.map +1 -1
  55. package/dist/types/commands/package/handle-purl-deep-score.d.mts.map +1 -1
  56. package/dist/types/commands/package/handle-purls-shallow-score.d.mts.map +1 -1
  57. package/dist/types/commands/pnpm/cmd-pnpm.d.mts.map +1 -1
  58. package/dist/types/commands/raw-npm/run-raw-npm.d.mts.map +1 -1
  59. package/dist/types/commands/raw-npx/run-raw-npx.d.mts.map +1 -1
  60. package/dist/types/commands/repository/cmd-repository-create.d.mts.map +1 -1
  61. package/dist/types/commands/repository/cmd-repository-del.d.mts.map +1 -1
  62. package/dist/types/commands/repository/cmd-repository-list.d.mts.map +1 -1
  63. package/dist/types/commands/repository/cmd-repository-update.d.mts.map +1 -1
  64. package/dist/types/commands/repository/cmd-repository-view.d.mts.map +1 -1
  65. package/dist/types/commands/repository/handle-create-repo.d.mts.map +1 -1
  66. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  67. package/dist/types/commands/scan/cmd-scan-diff.d.mts.map +1 -1
  68. package/dist/types/commands/scan/cmd-scan-list.d.mts.map +1 -1
  69. package/dist/types/commands/scan/create-scan-from-github.d.mts.map +1 -1
  70. package/dist/types/commands/scan/fetch-report-data.d.mts.map +1 -1
  71. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  72. package/dist/types/commands/scan/output-diff-scan.d.mts.map +1 -1
  73. package/dist/types/commands/scan/output-scan-view.d.mts.map +1 -1
  74. package/dist/types/commands/threat-feed/cmd-threat-feed.d.mts.map +1 -1
  75. package/dist/types/commands/wrapper/postinstall-wrapper.d.mts.map +1 -1
  76. package/dist/types/commands/yarn/cmd-yarn.d.mts.map +1 -1
  77. package/dist/types/constants.d.mts +1 -0
  78. package/dist/types/constants.d.mts.map +1 -1
  79. package/dist/types/flags.d.mts.map +1 -1
  80. package/dist/types/shadow/common.d.mts +31 -0
  81. package/dist/types/shadow/common.d.mts.map +1 -0
  82. package/dist/types/shadow/npm/bin.d.mts +4 -10
  83. package/dist/types/shadow/npm/bin.d.mts.map +1 -1
  84. package/dist/types/shadow/npm-base.d.mts +11 -0
  85. package/dist/types/shadow/npm-base.d.mts.map +1 -0
  86. package/dist/types/shadow/npx/bin.d.mts +5 -0
  87. package/dist/types/shadow/npx/bin.d.mts.map +1 -0
  88. package/dist/types/shadow/pnpm/bin.d.mts +1 -1
  89. package/dist/types/shadow/pnpm/bin.d.mts.map +1 -1
  90. package/dist/types/shadow/stdio-ipc.d.mts +7 -0
  91. package/dist/types/shadow/stdio-ipc.d.mts.map +1 -0
  92. package/dist/types/shadow/yarn/bin.d.mts +1 -1
  93. package/dist/types/shadow/yarn/bin.d.mts.map +1 -1
  94. package/dist/types/utils/agent.d.mts.map +1 -1
  95. package/dist/types/utils/alerts-map.d.mts.map +1 -1
  96. package/dist/types/utils/api.d.mts.map +1 -1
  97. package/dist/types/utils/cmd.d.mts.map +1 -1
  98. package/dist/types/utils/coana.d.mts.map +1 -1
  99. package/dist/types/utils/color-or-markdown.d.mts.map +1 -1
  100. package/dist/types/utils/config.d.mts.map +1 -1
  101. package/dist/types/utils/cve-to-ghsa.d.mts.map +1 -1
  102. package/dist/types/utils/debug.d.mts +45 -0
  103. package/dist/types/utils/debug.d.mts.map +1 -0
  104. package/dist/types/utils/determine-org-slug.d.mts.map +1 -1
  105. package/dist/types/utils/dlx.d.mts +1 -1
  106. package/dist/types/utils/dlx.d.mts.map +1 -1
  107. package/dist/types/utils/ecosystem.d.mts.map +1 -1
  108. package/dist/types/utils/errors.d.mts +48 -0
  109. package/dist/types/utils/errors.d.mts.map +1 -1
  110. package/dist/types/utils/filter-config.d.mts.map +1 -1
  111. package/dist/types/utils/fs.d.mts.map +1 -1
  112. package/dist/types/utils/get-output-kind.d.mts.map +1 -1
  113. package/dist/types/utils/git.d.mts.map +1 -1
  114. package/dist/types/utils/github.d.mts.map +1 -1
  115. package/dist/types/utils/markdown.d.mts +17 -0
  116. package/dist/types/utils/markdown.d.mts.map +1 -1
  117. package/dist/types/utils/meow-with-subcommands.d.mts.map +1 -1
  118. package/dist/types/utils/npm-package-arg.d.mts +5 -1
  119. package/dist/types/utils/npm-package-arg.d.mts.map +1 -1
  120. package/dist/types/utils/npm-paths.d.mts.map +1 -1
  121. package/dist/types/utils/npm-spec.d.mts +57 -0
  122. package/dist/types/utils/npm-spec.d.mts.map +1 -0
  123. package/dist/types/utils/output-formatting.d.mts.map +1 -1
  124. package/dist/types/utils/package-environment.d.mts.map +1 -1
  125. package/dist/types/utils/pnpm-paths.d.mts.map +1 -1
  126. package/dist/types/utils/purl-to-ghsa.d.mts.map +1 -1
  127. package/dist/types/utils/purl.d.mts +24 -0
  128. package/dist/types/utils/purl.d.mts.map +1 -1
  129. package/dist/types/utils/requirements.d.mts.map +1 -1
  130. package/dist/types/utils/sdk.d.mts.map +1 -1
  131. package/dist/types/utils/serialize-result-json.d.mts.map +1 -1
  132. package/dist/types/utils/socket-json.d.mts.map +1 -1
  133. package/dist/types/utils/socket-package-alert.d.mts.map +1 -1
  134. package/dist/types/utils/socket-url.d.mts.map +1 -1
  135. package/dist/types/utils/strings.d.mts +12 -0
  136. package/dist/types/utils/strings.d.mts.map +1 -1
  137. package/dist/types/utils/terminal-link.d.mts +45 -0
  138. package/dist/types/utils/terminal-link.d.mts.map +1 -0
  139. package/dist/types/utils/tildify.d.mts +0 -2
  140. package/dist/types/utils/tildify.d.mts.map +1 -1
  141. package/dist/types/utils/yarn-paths.d.mts.map +1 -1
  142. package/dist/types/utils/yarn-version.d.mts.map +1 -1
  143. package/dist/utils.js +1393 -453
  144. package/dist/utils.js.map +1 -1
  145. package/dist/vendor.js +682 -682
  146. package/package.json +3 -3
  147. package/shadow-bin/npm +2 -2
  148. package/shadow-bin/npx +2 -2
  149. package/shadow-bin/pnpm +2 -2
  150. package/shadow-bin/yarn +2 -2
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+
3
+ var fs = require('node:fs');
4
+ var path = require('node:path');
5
+ var require$$0 = require('node:url');
6
+ var require$$9 = require('../external/@socketsecurity/registry/lib/debug');
7
+ var logger = require('../external/@socketsecurity/registry/lib/logger');
8
+ var spawn = require('../external/@socketsecurity/registry/lib/spawn');
9
+ var constants = require('./constants.js');
10
+ var utils = require('./utils.js');
11
+ var shadowNpmBin = require('./shadow-npm-bin2.js');
12
+ var vendor = require('./vendor.js');
13
+
14
+ /**
15
+ * Extract package PURLs from add/dlx command arguments.
16
+ */
17
+ function extractPackagePurlsFromArgs(command, rawArgs, dlxCommands) {
18
+ const packagePurls = [];
19
+ const isDlxCommand = dlxCommands?.has(command);
20
+ if (command === 'add' || isDlxCommand) {
21
+ // For 'add package1 package2@version' or 'dlx package', get packages from args.
22
+ const packageArgs = rawArgs.slice(1).filter(arg => !arg.startsWith('-') && arg !== '--');
23
+ for (const pkgSpec of packageArgs) {
24
+ const purl = utils.safeNpmSpecToPurl(pkgSpec);
25
+ if (purl) {
26
+ packagePurls.push(purl);
27
+ }
28
+ }
29
+ }
30
+ return packagePurls;
31
+ }
32
+
33
+ /**
34
+ * Extract package PURLs from package.json for install/update commands.
35
+ */
36
+ async function extractPackagePurlsFromPackageJson(cwd) {
37
+ const packagePurls = [];
38
+ try {
39
+ const packageJsonContent = await fs.promises.readFile(`${cwd}/package.json`, 'utf8');
40
+ const packageJson = JSON.parse(packageJsonContent);
41
+ const allDeps = {
42
+ ...packageJson.dependencies,
43
+ ...packageJson.devDependencies,
44
+ ...packageJson.optionalDependencies,
45
+ ...packageJson.peerDependencies
46
+ };
47
+ for (const [name, version] of Object.entries(allDeps)) {
48
+ const purl = utils.safeNpmSpecToPurl(typeof version === 'string' ? `${name}@${version}` : name);
49
+ if (purl) {
50
+ packagePurls.push(purl);
51
+ }
52
+ }
53
+ utils.debugScan('start', packagePurls.length);
54
+ } catch (e) {
55
+ require$$9.debugFn('warn', 'Package.json not found or invalid during dependency scanning');
56
+ require$$9.debugDir('error', e);
57
+ }
58
+ return packagePurls;
59
+ }
60
+ /**
61
+ * Scan packages and log alerts if found.
62
+ */
63
+ async function scanPackagesAndLogAlerts(options) {
64
+ const {
65
+ acceptRisks,
66
+ command,
67
+ dlxCommands,
68
+ installCommands,
69
+ managerName,
70
+ nothrow = true,
71
+ rawArgs,
72
+ spinner,
73
+ viewAllRisks
74
+ } = options;
75
+ let {
76
+ cwd = process.cwd()
77
+ } = options;
78
+ if (cwd instanceof URL) {
79
+ cwd = require$$0.fileURLToPath(cwd);
80
+ }
81
+
82
+ // Check if this is a command that needs security scanning.
83
+ const isDlxCommand = dlxCommands && command && dlxCommands.has(command);
84
+ const isInstallCommand = command && installCommands.has(command);
85
+ const needsScanning = isDlxCommand || isInstallCommand;
86
+ if (!needsScanning || rawArgs.includes(constants.FLAG_DRY_RUN)) {
87
+ return {
88
+ shouldExit: false
89
+ };
90
+ }
91
+
92
+ // Extract package names from command arguments before any downloads.
93
+ let packagePurls = [];
94
+ if (command === 'add' || isDlxCommand) {
95
+ packagePurls = extractPackagePurlsFromArgs(command, rawArgs, dlxCommands);
96
+ } else if (isInstallCommand) {
97
+ // For install/update, scan dependencies from package.json.
98
+ // Note: This scans direct dependencies only.
99
+ packagePurls = await extractPackagePurlsFromPackageJson(cwd);
100
+ }
101
+ if (!packagePurls.length) {
102
+ return {
103
+ shouldExit: false
104
+ };
105
+ }
106
+ utils.debugScan('start', packagePurls.length);
107
+ require$$9.debugDir('inspect', {
108
+ packagePurls
109
+ });
110
+ try {
111
+ const alertsMap = await utils.getAlertsMapFromPurls(packagePurls, {
112
+ filter: acceptRisks ? {
113
+ actions: ['error'],
114
+ blocked: true
115
+ } : {
116
+ actions: ['error', 'monitor', 'warn']
117
+ },
118
+ nothrow,
119
+ spinner
120
+ });
121
+ if (alertsMap.size) {
122
+ process.exitCode = 1;
123
+ spinner?.stop();
124
+ utils.logAlertsMap(alertsMap, {
125
+ hideAt: viewAllRisks ? 'none' : 'middle',
126
+ output: process.stderr
127
+ });
128
+ const errorMessage = `Socket ${managerName} exiting due to risks.${viewAllRisks ? '' : `\nView all risks - Rerun with environment variable ${constants.default.SOCKET_CLI_VIEW_ALL_RISKS}=1.`}${acceptRisks ? '' : `\nAccept risks - Rerun with environment variable ${constants.default.SOCKET_CLI_ACCEPT_RISKS}=1.`}`.trim();
129
+ logger.logger.error(errorMessage);
130
+ return {
131
+ alertsMap,
132
+ shouldExit: true
133
+ };
134
+ }
135
+ } catch (e) {
136
+ spinner?.stop();
137
+ // Re-throw process.exit errors from tests.
138
+ if (e instanceof Error && e.message === 'process.exit called') {
139
+ throw e;
140
+ }
141
+ utils.debugScan('error', undefined, e);
142
+ // Continue with installation if scanning fails.
143
+ }
144
+ utils.debugScan('complete', packagePurls.length);
145
+ require$$9.debugDir('inspect', {
146
+ args: rawArgs.slice(1)
147
+ });
148
+ return {
149
+ shouldExit: false
150
+ };
151
+ }
152
+
153
+ async function installLinks(shadowBinPath, _binName) {
154
+ // Find pnpm being shadowed by this process.
155
+ const binPath = utils.getPnpmBinPath();
156
+ const {
157
+ WIN32
158
+ } = constants.default;
159
+
160
+ // TODO: Is this early exit needed?
161
+ if (WIN32 && binPath) {
162
+ return binPath;
163
+ }
164
+ const shadowed = utils.isPnpmBinPathShadowed();
165
+
166
+ // Move our bin directory to front of PATH so its found first.
167
+ if (!shadowed) {
168
+ if (WIN32) {
169
+ await vendor.libExports(path.join(constants.default.distPath, 'pnpm-cli.js'), path.join(shadowBinPath, 'pnpm'));
170
+ }
171
+ const {
172
+ env
173
+ } = process;
174
+ env['PATH'] = `${shadowBinPath}${path.delimiter}${env['PATH']}`;
175
+ }
176
+ return binPath;
177
+ }
178
+
179
+ const DLX_COMMANDS = new Set(['dlx']);
180
+ const INSTALL_COMMANDS = new Set(['add', 'i', 'install', 'install-test', 'it', 'update', 'up']);
181
+ async function shadowPnpmBin(args = process.argv.slice(2), options, extra) {
182
+ const opts = {
183
+ __proto__: null,
184
+ ...options
185
+ };
186
+ const {
187
+ env: spawnEnv,
188
+ ipc,
189
+ ...spawnOpts
190
+ } = opts;
191
+ const {
192
+ spinner
193
+ } = opts;
194
+ const wasSpinning = !!spinner?.isSpinning;
195
+ spinner?.start();
196
+ let {
197
+ cwd = process.cwd()
198
+ } = opts;
199
+ if (cwd instanceof URL) {
200
+ cwd = require$$0.fileURLToPath(cwd);
201
+ }
202
+ const terminatorPos = args.indexOf('--');
203
+ const rawPnpmArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos);
204
+ const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos);
205
+
206
+ // Check if this is a command that needs security scanning.
207
+ const command = rawPnpmArgs[0];
208
+ const isDlxCommand = command && DLX_COMMANDS.has(command);
209
+ const isInstallCommand = command && INSTALL_COMMANDS.has(command);
210
+ const needsScanning = isDlxCommand || isInstallCommand;
211
+ if (needsScanning && !rawPnpmArgs.includes(constants.FLAG_DRY_RUN)) {
212
+ const acceptRisks = !!constants.default.ENV.SOCKET_CLI_ACCEPT_RISKS;
213
+ const viewAllRisks = !!constants.default.ENV.SOCKET_CLI_VIEW_ALL_RISKS;
214
+
215
+ // Handle add and dlx commands with shared utility.
216
+ if (command === 'add' || isDlxCommand) {
217
+ const scanResult = await scanPackagesAndLogAlerts({
218
+ acceptRisks,
219
+ command,
220
+ cwd,
221
+ dlxCommands: DLX_COMMANDS,
222
+ installCommands: INSTALL_COMMANDS,
223
+ managerName: 'pnpm',
224
+ rawArgs: rawPnpmArgs,
225
+ spinner,
226
+ viewAllRisks
227
+ });
228
+ if (scanResult.shouldExit) {
229
+ // eslint-disable-next-line n/no-process-exit
230
+ process.exit(1);
231
+ // This line is never reached in production, but helps tests.
232
+ throw new Error('process.exit called');
233
+ }
234
+ } else if (['install', 'i', 'update', 'up'].includes(command)) {
235
+ // For install/update, scan all dependencies from pnpm-lock.yaml
236
+ const pnpmLockPath = path.join(cwd, constants.PNPM_LOCK_YAML);
237
+ if (fs.existsSync(pnpmLockPath)) {
238
+ try {
239
+ const lockfileContent = await utils.readPnpmLockfile(pnpmLockPath);
240
+ if (lockfileContent) {
241
+ const lockfile = utils.parsePnpmLockfile(lockfileContent);
242
+ if (lockfile) {
243
+ // Use existing function to scan the entire lockfile
244
+ require$$9.debugFn('notice', `scanning: all dependencies from ${constants.PNPM_LOCK_YAML}`);
245
+ const alertsMap = await utils.getAlertsMapFromPnpmLockfile(lockfile, {
246
+ nothrow: true,
247
+ filter: acceptRisks ? {
248
+ actions: ['error'],
249
+ blocked: true
250
+ } : {
251
+ actions: ['error', 'monitor', 'warn']
252
+ }
253
+ });
254
+ spinner?.stop();
255
+ if (alertsMap.size) {
256
+ process.exitCode = 1;
257
+ utils.logAlertsMap(alertsMap, {
258
+ hideAt: viewAllRisks ? 'none' : 'middle',
259
+ output: process.stderr
260
+ });
261
+ const errorMessage = `Socket pnpm exiting due to risks.${viewAllRisks ? '' : `\nView all risks - Rerun with environment variable ${constants.default.SOCKET_CLI_VIEW_ALL_RISKS}=1.`}${acceptRisks ? '' : `\nAccept risks - Rerun with environment variable ${constants.default.SOCKET_CLI_ACCEPT_RISKS}=1.`}`.trim();
262
+ logger.logger.error(errorMessage);
263
+ // eslint-disable-next-line n/no-process-exit
264
+ process.exit(1);
265
+ // This line is never reached in production, but helps tests.
266
+ throw new Error('process.exit called');
267
+ }
268
+
269
+ // Return early since we've already done the scanning
270
+ require$$9.debugFn('notice', 'complete: lockfile scanning, proceeding with install');
271
+ }
272
+ }
273
+ } catch (e) {
274
+ require$$9.debugFn('error', 'PNPM lockfile scanning failed');
275
+ require$$9.debugDir('error', e);
276
+ }
277
+ } else {
278
+ require$$9.debugFn('notice', 'skip: no pnpm-lock.yaml found, skipping bulk install scanning');
279
+ }
280
+ }
281
+ require$$9.debugFn('notice', 'complete: scanning, proceeding with install');
282
+ }
283
+ const realPnpmPath = await installLinks(constants.default.shadowBinPath);
284
+ spinner?.stop();
285
+ const suffixArgs = [...rawPnpmArgs, ...otherArgs];
286
+ require$$9.debugFn('notice', `spawn: ${constants.PNPM} shadow bin ${realPnpmPath} ${utils.cmdFlagsToString(suffixArgs)}`);
287
+ if (wasSpinning) {
288
+ spinner?.start();
289
+ }
290
+
291
+ // Set up stdio with IPC channel.
292
+ const stdio = shadowNpmBin.ensureIpcInStdio(spawnOpts.stdio);
293
+ const spawnPromise = spawn.spawn(realPnpmPath, suffixArgs, {
294
+ ...spawnOpts,
295
+ env: {
296
+ ...process.env,
297
+ ...spawnEnv
298
+ },
299
+ stdio,
300
+ // On Windows, pnpm is often a .cmd file that requires shell execution.
301
+ // The spawn function from @socketsecurity/registry will handle this properly
302
+ // when shell is true.
303
+ shell: constants.default.WIN32
304
+ }, extra);
305
+
306
+ // Send IPC handshake.
307
+ spawnPromise.process.send({
308
+ [constants.default.SOCKET_IPC_HANDSHAKE]: {
309
+ [constants.default.SOCKET_CLI_SHADOW_API_TOKEN]: utils.getPublicApiToken(),
310
+ [constants.default.SOCKET_CLI_SHADOW_BIN]: constants.PNPM,
311
+ [constants.default.SOCKET_CLI_SHADOW_PROGRESS]: true,
312
+ ...ipc
313
+ }
314
+ });
315
+ return {
316
+ spawnPromise
317
+ };
318
+ }
319
+
320
+ exports.scanPackagesAndLogAlerts = scanPackagesAndLogAlerts;
321
+ exports.shadowPnpmBin = shadowPnpmBin;
322
+ //# debugId=cdf92b11-1bc1-407a-8db7-38adcb6998cc
323
+ //# sourceMappingURL=shadow-pnpm-bin2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-pnpm-bin2.js","sources":["../src/shadow/common.mts","../src/shadow/pnpm/link.mts","../src/shadow/pnpm/bin.mts"],"sourcesContent":["import { promises as fs } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\nimport { debugDir, debugFn } from '@socketsecurity/registry/lib/debug'\nimport { logger } from '@socketsecurity/registry/lib/logger'\n\nimport constants, { FLAG_DRY_RUN } from '../constants.mts'\nimport { getAlertsMapFromPurls } from '../utils/alerts-map.mts'\nimport { debugScan } from '../utils/debug.mts'\nimport { safeNpmSpecToPurl } from '../utils/npm-spec.mts'\nimport { logAlertsMap } from '../utils/socket-package-alert.mts'\n\nimport type { AlertsByPurl } from '../utils/socket-package-alert.mts'\nimport type { Spinner } from '@socketsecurity/registry/lib/spinner'\n\n/**\n * Extract package PURLs from add/dlx command arguments.\n */\nexport function extractPackagePurlsFromArgs(\n command: string,\n rawArgs: string[] | readonly string[],\n dlxCommands?: Set<string>,\n): string[] {\n const packagePurls: string[] = []\n const isDlxCommand = dlxCommands?.has(command)\n\n if (command === 'add' || isDlxCommand) {\n // For 'add package1 package2@version' or 'dlx package', get packages from args.\n const packageArgs = rawArgs\n .slice(1)\n .filter(arg => !arg.startsWith('-') && arg !== '--')\n\n for (const pkgSpec of packageArgs) {\n const purl = safeNpmSpecToPurl(pkgSpec)\n if (purl) {\n packagePurls.push(purl)\n }\n }\n }\n\n return packagePurls\n}\n\n/**\n * Extract package PURLs from package.json for install/update commands.\n */\nexport async function extractPackagePurlsFromPackageJson(\n cwd: string,\n): Promise<string[]> {\n const packagePurls: string[] = []\n\n try {\n const packageJsonContent = await fs.readFile(`${cwd}/package.json`, 'utf8')\n const packageJson = JSON.parse(packageJsonContent)\n\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.optionalDependencies,\n ...packageJson.peerDependencies,\n }\n\n for (const [name, version] of Object.entries(allDeps)) {\n const purl = safeNpmSpecToPurl(\n typeof version === 'string' ? `${name}@${version}` : name,\n )\n if (purl) {\n packagePurls.push(purl)\n }\n }\n\n debugScan('start', packagePurls.length)\n } catch (e) {\n debugFn(\n 'warn',\n 'Package.json not found or invalid during dependency scanning',\n )\n debugDir('error', e)\n }\n\n return packagePurls\n}\n\nexport type PackageScanOptions = {\n acceptRisks: boolean\n command: string | undefined\n cwd?: string | URL\n dlxCommands?: Set<string>\n installCommands: Set<string>\n managerName: string\n nothrow?: boolean\n rawArgs: string[] | readonly string[]\n spinner?: Spinner | undefined\n viewAllRisks: boolean\n}\n\nexport type PackageScanResult = {\n alertsMap?: AlertsByPurl\n shouldExit: boolean\n}\n\n/**\n * Scan packages and log alerts if found.\n */\nexport async function scanPackagesAndLogAlerts(\n options: PackageScanOptions,\n): Promise<PackageScanResult> {\n const {\n acceptRisks,\n command,\n dlxCommands,\n installCommands,\n managerName,\n nothrow = true,\n rawArgs,\n spinner,\n viewAllRisks,\n } = options\n\n let { cwd = process.cwd() } = options\n if (cwd instanceof URL) {\n cwd = fileURLToPath(cwd)\n }\n\n // Check if this is a command that needs security scanning.\n const isDlxCommand = dlxCommands && command && dlxCommands.has(command)\n const isInstallCommand = command && installCommands.has(command)\n const needsScanning = isDlxCommand || isInstallCommand\n\n if (!needsScanning || rawArgs.includes(FLAG_DRY_RUN)) {\n return { shouldExit: false }\n }\n\n // Extract package names from command arguments before any downloads.\n let packagePurls: string[] = []\n\n if (command === 'add' || isDlxCommand) {\n packagePurls = extractPackagePurlsFromArgs(command, rawArgs, dlxCommands)\n } else if (isInstallCommand) {\n // For install/update, scan dependencies from package.json.\n // Note: This scans direct dependencies only.\n packagePurls = await extractPackagePurlsFromPackageJson(cwd)\n }\n\n if (!packagePurls.length) {\n return { shouldExit: false }\n }\n\n debugScan('start', packagePurls.length)\n debugDir('inspect', { packagePurls })\n\n try {\n const alertsMap = await getAlertsMapFromPurls(packagePurls, {\n filter: acceptRisks\n ? { actions: ['error'], blocked: true }\n : { actions: ['error', 'monitor', 'warn'] },\n nothrow,\n spinner,\n })\n\n if (alertsMap.size) {\n process.exitCode = 1\n spinner?.stop()\n logAlertsMap(alertsMap, {\n hideAt: viewAllRisks ? 'none' : 'middle',\n output: process.stderr,\n })\n\n const errorMessage = `Socket ${managerName} exiting due to risks.${\n viewAllRisks\n ? ''\n : `\\nView all risks - Rerun with environment variable ${constants.SOCKET_CLI_VIEW_ALL_RISKS}=1.`\n }${\n acceptRisks\n ? ''\n : `\\nAccept risks - Rerun with environment variable ${constants.SOCKET_CLI_ACCEPT_RISKS}=1.`\n }`.trim()\n\n logger.error(errorMessage)\n return { alertsMap, shouldExit: true }\n }\n } catch (e) {\n spinner?.stop()\n // Re-throw process.exit errors from tests.\n if (e instanceof Error && e.message === 'process.exit called') {\n throw e\n }\n debugScan('error', undefined, e)\n // Continue with installation if scanning fails.\n }\n\n debugScan('complete', packagePurls.length)\n debugDir('inspect', { args: rawArgs.slice(1) })\n\n return { shouldExit: false }\n}\n","import path from 'node:path'\n\nimport cmdShim from 'cmd-shim'\n\nimport constants from '../../constants.mts'\nimport {\n getPnpmBinPath,\n isPnpmBinPathShadowed,\n} from '../../utils/pnpm-paths.mts'\n\nexport async function installLinks(\n shadowBinPath: string,\n _binName: 'pnpm',\n): Promise<string> {\n // Find pnpm being shadowed by this process.\n const binPath = getPnpmBinPath()\n const { WIN32 } = constants\n\n // TODO: Is this early exit needed?\n if (WIN32 && binPath) {\n return binPath\n }\n\n const shadowed = isPnpmBinPathShadowed()\n\n // Move our bin directory to front of PATH so its found first.\n if (!shadowed) {\n if (WIN32) {\n await cmdShim(\n path.join(constants.distPath, 'pnpm-cli.js'),\n path.join(shadowBinPath, 'pnpm'),\n )\n }\n const { env } = process\n env['PATH'] = `${shadowBinPath}${path.delimiter}${env['PATH']}`\n }\n\n return binPath\n}\n","import { existsSync } from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { debugDir, debugFn } from '@socketsecurity/registry/lib/debug'\nimport { logger } from '@socketsecurity/registry/lib/logger'\nimport { spawn } from '@socketsecurity/registry/lib/spawn'\n\nimport { scanPackagesAndLogAlerts } from '../common.mts'\nimport { ensureIpcInStdio } from '../stdio-ipc.mts'\nimport { installLinks } from './link.mts'\nimport constants, {\n FLAG_DRY_RUN,\n PNPM,\n PNPM_LOCK_YAML,\n} from '../../constants.mts'\nimport { getAlertsMapFromPnpmLockfile } from '../../utils/alerts-map.mts'\nimport { cmdFlagsToString } from '../../utils/cmd.mts'\nimport { parsePnpmLockfile, readPnpmLockfile } from '../../utils/pnpm.mts'\nimport { getPublicApiToken } from '../../utils/sdk.mts'\nimport { logAlertsMap } from '../../utils/socket-package-alert.mts'\n\nimport type { IpcObject } from '../../constants.mts'\nimport type {\n SpawnExtra,\n SpawnOptions,\n SpawnResult,\n} from '@socketsecurity/registry/lib/spawn'\n\nexport type ShadowPnpmOptions = SpawnOptions & {\n ipc?: IpcObject | undefined\n}\n\nexport type ShadowPnpmResult = {\n spawnPromise: SpawnResult<string, SpawnExtra | undefined>\n}\n\nconst DLX_COMMANDS = new Set(['dlx'])\n\nconst INSTALL_COMMANDS = new Set([\n 'add',\n 'i',\n 'install',\n 'install-test',\n 'it',\n 'update',\n 'up',\n])\n\nexport default async function shadowPnpmBin(\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowPnpmOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowPnpmResult> {\n const opts = { __proto__: null, ...options } as ShadowPnpmOptions\n const { env: spawnEnv, ipc, ...spawnOpts } = opts\n const { spinner } = opts\n\n const wasSpinning = !!spinner?.isSpinning\n\n spinner?.start()\n\n let { cwd = process.cwd() } = opts\n if (cwd instanceof URL) {\n cwd = fileURLToPath(cwd)\n }\n\n const terminatorPos = args.indexOf('--')\n const rawPnpmArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos)\n const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos)\n\n // Check if this is a command that needs security scanning.\n const command = rawPnpmArgs[0]\n const isDlxCommand = command && DLX_COMMANDS.has(command)\n const isInstallCommand = command && INSTALL_COMMANDS.has(command)\n const needsScanning = isDlxCommand || isInstallCommand\n\n if (needsScanning && !rawPnpmArgs.includes(FLAG_DRY_RUN)) {\n const acceptRisks = !!constants.ENV.SOCKET_CLI_ACCEPT_RISKS\n const viewAllRisks = !!constants.ENV.SOCKET_CLI_VIEW_ALL_RISKS\n\n // Handle add and dlx commands with shared utility.\n if (command === 'add' || isDlxCommand) {\n const scanResult = await scanPackagesAndLogAlerts({\n acceptRisks,\n command,\n cwd,\n dlxCommands: DLX_COMMANDS,\n installCommands: INSTALL_COMMANDS,\n managerName: 'pnpm',\n rawArgs: rawPnpmArgs,\n spinner,\n viewAllRisks,\n })\n\n if (scanResult.shouldExit) {\n // eslint-disable-next-line n/no-process-exit\n process.exit(1)\n // This line is never reached in production, but helps tests.\n throw new Error('process.exit called')\n }\n } else if (['install', 'i', 'update', 'up'].includes(command)) {\n // For install/update, scan all dependencies from pnpm-lock.yaml\n const pnpmLockPath = path.join(cwd, PNPM_LOCK_YAML)\n if (existsSync(pnpmLockPath)) {\n try {\n const lockfileContent = await readPnpmLockfile(pnpmLockPath)\n if (lockfileContent) {\n const lockfile = parsePnpmLockfile(lockfileContent)\n if (lockfile) {\n // Use existing function to scan the entire lockfile\n debugFn(\n 'notice',\n `scanning: all dependencies from ${PNPM_LOCK_YAML}`,\n )\n\n const alertsMap = await getAlertsMapFromPnpmLockfile(lockfile, {\n nothrow: true,\n filter: acceptRisks\n ? { actions: ['error'], blocked: true }\n : { actions: ['error', 'monitor', 'warn'] },\n })\n\n spinner?.stop()\n\n if (alertsMap.size) {\n process.exitCode = 1\n logAlertsMap(alertsMap, {\n hideAt: viewAllRisks ? 'none' : 'middle',\n output: process.stderr,\n })\n\n const errorMessage = `Socket pnpm exiting due to risks.${\n viewAllRisks\n ? ''\n : `\\nView all risks - Rerun with environment variable ${constants.SOCKET_CLI_VIEW_ALL_RISKS}=1.`\n }${\n acceptRisks\n ? ''\n : `\\nAccept risks - Rerun with environment variable ${constants.SOCKET_CLI_ACCEPT_RISKS}=1.`\n }`.trim()\n\n logger.error(errorMessage)\n // eslint-disable-next-line n/no-process-exit\n process.exit(1)\n // This line is never reached in production, but helps tests.\n throw new Error('process.exit called')\n }\n\n // Return early since we've already done the scanning\n debugFn(\n 'notice',\n 'complete: lockfile scanning, proceeding with install',\n )\n }\n }\n } catch (e) {\n debugFn('error', 'PNPM lockfile scanning failed')\n debugDir('error', e)\n }\n } else {\n debugFn(\n 'notice',\n 'skip: no pnpm-lock.yaml found, skipping bulk install scanning',\n )\n }\n }\n\n debugFn('notice', 'complete: scanning, proceeding with install')\n }\n\n const realPnpmPath = await installLinks(constants.shadowBinPath, PNPM)\n\n spinner?.stop()\n\n const suffixArgs = [...rawPnpmArgs, ...otherArgs]\n\n debugFn(\n 'notice',\n `spawn: ${PNPM} shadow bin ${realPnpmPath} ${cmdFlagsToString(suffixArgs)}`,\n )\n\n if (wasSpinning) {\n spinner?.start()\n }\n\n // Set up stdio with IPC channel.\n const stdio = ensureIpcInStdio(spawnOpts.stdio)\n\n const spawnPromise = spawn(\n realPnpmPath,\n suffixArgs,\n {\n ...spawnOpts,\n env: {\n ...process.env,\n ...spawnEnv,\n },\n stdio,\n // On Windows, pnpm is often a .cmd file that requires shell execution.\n // The spawn function from @socketsecurity/registry will handle this properly\n // when shell is true.\n shell: constants.WIN32,\n },\n extra,\n )\n\n // Send IPC handshake.\n spawnPromise.process.send({\n [constants.SOCKET_IPC_HANDSHAKE]: {\n [constants.SOCKET_CLI_SHADOW_API_TOKEN]: getPublicApiToken(),\n [constants.SOCKET_CLI_SHADOW_BIN]: PNPM,\n [constants.SOCKET_CLI_SHADOW_PROGRESS]: true,\n ...ipc,\n },\n })\n\n return { spawnPromise }\n}\n"],"names":["packagePurls","debugScan","debugFn","debugDir","nothrow","viewAllRisks","cwd","shouldExit","blocked","actions","spinner","hideAt","logger","args","WIN32","env","__proto__","dlxCommands","installCommands","managerName","rawArgs","process","spawnPromise"],"mappings":";;;;;;;;;;;;;AAeA;AACA;AACA;AACO;;AAML;AAEA;AACE;;AAKA;AACE;AACA;AACEA;AACF;AACF;AACF;AAEA;AACF;;AAEA;AACA;AACA;AACO;;;AAMH;AACA;AAEA;;;;AAIE;;AAGF;AACE;AAGA;AACEA;AACF;AACF;AAEAC;;AAEAC;AAIAC;AACF;AAEA;AACF;AAoBA;AACA;AACA;AACO;;;;;;;AASHC;;;AAGAC;AACF;;AAEMC;AAAoB;;AAExBA;AACF;;AAEA;;;AAGA;;;AAGWC;;AACX;;AAEA;;AAGA;;;AAGE;AACA;AACAP;AACF;AAEA;;AACWO;;AACX;AAEAN;;AACsBD;AAAa;;AAGjC;;;AAE4BQ;AAAc;AAClCC;;;AAENC;AACF;;;;;AAMIC;;AAEF;AAEA;AAUAC;;;AACoBL;;AACtB;;;AAGA;;AAEE;AACF;AACAN;AACA;AACF;AAEAA;;AACsBY;AAAuB;;AAEpCN;;AACX;;ACzLO;AAIL;AACA;;AACQO;AAAM;;AAEd;;AAEE;AACF;AAEA;;AAEA;;AAEE;;AAKA;;AACQC;AAAI;AACZA;AACF;AAEA;AACF;;ACDA;AAEA;AAUe;AAKb;AAAeC;;;;AACPD;;;AAAiC;;AACjCL;AAAQ;AAEhB;;;AAIMJ;AAAoB;;AAExBA;AACF;AAEA;AACA;AACA;;AAEA;AACA;;;AAGA;;;;;AAME;AACA;AACE;;;;AAIEW;AACAC;AACAC;AACAC;;AAEAf;AACF;;AAGE;AACAgB;AACA;AACA;AACF;AACF;AACE;;AAEA;;AAEI;AACA;AACE;AACA;AACE;AACAnB;AAKA;AACEE;;;AAE0BI;AAAc;AAClCC;AAAsC;AAC9C;;;;;AAOIE;;AAEF;;AAYAC;AACA;AACAS;AACA;AACA;AACF;;AAEA;AACAnB;AAIF;AACF;;AAEAA;AACAC;AACF;AACF;AACED;AAIF;AACF;AAEAA;AACF;;;;AAQAA;AAKA;;AAEA;;AAEA;AACA;AAEA;AAII;AACAa;;;;;AAKA;AACA;AACA;;;;AAMJ;AACAO;;AAEI;AACA;AACA;;AAEF;AACF;;AAESA;;AACX;;;","debugId":"cdf92b11-1bc1-407a-8db7-38adcb6998cc"}
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var fs = require('node:fs');
4
3
  var require$$9 = require('../external/@socketsecurity/registry/lib/debug');
5
- var logger = require('../external/@socketsecurity/registry/lib/logger');
6
4
  var spawn = require('../external/@socketsecurity/registry/lib/spawn');
5
+ var shadowPnpmBin = require('./shadow-pnpm-bin2.js');
6
+ var shadowNpmBin = require('./shadow-npm-bin2.js');
7
7
  var path = require('node:path');
8
8
  var vendor = require('./vendor.js');
9
9
  var constants = require('./constants.js');
@@ -30,171 +30,83 @@ async function installLinks(shadowBinPath, binName) {
30
30
  return binPath;
31
31
  }
32
32
 
33
- const INSTALL_COMMANDS = new Set(['add', 'install', 'up', 'upgrade', 'upgrade-interactive']);
34
33
  const DLX_COMMANDS = new Set(['dlx']);
35
- async function shadowYarn(args = process.argv.slice(2), options, extra) {
34
+ const INSTALL_COMMANDS = new Set(['add', 'install', 'up', 'upgrade', 'upgrade-interactive']);
35
+ async function shadowYarnBin(args = process.argv.slice(2), options, extra) {
36
+ const opts = {
37
+ __proto__: null,
38
+ ...options
39
+ };
36
40
  const {
37
41
  env: spawnEnv,
38
42
  ipc,
39
43
  ...spawnOpts
40
- } = {
41
- __proto__: null,
42
- ...options
43
- };
44
+ } = opts;
45
+ const {
46
+ spinner
47
+ } = opts;
48
+ const wasSpinning = !!spinner?.isSpinning;
49
+ spinner?.start();
44
50
  const terminatorPos = args.indexOf('--');
45
51
  const rawYarnArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos);
46
52
  const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos);
47
53
 
48
- // Check if this is a command that needs security scanning
54
+ // Check for package scanning.
49
55
  const command = rawYarnArgs[0];
50
- const needsScanning = command && (INSTALL_COMMANDS.has(command) || DLX_COMMANDS.has(command));
51
-
52
- // Get yarn path
53
- const realYarnPath = await installLinks(constants.default.shadowBinPath, 'yarn');
54
- const permArgs = [];
55
- const prefixArgs = [];
56
- const suffixArgs = [...rawYarnArgs, ...permArgs, ...otherArgs];
57
- if (needsScanning && !rawYarnArgs.includes(constants.FLAG_DRY_RUN)) {
58
- const acceptRisks = Boolean(process.env['SOCKET_CLI_ACCEPT_RISKS']);
59
- const viewAllRisks = Boolean(process.env['SOCKET_CLI_VIEW_ALL_RISKS']);
60
-
61
- // Extract package names from command arguments before any downloads
62
- const packagePurls = [];
63
- if (command === 'add' || command === 'dlx') {
64
- // For 'yarn add package1 package2@version' or 'yarn dlx package'
65
- const packageArgs = rawYarnArgs.slice(1).filter(arg => !arg.startsWith('-') && arg !== '--');
66
- for (const pkgSpec of packageArgs) {
67
- // Handle package specs like 'lodash', 'lodash@4.17.21', '@types/node@^20.0.0'
68
- let name;
69
- let version;
70
- if (pkgSpec.startsWith('@')) {
71
- // Scoped package: @scope/name or @scope/name@version
72
- const parts = pkgSpec.split('@');
73
- if (parts.length === 2) {
74
- // @scope/name (no version)
75
- name = pkgSpec;
76
- } else {
77
- // @scope/name@version
78
- name = `@${parts[1]}`;
79
- version = parts[2];
80
- }
81
- } else {
82
- // Regular package: name or name@version
83
- const atIndex = pkgSpec.indexOf('@');
84
- if (atIndex === -1) {
85
- name = pkgSpec;
86
- } else {
87
- name = pkgSpec.slice(0, atIndex);
88
- version = pkgSpec.slice(atIndex + 1);
89
- }
90
- }
91
- if (name) {
92
- packagePurls.push(version ? utils.idToNpmPurl(`${name}@${version}`) : utils.idToNpmPurl(name));
93
- }
94
- }
95
- } else if (['install', 'up', 'upgrade', 'upgrade-interactive'].includes(command)) {
96
- // For install/upgrade, scan all dependencies from package.json
97
- // Note: This scans direct dependencies only. For full transitive dependency
98
- // scanning, yarn.lock parsing would be needed (not yet implemented)
99
- try {
100
- const packageJsonContent = await fs.promises.readFile('package.json', 'utf8');
101
- const packageJson = JSON.parse(packageJsonContent);
102
- const allDeps = {
103
- ...packageJson.dependencies,
104
- ...packageJson.devDependencies,
105
- ...packageJson.optionalDependencies,
106
- ...packageJson.peerDependencies
107
- };
108
- for (const [name, version] of Object.entries(allDeps)) {
109
- if (typeof version === 'string') {
110
- packagePurls.push(utils.idToNpmPurl(`${name}@${version}`));
111
- } else {
112
- packagePurls.push(utils.idToNpmPurl(name));
113
- }
114
- }
115
- if (require$$9.isDebug()) {
116
- require$$9.debugFn('notice', `scanning: ${packagePurls.length} direct dependencies from package.json`);
117
- require$$9.debugFn('notice', 'note: transitive dependencies not scanned (yarn.lock parsing not implemented)');
118
- }
119
- } catch (e) {
120
- if (require$$9.isDebug()) {
121
- require$$9.debugFn('error', 'caught: package.json read error during dependency scanning');
122
- require$$9.debugDir('inspect', {
123
- error: e
124
- });
125
- }
126
- }
127
- }
128
- if (packagePurls.length > 0) {
129
- if (require$$9.isDebug()) {
130
- require$$9.debugFn('notice', 'scanning: packages before download');
131
- require$$9.debugDir('inspect', {
132
- packagePurls
133
- });
134
- }
135
- try {
136
- const alertsMap = await utils.getAlertsMapFromPurls(packagePurls, {
137
- nothrow: true,
138
- filter: acceptRisks ? {
139
- actions: ['error'],
140
- blocked: true
141
- } : {
142
- actions: ['error', 'monitor', 'warn']
143
- }
144
- });
145
- if (alertsMap.size) {
146
- process.exitCode = 1;
147
- utils.logAlertsMap(alertsMap, {
148
- hideAt: viewAllRisks ? 'none' : 'middle',
149
- output: process.stderr
150
- });
151
- const errorMessage = `
152
- Socket yarn exiting due to risks.${viewAllRisks ? '' : `\nView all risks - Rerun with environment variable ${constants.default.SOCKET_CLI_VIEW_ALL_RISKS}=1.`}${acceptRisks ? '' : `\nAccept risks - Rerun with environment variable ${constants.default.SOCKET_CLI_ACCEPT_RISKS}=1.`}`.trim();
153
- logger.logger.error(errorMessage);
154
- // eslint-disable-next-line n/no-process-exit
155
- process.exit(1);
156
- // This line is never reached in production, but helps tests.
157
- throw new Error('process.exit called');
158
- }
159
- } catch (e) {
160
- // Re-throw process.exit errors from tests.
161
- if (e instanceof Error && e.message === 'process.exit called') {
162
- throw e;
163
- }
164
- if (require$$9.isDebug()) {
165
- require$$9.debugFn('error', 'caught: package scanning error');
166
- require$$9.debugDir('inspect', {
167
- error: e
168
- });
169
- }
170
- // Continue with installation if scanning fails
171
- }
172
- }
173
- if (require$$9.isDebug()) {
174
- require$$9.debugFn('notice', 'complete: scanning, proceeding with install');
175
- require$$9.debugDir('inspect', {
176
- args: rawYarnArgs.slice(1)
177
- });
178
- }
56
+ const scanResult = await shadowPnpmBin.scanPackagesAndLogAlerts({
57
+ acceptRisks: !!constants.default.ENV.SOCKET_CLI_ACCEPT_RISKS,
58
+ command,
59
+ cwd: process.cwd(),
60
+ dlxCommands: DLX_COMMANDS,
61
+ installCommands: INSTALL_COMMANDS,
62
+ managerName: 'yarn',
63
+ rawArgs: rawYarnArgs,
64
+ spinner,
65
+ viewAllRisks: !!constants.default.ENV.SOCKET_CLI_VIEW_ALL_RISKS
66
+ });
67
+ if (scanResult.shouldExit) {
68
+ // eslint-disable-next-line n/no-process-exit
69
+ process.exit(1);
70
+ // This line is never reached in production, but helps tests.
71
+ throw new Error('process.exit called');
179
72
  }
180
- const argsToString = utils.cmdFlagsToString([...prefixArgs, ...suffixArgs]);
181
- const env = {
182
- ...process.env,
183
- ...spawnEnv
184
- };
185
- if (require$$9.isDebug()) {
186
- require$$9.debugFn('notice', `spawn: yarn shadow bin ${realYarnPath} ${argsToString}`);
73
+ const realYarnPath = await installLinks(constants.default.shadowBinPath, constants.YARN);
74
+ spinner?.stop();
75
+ const suffixArgs = [...rawYarnArgs, ...otherArgs];
76
+ require$$9.debugFn('notice', `spawn: ${constants.YARN} shadow bin ${realYarnPath} ${utils.cmdFlagsToString(suffixArgs)}`);
77
+ if (wasSpinning) {
78
+ spinner?.start();
187
79
  }
188
- const spawnPromise = spawn.spawn(realYarnPath, [...prefixArgs, ...suffixArgs], {
80
+
81
+ // Set up stdio with IPC channel.
82
+ const stdio = shadowNpmBin.ensureIpcInStdio(spawnOpts.stdio);
83
+ const spawnPromise = spawn.spawn(realYarnPath, suffixArgs, {
189
84
  ...spawnOpts,
190
- env,
191
- extra
85
+ env: {
86
+ ...process.env,
87
+ ...spawnEnv
88
+ },
89
+ stdio,
90
+ // On Windows, yarn is often a .cmd file that requires shell execution.
91
+ // The spawn function from @socketsecurity/registry will handle this properly
92
+ // when shell is true.
93
+ shell: constants.default.WIN32
94
+ }, extra);
95
+
96
+ // Send IPC handshake.
97
+ spawnPromise.process.send({
98
+ [constants.default.SOCKET_IPC_HANDSHAKE]: {
99
+ [constants.default.SOCKET_CLI_SHADOW_API_TOKEN]: utils.getPublicApiToken(),
100
+ [constants.default.SOCKET_CLI_SHADOW_BIN]: constants.YARN,
101
+ [constants.default.SOCKET_CLI_SHADOW_PROGRESS]: true,
102
+ ...ipc
103
+ }
192
104
  });
193
105
  return {
194
106
  spawnPromise
195
107
  };
196
108
  }
197
109
 
198
- module.exports = shadowYarn;
199
- //# debugId=bc98a9a0-2f24-4096-92b6-cda4ca25f80c
110
+ module.exports = shadowYarnBin;
111
+ //# debugId=1cf735f7-d72e-4a95-9a5f-bc6822d5a468
200
112
  //# sourceMappingURL=shadow-yarn-bin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadow-yarn-bin.js","sources":["../src/shadow/yarn/link.mts","../src/shadow/yarn/bin.mts"],"sourcesContent":["import path from 'node:path'\n\nimport cmdShim from 'cmd-shim'\n\nimport constants from '../../constants.mts'\nimport {\n getYarnBinPath,\n isYarnBinPathShadowed,\n} from '../../utils/yarn-paths.mts'\n\nexport async function installLinks(\n shadowBinPath: string,\n binName: 'yarn',\n): Promise<string> {\n const binPath = getYarnBinPath()\n const { WIN32 } = constants\n\n if (WIN32 && binPath) {\n return binPath\n }\n\n const shadowed = isYarnBinPathShadowed()\n\n if (!shadowed) {\n if (WIN32) {\n await cmdShim(\n path.join(constants.distPath, `${binName}-cli.js`),\n path.join(shadowBinPath, binName),\n )\n }\n const { env } = process\n env['PATH'] = `${shadowBinPath}${path.delimiter}${env['PATH']}`\n }\n\n return binPath\n}\n","import { promises as fs } from 'node:fs'\n\nimport { debugDir, debugFn, isDebug } from '@socketsecurity/registry/lib/debug'\nimport { logger } from '@socketsecurity/registry/lib/logger'\nimport { spawn } from '@socketsecurity/registry/lib/spawn'\n\nimport { installLinks } from './link.mts'\nimport constants, { FLAG_DRY_RUN } from '../../constants.mts'\nimport { getAlertsMapFromPurls } from '../../utils/alerts-map.mts'\nimport { cmdFlagsToString } from '../../utils/cmd.mts'\nimport { logAlertsMap } from '../../utils/socket-package-alert.mts'\nimport { idToNpmPurl } from '../../utils/spec.mts'\n\nimport type { IpcObject } from '../../constants.mts'\nimport type {\n SpawnExtra,\n SpawnOptions,\n SpawnResult,\n} from '@socketsecurity/registry/lib/spawn'\n\nexport type ShadowYarnOptions = SpawnOptions & {\n ipc?: IpcObject | undefined\n}\n\nexport type ShadowYarnResult = {\n spawnPromise: SpawnResult<string, SpawnExtra | undefined>\n}\n\nconst INSTALL_COMMANDS = new Set([\n 'add',\n 'install',\n 'up',\n 'upgrade',\n 'upgrade-interactive',\n])\n\nconst DLX_COMMANDS = new Set(['dlx'])\n\nexport default async function shadowYarn(\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowYarnOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowYarnResult> {\n const {\n env: spawnEnv,\n ipc,\n ...spawnOpts\n } = { __proto__: null, ...options } as ShadowYarnOptions\n const terminatorPos = args.indexOf('--')\n const rawYarnArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos)\n const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos)\n\n // Check if this is a command that needs security scanning\n const command = rawYarnArgs[0]\n const needsScanning =\n command && (INSTALL_COMMANDS.has(command) || DLX_COMMANDS.has(command))\n\n // Get yarn path\n const realYarnPath = await installLinks(constants.shadowBinPath, 'yarn')\n\n const permArgs: string[] = []\n\n const prefixArgs: string[] = []\n const suffixArgs = [...rawYarnArgs, ...permArgs, ...otherArgs]\n\n if (needsScanning && !rawYarnArgs.includes(FLAG_DRY_RUN)) {\n const acceptRisks = Boolean(process.env['SOCKET_CLI_ACCEPT_RISKS'])\n const viewAllRisks = Boolean(process.env['SOCKET_CLI_VIEW_ALL_RISKS'])\n\n // Extract package names from command arguments before any downloads\n const packagePurls: string[] = []\n\n if (command === 'add' || command === 'dlx') {\n // For 'yarn add package1 package2@version' or 'yarn dlx package'\n const packageArgs = rawYarnArgs\n .slice(1)\n .filter(arg => !arg.startsWith('-') && arg !== '--')\n\n for (const pkgSpec of packageArgs) {\n // Handle package specs like 'lodash', 'lodash@4.17.21', '@types/node@^20.0.0'\n let name: string\n let version: string | undefined\n\n if (pkgSpec.startsWith('@')) {\n // Scoped package: @scope/name or @scope/name@version\n const parts = pkgSpec.split('@')\n if (parts.length === 2) {\n // @scope/name (no version)\n name = pkgSpec\n } else {\n // @scope/name@version\n name = `@${parts[1]}`\n version = parts[2]\n }\n } else {\n // Regular package: name or name@version\n const atIndex = pkgSpec.indexOf('@')\n if (atIndex === -1) {\n name = pkgSpec\n } else {\n name = pkgSpec.slice(0, atIndex)\n version = pkgSpec.slice(atIndex + 1)\n }\n }\n\n if (name) {\n packagePurls.push(\n version ? idToNpmPurl(`${name}@${version}`) : idToNpmPurl(name),\n )\n }\n }\n } else if (\n ['install', 'up', 'upgrade', 'upgrade-interactive'].includes(command)\n ) {\n // For install/upgrade, scan all dependencies from package.json\n // Note: This scans direct dependencies only. For full transitive dependency\n // scanning, yarn.lock parsing would be needed (not yet implemented)\n try {\n const packageJsonContent = await fs.readFile('package.json', 'utf8')\n const packageJson = JSON.parse(packageJsonContent)\n\n const allDeps = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n ...packageJson.optionalDependencies,\n ...packageJson.peerDependencies,\n }\n\n for (const [name, version] of Object.entries(allDeps)) {\n if (typeof version === 'string') {\n packagePurls.push(idToNpmPurl(`${name}@${version}`))\n } else {\n packagePurls.push(idToNpmPurl(name))\n }\n }\n\n if (isDebug()) {\n debugFn(\n 'notice',\n `scanning: ${packagePurls.length} direct dependencies from package.json`,\n )\n debugFn(\n 'notice',\n 'note: transitive dependencies not scanned (yarn.lock parsing not implemented)',\n )\n }\n } catch (e) {\n if (isDebug()) {\n debugFn(\n 'error',\n 'caught: package.json read error during dependency scanning',\n )\n debugDir('inspect', { error: e })\n }\n }\n }\n\n if (packagePurls.length > 0) {\n if (isDebug()) {\n debugFn('notice', 'scanning: packages before download')\n debugDir('inspect', { packagePurls })\n }\n\n try {\n const alertsMap = await getAlertsMapFromPurls(packagePurls, {\n nothrow: true,\n filter: acceptRisks\n ? { actions: ['error'], blocked: true }\n : { actions: ['error', 'monitor', 'warn'] },\n })\n\n if (alertsMap.size) {\n process.exitCode = 1\n logAlertsMap(alertsMap, {\n hideAt: viewAllRisks ? 'none' : 'middle',\n output: process.stderr,\n })\n\n const errorMessage = `\nSocket yarn exiting due to risks.${\n viewAllRisks\n ? ''\n : `\\nView all risks - Rerun with environment variable ${constants.SOCKET_CLI_VIEW_ALL_RISKS}=1.`\n }${\n acceptRisks\n ? ''\n : `\\nAccept risks - Rerun with environment variable ${constants.SOCKET_CLI_ACCEPT_RISKS}=1.`\n }`.trim()\n\n logger.error(errorMessage)\n // eslint-disable-next-line n/no-process-exit\n process.exit(1)\n // This line is never reached in production, but helps tests.\n throw new Error('process.exit called')\n }\n } catch (e) {\n // Re-throw process.exit errors from tests.\n if (e instanceof Error && e.message === 'process.exit called') {\n throw e\n }\n if (isDebug()) {\n debugFn('error', 'caught: package scanning error')\n debugDir('inspect', { error: e })\n }\n // Continue with installation if scanning fails\n }\n }\n\n if (isDebug()) {\n debugFn('notice', 'complete: scanning, proceeding with install')\n debugDir('inspect', { args: rawYarnArgs.slice(1) })\n }\n }\n\n const argsToString = cmdFlagsToString([...prefixArgs, ...suffixArgs])\n const env = {\n ...process.env,\n ...spawnEnv,\n } as Record<string, string>\n\n if (isDebug()) {\n debugFn('notice', `spawn: yarn shadow bin ${realYarnPath} ${argsToString}`)\n }\n\n const spawnPromise = spawn(realYarnPath, [...prefixArgs, ...suffixArgs], {\n ...spawnOpts,\n env,\n extra,\n })\n\n return { spawnPromise }\n}\n"],"names":["WIN32","env","__proto__","name","version","packagePurls","debugFn","error","nothrow","blocked","actions","hideAt","logger","process","args","extra","spawnPromise"],"mappings":";;;;;;;;;;;AAUO;AAIL;;AACQA;AAAM;;AAGZ;AACF;AAEA;;AAGE;;AAKA;;AACQC;AAAI;AACZA;AACF;AAEA;AACF;;ACPA;AAQA;AAEe;;AAMXA;;;AAGF;AAAMC;;;AACN;AACA;AACA;;AAEA;AACA;AACA;;AAGA;;;;;;;;;AAYE;;AAGA;AACE;;AAKA;AACE;AACA;AACA;AAEA;AACE;AACA;AACA;AACE;AACAC;AACF;AACE;AACAA;AACAC;AACF;AACF;AACE;AACA;AACA;AACED;AACF;;;AAGA;AACF;AAEA;AACEE;AAGF;AACF;AACF;AAGE;AACA;AACA;;;AAGE;AAEA;;;;AAIE;;AAGF;AACE;;AAEA;AACEA;AACF;AACF;;;AAOEC;AAIF;;;AAGEA;;AAIsBC;AAAS;AACjC;AACF;AACF;AAEA;;AAEID;;AACsBD;AAAa;AACrC;;AAGE;AACEG;;;AAE0BC;AAAc;AAClCC;AAAsC;AAC9C;;;;AAKIC;;AAEF;AAEA;AACV;AAUUC;AACA;AACAC;AACA;AACA;AACF;;AAEA;;AAEE;AACF;;AAEEP;;AACsBC;AAAS;AACjC;AACA;AACF;AACF;;AAGED;;AACsBQ;AAA2B;AACnD;AACF;;AAGA;;;;;;AAOA;AAEA;AACE;;AAEAC;AACF;;AAESC;;AACX;;","debugId":"bc98a9a0-2f24-4096-92b6-cda4ca25f80c"}
1
+ {"version":3,"file":"shadow-yarn-bin.js","sources":["../src/shadow/yarn/link.mts","../src/shadow/yarn/bin.mts"],"sourcesContent":["import path from 'node:path'\n\nimport cmdShim from 'cmd-shim'\n\nimport constants from '../../constants.mts'\nimport {\n getYarnBinPath,\n isYarnBinPathShadowed,\n} from '../../utils/yarn-paths.mts'\n\nexport async function installLinks(\n shadowBinPath: string,\n binName: 'yarn',\n): Promise<string> {\n const binPath = getYarnBinPath()\n const { WIN32 } = constants\n\n if (WIN32 && binPath) {\n return binPath\n }\n\n const shadowed = isYarnBinPathShadowed()\n\n if (!shadowed) {\n if (WIN32) {\n await cmdShim(\n path.join(constants.distPath, `${binName}-cli.js`),\n path.join(shadowBinPath, binName),\n )\n }\n const { env } = process\n env['PATH'] = `${shadowBinPath}${path.delimiter}${env['PATH']}`\n }\n\n return binPath\n}\n","import { debugFn } from '@socketsecurity/registry/lib/debug'\nimport { spawn } from '@socketsecurity/registry/lib/spawn'\n\nimport { scanPackagesAndLogAlerts } from '../common.mts'\nimport { ensureIpcInStdio } from '../stdio-ipc.mts'\nimport { installLinks } from './link.mts'\nimport constants, { YARN } from '../../constants.mts'\nimport { cmdFlagsToString } from '../../utils/cmd.mts'\nimport { getPublicApiToken } from '../../utils/sdk.mts'\n\nimport type { IpcObject } from '../../constants.mts'\nimport type {\n SpawnExtra,\n SpawnOptions,\n SpawnResult,\n} from '@socketsecurity/registry/lib/spawn'\n\nexport type ShadowYarnOptions = SpawnOptions & {\n ipc?: IpcObject | undefined\n}\n\nexport type ShadowYarnResult = {\n spawnPromise: SpawnResult<string, SpawnExtra | undefined>\n}\n\nconst DLX_COMMANDS = new Set(['dlx'])\n\nconst INSTALL_COMMANDS = new Set([\n 'add',\n 'install',\n 'up',\n 'upgrade',\n 'upgrade-interactive',\n])\n\nexport default async function shadowYarnBin(\n args: string[] | readonly string[] = process.argv.slice(2),\n options?: ShadowYarnOptions | undefined,\n extra?: SpawnExtra | undefined,\n): Promise<ShadowYarnResult> {\n const opts = { __proto__: null, ...options } as ShadowYarnOptions\n const { env: spawnEnv, ipc, ...spawnOpts } = opts\n const { spinner } = opts\n\n const wasSpinning = !!spinner?.isSpinning\n\n spinner?.start()\n\n const terminatorPos = args.indexOf('--')\n const rawYarnArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos)\n const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos)\n\n // Check for package scanning.\n const command = rawYarnArgs[0]\n const scanResult = await scanPackagesAndLogAlerts({\n acceptRisks: !!constants.ENV.SOCKET_CLI_ACCEPT_RISKS,\n command,\n cwd: process.cwd(),\n dlxCommands: DLX_COMMANDS,\n installCommands: INSTALL_COMMANDS,\n managerName: 'yarn',\n rawArgs: rawYarnArgs,\n spinner,\n viewAllRisks: !!constants.ENV.SOCKET_CLI_VIEW_ALL_RISKS,\n })\n\n if (scanResult.shouldExit) {\n // eslint-disable-next-line n/no-process-exit\n process.exit(1)\n // This line is never reached in production, but helps tests.\n throw new Error('process.exit called')\n }\n\n const realYarnPath = await installLinks(constants.shadowBinPath, YARN)\n\n spinner?.stop()\n\n const suffixArgs = [...rawYarnArgs, ...otherArgs]\n\n debugFn(\n 'notice',\n `spawn: ${YARN} shadow bin ${realYarnPath} ${cmdFlagsToString(suffixArgs)}`,\n )\n\n if (wasSpinning) {\n spinner?.start()\n }\n\n // Set up stdio with IPC channel.\n const stdio = ensureIpcInStdio(spawnOpts.stdio)\n\n const spawnPromise = spawn(\n realYarnPath,\n suffixArgs,\n {\n ...spawnOpts,\n env: {\n ...process.env,\n ...spawnEnv,\n },\n stdio,\n // On Windows, yarn is often a .cmd file that requires shell execution.\n // The spawn function from @socketsecurity/registry will handle this properly\n // when shell is true.\n shell: constants.WIN32,\n },\n extra,\n )\n\n // Send IPC handshake.\n spawnPromise.process.send({\n [constants.SOCKET_IPC_HANDSHAKE]: {\n [constants.SOCKET_CLI_SHADOW_API_TOKEN]: getPublicApiToken(),\n [constants.SOCKET_CLI_SHADOW_BIN]: YARN,\n [constants.SOCKET_CLI_SHADOW_PROGRESS]: true,\n ...ipc,\n },\n })\n\n return { spawnPromise }\n}\n"],"names":["WIN32","env","__proto__","spinner","acceptRisks","cwd","dlxCommands","installCommands","managerName","rawArgs","viewAllRisks","process","debugFn","spawnPromise"],"mappings":";;;;;;;;;;;AAUO;AAIL;;AACQA;AAAM;;AAGZ;AACF;AAEA;;AAGE;;AAKA;;AACQC;AAAI;AACZA;AACF;AAEA;AACF;;ACVA;AAEA;AAQe;AAKb;AAAeC;;;;AACPD;;;AAAiC;;AACjCE;AAAQ;AAEhB;;AAIA;AACA;AACA;;AAEA;AACA;AACA;AACEC;;AAEAC;AACAC;AACAC;AACAC;AACAC;;AAEAC;AACF;;AAGE;AACAC;AACA;AACA;AACF;;;;AAQAC;AAKA;;AAEA;;AAEA;AACA;AAEA;AAII;AACAX;;;;;AAKA;AACA;AACA;;;;AAMJ;AACAY;;AAEI;AACA;AACA;;AAEF;AACF;;AAESA;;AACX;;","debugId":"1cf735f7-d72e-4a95-9a5f-bc6822d5a468"}