@socketsecurity/cli 1.1.28 → 1.1.30

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.1.30](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.30) - 2025-11-18
8
+
9
+ ### Changed
10
+ - Enhanced `SOCKET_CLI_COANA_LOCAL_PATH` to support compiled Coana CLI binaries alongside Node.js script files
11
+
12
+ ### Fixed
13
+ - Resolved PR creation workflow to properly recreate pull requests after closing or merging
14
+ - Corrected API token selection to honor `SOCKET_CLI_API_TOKEN` environment variable in package alert requests
15
+
16
+ ## [1.1.29](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.29) - 2025-11-16
17
+
18
+ ### Added
19
+ - Added options `--reach-concurrency <number>` and `--reach-disable-analysis-splitting` for `socket scan create --reach`
20
+
7
21
  ## [1.1.28](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.28) - 2025-11-13
8
22
 
9
23
  ### Added
package/dist/cli.js CHANGED
@@ -1631,7 +1631,7 @@ async function performReachabilityAnalysis(options) {
1631
1631
  spinner?.infoAndStop('Running reachability analysis with Coana...');
1632
1632
 
1633
1633
  // Build Coana arguments.
1634
- const coanaArgs = ['run', cwd, '--output-dir', cwd, '--socket-mode', constants.default.DOT_SOCKET_DOT_FACTS_JSON, '--disable-report-submission', ...(reachabilityOptions.reachAnalysisTimeout ? ['--analysis-timeout', `${reachabilityOptions.reachAnalysisTimeout}`] : []), ...(reachabilityOptions.reachAnalysisMemoryLimit ? ['--memory-limit', `${reachabilityOptions.reachAnalysisMemoryLimit}`] : []), ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), ...(tarHash ? ['--run-without-docker', '--manifests-tar-hash', tarHash] : []),
1634
+ const coanaArgs = ['run', cwd, '--output-dir', cwd, '--socket-mode', constants.default.DOT_SOCKET_DOT_FACTS_JSON, '--disable-report-submission', ...(reachabilityOptions.reachAnalysisTimeout ? ['--analysis-timeout', `${reachabilityOptions.reachAnalysisTimeout}`] : []), ...(reachabilityOptions.reachAnalysisMemoryLimit ? ['--memory-limit', `${reachabilityOptions.reachAnalysisMemoryLimit}`] : []), ...(reachabilityOptions.reachConcurrency ? ['--concurrency', `${reachabilityOptions.reachConcurrency}`] : []), ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), ...(reachabilityOptions.reachDisableAnalysisSplitting ? ['--disable-analysis-splitting'] : []), ...(tarHash ? ['--run-without-docker', '--manifests-tar-hash', tarHash] : []),
1635
1635
  // Empty reachEcosystems implies scanning all ecosystems.
1636
1636
  ...(reachabilityOptions.reachEcosystems.length ? ['--purl-types', ...reachabilityOptions.reachEcosystems] : []), ...(reachabilityOptions.reachExcludePaths.length ? ['--exclude-dirs', ...reachabilityOptions.reachExcludePaths] : []), ...(reachabilityOptions.reachSkipCache ? ['--skip-cache-usage'] : [])];
1637
1637
 
@@ -2371,7 +2371,9 @@ async function handleCi(autoManifest) {
2371
2371
  reach: {
2372
2372
  reachAnalysisTimeout: 0,
2373
2373
  reachAnalysisMemoryLimit: 0,
2374
+ reachConcurrency: 1,
2374
2375
  reachDisableAnalytics: false,
2376
+ reachDisableAnalysisSplitting: false,
2375
2377
  reachEcosystems: [],
2376
2378
  reachExcludePaths: [],
2377
2379
  reachSkipCache: false,
@@ -3262,6 +3264,68 @@ const cmdConfig = {
3262
3264
  }
3263
3265
  };
3264
3266
 
3267
+ /**
3268
+ * Branch cleanup utilities for socket fix command.
3269
+ * Manages local and remote branch lifecycle during PR creation.
3270
+ *
3271
+ * Critical distinction: Remote branches are sacred when a PR exists, disposable when they don't.
3272
+ */
3273
+
3274
+
3275
+ /**
3276
+ * Clean up a stale branch (both remote and local).
3277
+ * Safe to delete both since no PR exists for this branch.
3278
+ *
3279
+ * Returns true if cleanup succeeded or should continue, false if should skip GHSA.
3280
+ */
3281
+ async function cleanupStaleBranch(branch, ghsaId, cwd) {
3282
+ logger.logger.warn(`Stale branch ${branch} found without open PR, cleaning up...`);
3283
+ require$$9.debugFn('notice', `cleanup: deleting stale branch ${branch}`);
3284
+ const deleted = await utils.gitDeleteRemoteBranch(branch, cwd);
3285
+ if (!deleted) {
3286
+ logger.logger.error(`Failed to delete stale remote branch ${branch}, skipping ${ghsaId}.`);
3287
+ require$$9.debugFn('error', `cleanup: remote deletion failed for ${branch}`);
3288
+ return false;
3289
+ }
3290
+
3291
+ // Clean up local branch too to avoid conflicts.
3292
+ await utils.gitDeleteBranch(branch, cwd);
3293
+ return true;
3294
+ }
3295
+
3296
+ /**
3297
+ * Clean up branches after PR creation failure.
3298
+ * Safe to delete both remote and local since no PR was created.
3299
+ */
3300
+ async function cleanupFailedPrBranches(branch, cwd) {
3301
+ // Clean up pushed branch since PR creation failed.
3302
+ // Safe to delete both remote and local since no PR exists.
3303
+ await utils.gitDeleteRemoteBranch(branch, cwd);
3304
+ await utils.gitDeleteBranch(branch, cwd);
3305
+ }
3306
+
3307
+ /**
3308
+ * Clean up local branch after successful PR creation.
3309
+ * Keeps remote branch - PR needs it to be mergeable.
3310
+ */
3311
+ async function cleanupSuccessfulPrLocalBranch(branch, cwd) {
3312
+ // Clean up local branch only - keep remote branch for PR merge.
3313
+ await utils.gitDeleteBranch(branch, cwd);
3314
+ }
3315
+
3316
+ /**
3317
+ * Clean up branches in catch block after unexpected error.
3318
+ * Safe to delete both remote and local since no PR was created.
3319
+ */
3320
+ async function cleanupErrorBranches(branch, cwd, remoteBranchExists) {
3321
+ // Clean up remote branch if it exists (push may have succeeded before error).
3322
+ // Safe to delete both remote and local since no PR was created.
3323
+ if (remoteBranchExists) {
3324
+ await utils.gitDeleteRemoteBranch(branch, cwd);
3325
+ }
3326
+ await utils.gitDeleteBranch(branch, cwd);
3327
+ }
3328
+
3265
3329
  const GITHUB_ADVISORIES_URL = 'https://github.com/advisories';
3266
3330
  function getSocketFixBranchName(ghsaId) {
3267
3331
  return `socket/fix/${ghsaId}`;
@@ -3321,17 +3385,66 @@ async function openSocketFixPr(owner, repo, branch, ghsaIds, options) {
3321
3385
  require$$9.debugDir('inspect', {
3322
3386
  octokitPullsCreateParams
3323
3387
  });
3324
- return await octokit.pulls.create(octokitPullsCreateParams);
3388
+ const pr = await octokit.pulls.create(octokitPullsCreateParams);
3389
+ return {
3390
+ ok: true,
3391
+ pr
3392
+ };
3325
3393
  } catch (e) {
3326
- let message = `Failed to open pull request`;
3327
- const errors = e instanceof vendor.RequestError ? e.response?.data?.['errors'] : undefined;
3328
- if (Array.isArray(errors) && errors.length) {
3329
- const details = errors.map(d => `- ${d.message?.trim() ?? `${d.resource}.${d.field} (${d.code})`}`).join('\n');
3330
- message += `:\n${details}`;
3394
+ // Handle RequestError from Octokit.
3395
+ if (e instanceof vendor.RequestError) {
3396
+ const errors = e.response?.data?.['errors'];
3397
+ const errorMessages = Array.isArray(errors) ? errors.map(d => d.message?.trim() ?? `${d.resource}.${d.field} (${d.code})`) : [];
3398
+
3399
+ // Check for "PR already exists" error.
3400
+ if (errorMessages.some(msg => msg.toLowerCase().includes('pull request already exists'))) {
3401
+ require$$9.debugFn('error', 'Failed to open pull request: already exists');
3402
+ return {
3403
+ ok: false,
3404
+ reason: 'already_exists',
3405
+ error: e
3406
+ };
3407
+ }
3408
+
3409
+ // Check for validation errors (e.g., no commits between branches).
3410
+ if (errors && errors.length > 0) {
3411
+ const details = errorMessages.map(d => `- ${d}`).join('\n');
3412
+ require$$9.debugFn('error', `Failed to open pull request:\n${details}`);
3413
+ return {
3414
+ ok: false,
3415
+ reason: 'validation_error',
3416
+ error: e,
3417
+ details
3418
+ };
3419
+ }
3420
+
3421
+ // Check HTTP status codes.
3422
+ if (e.status === 403 || e.status === 401) {
3423
+ require$$9.debugFn('error', 'Failed to open pull request: permission denied');
3424
+ return {
3425
+ ok: false,
3426
+ reason: 'permission_denied',
3427
+ error: e
3428
+ };
3429
+ }
3430
+ if (e.status && e.status >= 500) {
3431
+ require$$9.debugFn('error', 'Failed to open pull request: network error');
3432
+ return {
3433
+ ok: false,
3434
+ reason: 'network_error',
3435
+ error: e
3436
+ };
3437
+ }
3331
3438
  }
3332
- require$$9.debugFn('error', message);
3439
+
3440
+ // Unknown error.
3441
+ require$$9.debugFn('error', `Failed to open pull request: ${e}`);
3442
+ return {
3443
+ ok: false,
3444
+ reason: 'unknown',
3445
+ error: e
3446
+ };
3333
3447
  }
3334
- return undefined;
3335
3448
  }
3336
3449
  async function getSocketFixPrs(owner, repo, options) {
3337
3450
  return (await getSocketFixPrsWithContext(owner, repo, options)).map(d => d.match);
@@ -3758,10 +3871,34 @@ async function coanaFix(fixConfig) {
3758
3871
  overallFixed = true;
3759
3872
  const branch = getSocketFixBranchName(ghsaId);
3760
3873
  try {
3761
- // Check if branch already exists.
3874
+ // Check if an open PR already exists for this GHSA.
3875
+ // eslint-disable-next-line no-await-in-loop
3876
+ const existingOpenPrs = await getSocketFixPrs(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, {
3877
+ ghsaId,
3878
+ states: constants.GQL_PR_STATE_OPEN
3879
+ });
3880
+ if (existingOpenPrs.length > 0) {
3881
+ const prNum = existingOpenPrs[0].number;
3882
+ logger.logger.info(`PR #${prNum} already exists for ${ghsaId}, skipping.`);
3883
+ require$$9.debugFn('notice', `skip: open PR #${prNum} exists for ${ghsaId}`);
3884
+ continue ghsaLoop;
3885
+ }
3886
+
3887
+ // If branch exists but no open PR, delete the stale branch.
3888
+ // This handles cases where PR creation failed but branch was pushed.
3762
3889
  // eslint-disable-next-line no-await-in-loop
3763
3890
  if (await utils.gitRemoteBranchExists(branch, cwd)) {
3764
- require$$9.debugFn('notice', `skip: remote branch "${branch}" exists`);
3891
+ // eslint-disable-next-line no-await-in-loop
3892
+ const shouldContinue = await cleanupStaleBranch(branch, ghsaId, cwd);
3893
+ if (!shouldContinue) {
3894
+ continue ghsaLoop;
3895
+ }
3896
+ }
3897
+
3898
+ // Check for GitHub token before doing any git operations.
3899
+ if (!fixEnv.githubToken) {
3900
+ logger.logger.error('Cannot create pull request: SOCKET_CLI_GITHUB_TOKEN environment variable is not set.\n' + 'Set SOCKET_CLI_GITHUB_TOKEN or GITHUB_TOKEN to enable PR creation.');
3901
+ require$$9.debugFn('error', `skip: missing GitHub token for ${ghsaId}`);
3765
3902
  continue ghsaLoop;
3766
3903
  }
3767
3904
  require$$9.debugFn('notice', `pr: creating for ${ghsaId}`);
@@ -3792,31 +3929,21 @@ async function coanaFix(fixConfig) {
3792
3929
  }
3793
3930
 
3794
3931
  // Set up git remote.
3795
- if (!fixEnv.githubToken) {
3796
- logger.logger.error('Cannot create pull request: SOCKET_CLI_GITHUB_TOKEN environment variable is not set.\n' + 'Set SOCKET_CLI_GITHUB_TOKEN or GITHUB_TOKEN to enable PR creation.');
3797
- // eslint-disable-next-line no-await-in-loop
3798
- await utils.gitResetAndClean(fixEnv.baseBranch, cwd);
3799
- // eslint-disable-next-line no-await-in-loop
3800
- await utils.gitCheckoutBranch(fixEnv.baseBranch, cwd);
3801
- // eslint-disable-next-line no-await-in-loop
3802
- await utils.gitDeleteBranch(branch, cwd);
3803
- continue ghsaLoop;
3804
- }
3805
3932
  // eslint-disable-next-line no-await-in-loop
3806
3933
  await utils.setGitRemoteGithubRepoUrl(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, fixEnv.githubToken, cwd);
3807
3934
 
3808
3935
  // eslint-disable-next-line no-await-in-loop
3809
- const prResponse = await openSocketFixPr(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, branch,
3936
+ const prResult = await openSocketFixPr(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, branch,
3810
3937
  // Single GHSA ID.
3811
3938
  [ghsaId], {
3812
3939
  baseBranch: fixEnv.baseBranch,
3813
3940
  cwd,
3814
3941
  ghsaDetails
3815
3942
  });
3816
- if (prResponse) {
3943
+ if (prResult.ok) {
3817
3944
  const {
3818
3945
  data
3819
- } = prResponse;
3946
+ } = prResult.pr;
3820
3947
  const prRef = `PR #${data.number}`;
3821
3948
  logger.logger.success(`Opened ${prRef} for ${ghsaId}.`);
3822
3949
  if (autopilot) {
@@ -3836,16 +3963,47 @@ async function coanaFix(fixConfig) {
3836
3963
  logger.logger.dedent();
3837
3964
  spinner?.dedent();
3838
3965
  }
3966
+
3967
+ // Clean up local branch only - keep remote branch for PR merge.
3968
+ // eslint-disable-next-line no-await-in-loop
3969
+ await cleanupSuccessfulPrLocalBranch(branch, cwd);
3970
+ } else {
3971
+ // Handle PR creation failures.
3972
+ if (prResult.reason === 'already_exists') {
3973
+ logger.logger.info(`PR already exists for ${ghsaId} (this should not happen due to earlier check).`);
3974
+ // Don't delete branch - PR exists and needs it.
3975
+ } else if (prResult.reason === 'validation_error') {
3976
+ logger.logger.error(`Failed to create PR for ${ghsaId}:\n${prResult.details}`);
3977
+ // eslint-disable-next-line no-await-in-loop
3978
+ await cleanupFailedPrBranches(branch, cwd);
3979
+ } else if (prResult.reason === 'permission_denied') {
3980
+ logger.logger.error(`Failed to create PR for ${ghsaId}: Permission denied. Check SOCKET_CLI_GITHUB_TOKEN permissions.`);
3981
+ // eslint-disable-next-line no-await-in-loop
3982
+ await cleanupFailedPrBranches(branch, cwd);
3983
+ } else if (prResult.reason === 'network_error') {
3984
+ logger.logger.error(`Failed to create PR for ${ghsaId}: Network error. Please try again.`);
3985
+ // eslint-disable-next-line no-await-in-loop
3986
+ await cleanupFailedPrBranches(branch, cwd);
3987
+ } else {
3988
+ logger.logger.error(`Failed to create PR for ${ghsaId}: ${prResult.error.message}`);
3989
+ // eslint-disable-next-line no-await-in-loop
3990
+ await cleanupFailedPrBranches(branch, cwd);
3991
+ }
3839
3992
  }
3840
3993
 
3841
3994
  // Reset back to base branch for next iteration.
3842
3995
  // eslint-disable-next-line no-await-in-loop
3843
- await utils.gitResetAndClean(branch, cwd);
3996
+ await utils.gitResetAndClean(fixEnv.baseBranch, cwd);
3844
3997
  // eslint-disable-next-line no-await-in-loop
3845
3998
  await utils.gitCheckoutBranch(fixEnv.baseBranch, cwd);
3846
3999
  } catch (e) {
3847
4000
  logger.logger.warn(`Unexpected condition: Push failed for ${ghsaId}, skipping PR creation.`);
3848
4001
  require$$9.debugDir('error', e);
4002
+ // Clean up branches (push may have succeeded before error).
4003
+ // eslint-disable-next-line no-await-in-loop
4004
+ const remoteBranchExists = await utils.gitRemoteBranchExists(branch, cwd);
4005
+ // eslint-disable-next-line no-await-in-loop
4006
+ await cleanupErrorBranches(branch, cwd, remoteBranchExists);
3849
4007
  // eslint-disable-next-line no-await-in-loop
3850
4008
  await utils.gitResetAndClean(fixEnv.baseBranch, cwd);
3851
4009
  // eslint-disable-next-line no-await-in-loop
@@ -10858,11 +11016,21 @@ const reachabilityFlags = {
10858
11016
  default: 0,
10859
11017
  description: 'Set timeout for the reachability analysis. Split analysis runs may cause the total scan time to exceed this timeout significantly.'
10860
11018
  },
11019
+ reachConcurrency: {
11020
+ type: 'number',
11021
+ default: 1,
11022
+ description: 'Set the maximum number of concurrent reachability analysis runs. It is recommended to choose a concurrency level that ensures each analysis run has at least the --reach-analysis-memory-limit amount of memory available. NPM reachability analysis does not support concurrent execution, so the concurrency level is ignored for NPM.'
11023
+ },
10861
11024
  reachDisableAnalytics: {
10862
11025
  type: 'boolean',
10863
11026
  default: false,
10864
11027
  description: 'Disable reachability analytics sharing with Socket. Also disables caching-based optimizations.'
10865
11028
  },
11029
+ reachDisableAnalysisSplitting: {
11030
+ type: 'boolean',
11031
+ default: false,
11032
+ description: 'Limits Coana to at most 1 reachability analysis run per workspace.'
11033
+ },
10866
11034
  reachEcosystems: {
10867
11035
  type: 'string',
10868
11036
  isMultiple: true,
@@ -11081,6 +11249,8 @@ async function run$d(argv, importMeta, {
11081
11249
  reach,
11082
11250
  reachAnalysisMemoryLimit,
11083
11251
  reachAnalysisTimeout,
11252
+ reachConcurrency,
11253
+ reachDisableAnalysisSplitting,
11084
11254
  reachDisableAnalytics,
11085
11255
  reachSkipCache,
11086
11256
  readOnly,
@@ -11208,8 +11378,9 @@ async function run$d(argv, importMeta, {
11208
11378
  const hasReachExcludePaths = reachExcludePaths.length > 0;
11209
11379
  const isUsingNonDefaultMemoryLimit = reachAnalysisMemoryLimit !== reachabilityFlags['reachAnalysisMemoryLimit']?.default;
11210
11380
  const isUsingNonDefaultTimeout = reachAnalysisTimeout !== reachabilityFlags['reachAnalysisTimeout']?.default;
11381
+ const isUsingNonDefaultConcurrency = reachConcurrency !== reachabilityFlags['reachConcurrency']?.default;
11211
11382
  const isUsingNonDefaultAnalytics = reachDisableAnalytics !== reachabilityFlags['reachDisableAnalytics']?.default;
11212
- const isUsingAnyReachabilityFlags = isUsingNonDefaultMemoryLimit || isUsingNonDefaultTimeout || isUsingNonDefaultAnalytics || hasReachEcosystems || hasReachExcludePaths || reachSkipCache;
11383
+ const isUsingAnyReachabilityFlags = isUsingNonDefaultMemoryLimit || isUsingNonDefaultTimeout || isUsingNonDefaultConcurrency || isUsingNonDefaultAnalytics || hasReachEcosystems || hasReachExcludePaths || reachSkipCache || reachDisableAnalysisSplitting;
11213
11384
  const wasValidInput = utils.checkCommandInput(outputKind, {
11214
11385
  nook: true,
11215
11386
  test: !!orgSlug,
@@ -11270,6 +11441,8 @@ async function run$d(argv, importMeta, {
11270
11441
  reachDisableAnalytics: Boolean(reachDisableAnalytics),
11271
11442
  reachAnalysisTimeout: Number(reachAnalysisTimeout),
11272
11443
  reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit),
11444
+ reachConcurrency: Number(reachConcurrency),
11445
+ reachDisableAnalysisSplitting: Boolean(reachDisableAnalysisSplitting),
11273
11446
  reachEcosystems,
11274
11447
  reachExcludePaths,
11275
11448
  reachSkipCache: Boolean(reachSkipCache)
@@ -11915,6 +12088,8 @@ async function scanOneRepo(repoSlug, {
11915
12088
  reachDisableAnalytics: false,
11916
12089
  reachAnalysisTimeout: 0,
11917
12090
  reachAnalysisMemoryLimit: 0,
12091
+ reachConcurrency: 1,
12092
+ reachDisableAnalysisSplitting: false,
11918
12093
  reachEcosystems: [],
11919
12094
  reachExcludePaths: [],
11920
12095
  reachSkipCache: false
@@ -13185,6 +13360,8 @@ async function run$7(argv, importMeta, {
13185
13360
  org: orgFlag,
13186
13361
  reachAnalysisMemoryLimit,
13187
13362
  reachAnalysisTimeout,
13363
+ reachConcurrency,
13364
+ reachDisableAnalysisSplitting,
13188
13365
  reachDisableAnalytics,
13189
13366
  reachSkipCache
13190
13367
  } = cli.flags;
@@ -13250,7 +13427,9 @@ async function run$7(argv, importMeta, {
13250
13427
  reachabilityOptions: {
13251
13428
  reachAnalysisTimeout: Number(reachAnalysisTimeout),
13252
13429
  reachAnalysisMemoryLimit: Number(reachAnalysisMemoryLimit),
13430
+ reachConcurrency: Number(reachConcurrency),
13253
13431
  reachDisableAnalytics: Boolean(reachDisableAnalytics),
13432
+ reachDisableAnalysisSplitting: Boolean(reachDisableAnalysisSplitting),
13254
13433
  reachEcosystems,
13255
13434
  reachExcludePaths,
13256
13435
  reachSkipCache: Boolean(reachSkipCache)
@@ -15091,5 +15270,5 @@ void (async () => {
15091
15270
  await utils.captureException(e);
15092
15271
  }
15093
15272
  })();
15094
- //# debugId=13d5a945-42af-4203-b65f-268cf102639c
15273
+ //# debugId=dbcc0fa8-7ea6-462d-9ebe-824e2129f7b8
15095
15274
  //# sourceMappingURL=cli.js.map