@socketsecurity/cli-with-sentry 1.14.1 → 1.15.0

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 (56) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +31 -6
  3. package/dist/cli.js +355 -206
  4. package/dist/cli.js.map +1 -1
  5. package/dist/constants.js +7 -4
  6. package/dist/constants.js.map +1 -1
  7. package/dist/manifest-scripts/dotnet-tool/socket-facts-dotnet.dll +0 -0
  8. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  9. package/dist/shadow-npm-bin2.js +5 -2
  10. package/dist/shadow-npm-bin2.js.map +1 -1
  11. package/dist/shadow-pnpm-bin2.js +3 -2
  12. package/dist/shadow-pnpm-bin2.js.map +1 -1
  13. package/dist/shadow-yarn-bin.js +4 -2
  14. package/dist/shadow-yarn-bin.js.map +1 -1
  15. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  16. package/dist/types/commands/config/handle-config-set.d.mts.map +1 -1
  17. package/dist/types/commands/login/apply-login.d.mts.map +1 -1
  18. package/dist/types/commands/login/attempt-login.d.mts +5 -1
  19. package/dist/types/commands/login/attempt-login.d.mts.map +1 -1
  20. package/dist/types/commands/login/cmd-login.d.mts.map +1 -1
  21. package/dist/types/commands/logout/apply-logout.d.mts.map +1 -1
  22. package/dist/types/commands/logout/attempt-logout.d.mts +1 -1
  23. package/dist/types/commands/logout/attempt-logout.d.mts.map +1 -1
  24. package/dist/types/commands/manifest/bazel/bazel-socket-json-trust.d.mts +35 -0
  25. package/dist/types/commands/manifest/bazel/bazel-socket-json-trust.d.mts.map +1 -0
  26. package/dist/types/commands/manifest/bazel/cmd-manifest-bazel.d.mts.map +1 -1
  27. package/dist/types/commands/scan/fetch-report-data.d.mts +1 -8
  28. package/dist/types/commands/scan/fetch-report-data.d.mts.map +1 -1
  29. package/dist/types/commands/scan/generate-report.d.mts +1 -2
  30. package/dist/types/commands/scan/generate-report.d.mts.map +1 -1
  31. package/dist/types/commands/scan/output-scan-report.d.mts +0 -2
  32. package/dist/types/commands/scan/output-scan-report.d.mts.map +1 -1
  33. package/dist/types/constants.d.mts +4 -2
  34. package/dist/types/constants.d.mts.map +1 -1
  35. package/dist/types/shadow/npm-base.d.mts.map +1 -1
  36. package/dist/types/shadow/pnpm/bin.d.mts.map +1 -1
  37. package/dist/types/shadow/yarn/bin.d.mts.map +1 -1
  38. package/dist/types/utils/api-transport.d.mts +4 -0
  39. package/dist/types/utils/api-transport.d.mts.map +1 -0
  40. package/dist/types/utils/api.d.mts +2 -1
  41. package/dist/types/utils/api.d.mts.map +1 -1
  42. package/dist/types/utils/cmd.d.mts.map +1 -1
  43. package/dist/types/utils/coana-release.d.mts.map +1 -1
  44. package/dist/types/utils/config.d.mts +1 -0
  45. package/dist/types/utils/config.d.mts.map +1 -1
  46. package/dist/types/utils/oauth.d.mts +33 -0
  47. package/dist/types/utils/oauth.d.mts.map +1 -0
  48. package/dist/types/utils/package-manager.d.mts +2 -2
  49. package/dist/types/utils/package-manager.d.mts.map +1 -1
  50. package/dist/types/utils/sdk.d.mts +14 -6
  51. package/dist/types/utils/sdk.d.mts.map +1 -1
  52. package/dist/utils.js +809 -128
  53. package/dist/utils.js.map +1 -1
  54. package/dist/vendor.js +567 -567
  55. package/package.json +2 -2
  56. package/requirements.json +2 -2
package/dist/cli.js CHANGED
@@ -1194,132 +1194,60 @@ async function finalizeTier1Scan(tier1ReachabilityScanId, scanId) {
1194
1194
  });
1195
1195
  }
1196
1196
 
1197
- /**
1198
- * This fetches all the relevant pieces of data to generate a report, given a
1199
- * full scan ID.
1200
- */
1197
+ /** Fetch the scan data needed to generate a report. */
1201
1198
  async function fetchScanData(orgSlug, scanId, options) {
1202
- const {
1203
- includeLicensePolicy,
1204
- sdkOpts
1205
- } = {
1206
- __proto__: null,
1207
- ...options
1208
- };
1209
- const sockSdkCResult = await utils.setupSdk(sdkOpts);
1210
- if (!sockSdkCResult.ok) {
1211
- return sockSdkCResult;
1212
- }
1213
- const sockSdk = sockSdkCResult.data;
1214
- let policyStatus = 'requested...';
1215
- let scanStatus = 'requested...';
1216
- let finishedFetching = false;
1217
1199
  const {
1218
1200
  spinner
1219
1201
  } = constants.default;
1220
- function updateScan(status) {
1221
- scanStatus = status;
1222
- updateProgress();
1223
- }
1224
- function updatePolicy(status) {
1225
- policyStatus = status;
1226
- updateProgress();
1227
- }
1228
- function updateProgress() {
1229
- if (finishedFetching) {
1230
- spinner.stop();
1231
- logger.logger.info(`Scan result: ${scanStatus}. Security policy: ${policyStatus}.`);
1232
- } else {
1233
- spinner.start(`Scan result: ${scanStatus}. Security policy: ${policyStatus}.`);
1234
- }
1235
- }
1236
- async function fetchScanResult() {
1237
- const result = await utils.queryApiSafeText(`orgs/${orgSlug}/full-scans/${encodeURIComponent(scanId)}${includeLicensePolicy ? '?include_license_details=true' : ''}`);
1238
- updateScan(`response received`);
1202
+ spinner.start('Scan result: requested...');
1203
+ try {
1204
+ const result = await utils.queryApiSafeText(`orgs/${orgSlug}/full-scans/${encodeURIComponent(scanId)}${options?.includeLicensePolicy ? '?include_license_details=true' : ''}`);
1239
1205
  if (!result.ok) {
1206
+ spinner.stop();
1207
+ logger.logger.info('Scan result: request failed.');
1240
1208
  return result;
1241
1209
  }
1242
- const ndJsonString = result.data;
1243
-
1244
- // This is nd-json; each line is a json object.
1245
- const lines = ndJsonString.split('\n').filter(Boolean);
1246
- let ok = true;
1247
- const data = lines.map(line => {
1210
+ const data = [];
1211
+ for (const line of result.data.split('\n').filter(Boolean)) {
1248
1212
  try {
1249
- return JSON.parse(line);
1250
- } catch (e) {
1251
- ok = false;
1213
+ data.push(JSON.parse(line));
1214
+ } catch (error) {
1252
1215
  debug.debugFn('error', 'Failed to parse report data line as JSON');
1253
1216
  debug.debugDir('error', {
1254
- error: e,
1217
+ error,
1255
1218
  line
1256
1219
  });
1257
- return;
1220
+ spinner.stop();
1221
+ logger.logger.info('Scan result: received invalid JSON response.');
1222
+ return {
1223
+ ok: false,
1224
+ message: 'Invalid Socket API response',
1225
+ cause: 'The Socket API responded with at least one line that was not valid JSON. Please report if this persists.'
1226
+ };
1258
1227
  }
1259
- });
1260
- if (ok) {
1261
- updateScan('success');
1262
- return {
1263
- ok: true,
1264
- data
1265
- };
1266
1228
  }
1267
- updateScan('received invalid JSON response');
1268
- return {
1269
- ok: false,
1270
- message: 'Invalid Socket API response',
1271
- cause: 'The Socket API responded with at least one line that was not valid JSON. Please report if this persists.'
1272
- };
1273
- }
1274
- async function fetchSecurityPolicy() {
1275
- const result = await utils.handleApiCallNoSpinner(sockSdk.getOrgSecurityPolicy(orgSlug), 'GetOrgSecurityPolicy');
1276
- updatePolicy('received policy');
1277
- return result;
1278
- }
1279
- updateProgress();
1280
- const [scan, securityPolicy] = await Promise.all([fetchScanResult().catch(e => {
1281
- updateScan('failure; unknown blocking error occurred');
1229
+ spinner.stop();
1230
+ logger.logger.info('Scan result: success.');
1282
1231
  return {
1283
- ok: false,
1284
- message: 'Socket API error',
1285
- cause: utils.formatErrorWithDetail('Error requesting scan', e) || 'Error requesting scan: (no error message found)'
1232
+ ok: true,
1233
+ data: {
1234
+ scan: data
1235
+ }
1286
1236
  };
1287
- }), fetchSecurityPolicy().catch(e => {
1288
- updatePolicy('failure; unknown blocking error occurred');
1237
+ } catch (error) {
1238
+ spinner.stop();
1239
+ logger.logger.info('Scan result: request failed.');
1289
1240
  return {
1290
1241
  ok: false,
1291
1242
  message: 'Socket API error',
1292
- cause: utils.formatErrorWithDetail('Error requesting policy', e) || 'Error requesting policy: (no error message found)'
1243
+ cause: utils.formatErrorWithDetail('Error requesting scan', error) || 'Error requesting scan: (no error message found)'
1293
1244
  };
1294
- })]).finally(() => {
1295
- finishedFetching = true;
1296
- updateProgress();
1297
- });
1298
- if (!scan.ok) {
1299
- return scan;
1300
1245
  }
1301
- if (!securityPolicy.ok) {
1302
- return securityPolicy;
1303
- }
1304
- if (!Array.isArray(scan.data)) {
1305
- return {
1306
- ok: false,
1307
- message: 'Failed to fetch',
1308
- cause: 'Was unable to fetch scan result, bailing'
1309
- };
1310
- }
1311
- return {
1312
- ok: true,
1313
- data: {
1314
- scan: scan.data,
1315
- securityPolicy: securityPolicy.data
1316
- }
1317
- };
1318
1246
  }
1319
1247
 
1320
1248
  // Note: The returned cResult will only be ok:false when the generation
1321
1249
  // failed. It won't reflect the healthy state.
1322
- function generateReport(scan, securityPolicy, {
1250
+ function generateReport(scan, {
1323
1251
  fold,
1324
1252
  orgSlug,
1325
1253
  reportLevel,
@@ -1341,7 +1269,7 @@ function generateReport(scan, securityPolicy, {
1341
1269
 
1342
1270
  // In the context of a report;
1343
1271
  // - the alert.severity is irrelevant
1344
- // - the securityPolicyDefault is irrelevant
1272
+ // - policy defaults are irrelevant because the API resolves actions per alert
1345
1273
  // - the report defaults to healthy:true with no alerts
1346
1274
  // - the appearance of an alert will trigger the policy action;
1347
1275
  // - error: healthy will end up as false, add alerts to report
@@ -1359,61 +1287,50 @@ function generateReport(scan, securityPolicy, {
1359
1287
 
1360
1288
  const violations = new Map();
1361
1289
  let healthy = true;
1362
- const securityRules = securityPolicy.securityPolicyRules;
1363
- if (securityRules) {
1364
- // Note: reportLevel: error > warn > monitor > ignore > defer
1365
- scan.forEach(artifact => {
1366
- const {
1367
- alerts,
1368
- name: pkgName = constants.UNKNOWN_VALUE,
1369
- type: ecosystem,
1370
- version = constants.UNKNOWN_VALUE
1371
- } = artifact;
1372
- alerts?.forEach(alert => {
1373
- const alertName = alert.type; // => policy[type]
1374
- const action = securityRules[alertName]?.action || '';
1375
- switch (action) {
1376
- case constants.default.REPORT_LEVEL_ERROR:
1377
- {
1378
- healthy = false;
1379
- if (!short) {
1380
- addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1381
- }
1382
- break;
1383
- }
1384
- case constants.default.REPORT_LEVEL_WARN:
1385
- {
1386
- if (!short && reportLevel !== constants.default.REPORT_LEVEL_ERROR) {
1387
- addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1388
- }
1389
- break;
1290
+
1291
+ // Note: reportLevel: error > warn > monitor > ignore > defer
1292
+ scan.forEach(artifact => {
1293
+ const {
1294
+ alerts,
1295
+ name: pkgName = constants.UNKNOWN_VALUE,
1296
+ type: ecosystem,
1297
+ version = constants.UNKNOWN_VALUE
1298
+ } = artifact;
1299
+ alerts?.forEach(alert => {
1300
+ const action = alert.action;
1301
+ switch (action) {
1302
+ case constants.default.REPORT_LEVEL_ERROR:
1303
+ {
1304
+ healthy = false;
1305
+ if (!short) {
1306
+ addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1390
1307
  }
1391
- case constants.default.REPORT_LEVEL_MONITOR:
1392
- {
1393
- if (!short && reportLevel !== constants.default.REPORT_LEVEL_WARN && reportLevel !== constants.default.REPORT_LEVEL_ERROR) {
1394
- addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1395
- }
1396
- break;
1308
+ break;
1309
+ }
1310
+ case constants.default.REPORT_LEVEL_WARN:
1311
+ {
1312
+ if (!short && reportLevel !== constants.default.REPORT_LEVEL_ERROR) {
1313
+ addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1397
1314
  }
1398
- case constants.default.REPORT_LEVEL_IGNORE:
1399
- {
1400
- if (!short && reportLevel !== constants.default.REPORT_LEVEL_MONITOR && reportLevel !== constants.default.REPORT_LEVEL_WARN && reportLevel !== constants.default.REPORT_LEVEL_ERROR) {
1401
- addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1402
- }
1403
- break;
1315
+ break;
1316
+ }
1317
+ case constants.default.REPORT_LEVEL_MONITOR:
1318
+ {
1319
+ if (!short && reportLevel !== constants.default.REPORT_LEVEL_WARN && reportLevel !== constants.default.REPORT_LEVEL_ERROR) {
1320
+ addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1404
1321
  }
1405
- case constants.default.REPORT_LEVEL_DEFER:
1406
- {
1407
- // Not sure but ignore for now. Defer to later ;)
1408
- if (!short && reportLevel === constants.default.REPORT_LEVEL_DEFER) {
1409
- addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1410
- }
1411
- break;
1322
+ break;
1323
+ }
1324
+ case constants.default.REPORT_LEVEL_IGNORE:
1325
+ {
1326
+ if (!short && reportLevel !== constants.default.REPORT_LEVEL_MONITOR && reportLevel !== constants.default.REPORT_LEVEL_WARN && reportLevel !== constants.default.REPORT_LEVEL_ERROR) {
1327
+ addAlert(artifact, violations, fold, ecosystem, pkgName, version, alert, action);
1412
1328
  }
1413
- }
1414
- });
1329
+ break;
1330
+ }
1331
+ }
1415
1332
  });
1416
- }
1333
+ });
1417
1334
  spinner?.successAndStop(`Generated reported in ${Date.now() - now} ms`);
1418
1335
  if (short) {
1419
1336
  return {
@@ -1556,7 +1473,7 @@ async function outputScanReport(result, {
1556
1473
  logger.logger.fail(utils.failMsgWithBadge(result.message, result.cause));
1557
1474
  return;
1558
1475
  }
1559
- const scanReport = generateReport(result.data.scan, result.data.securityPolicy, {
1476
+ const scanReport = generateReport(result.data.scan, {
1560
1477
  orgSlug,
1561
1478
  scanId,
1562
1479
  fold,
@@ -9652,6 +9569,11 @@ async function handleConfigSet({
9652
9569
  outputKind
9653
9570
  });
9654
9571
  const result = utils.updateConfigValue(key, value);
9572
+ if (result.ok && !result.data && key === constants.CONFIG_KEY_API_TOKEN) {
9573
+ // An explicit API-token update must take precedence over a saved OAuth
9574
+ // session, which otherwise remains the active credential source.
9575
+ utils.updateConfigValue(constants.CONFIG_KEY_AUTH_TYPE, 'api-token');
9576
+ }
9655
9577
 
9656
9578
  // `config set` is a one-shot command: an in-memory-only change is a no-op
9657
9579
  // because the process exits before anything reads it. updateConfigValue only
@@ -12047,13 +11969,20 @@ async function run$M(argv, importMeta, {
12047
11969
  function applyLogin(apiToken, enforcedOrgs, apiBaseUrl, apiProxy) {
12048
11970
  utils.updateConfigValue(constants.CONFIG_KEY_ENFORCED_ORGS, enforcedOrgs);
12049
11971
  utils.updateConfigValue(constants.CONFIG_KEY_API_TOKEN, apiToken);
11972
+ utils.updateConfigValue(constants.CONFIG_KEY_AUTH_TYPE, 'api-token');
12050
11973
  utils.updateConfigValue(constants.CONFIG_KEY_API_BASE_URL, apiBaseUrl);
12051
11974
  utils.updateConfigValue(constants.CONFIG_KEY_API_PROXY, apiProxy);
12052
11975
  }
12053
11976
 
12054
- async function attemptLogin(apiBaseUrl, apiProxy) {
11977
+ async function attemptLogin(apiBaseUrl, apiProxy, apiTokenMode = false) {
11978
+ if (!apiTokenMode) {
11979
+ return await attemptOAuthLogin(apiBaseUrl, apiProxy);
11980
+ }
11981
+ return await attemptApiTokenLogin(apiBaseUrl, apiProxy);
11982
+ }
11983
+ async function attemptApiTokenLogin(apiBaseUrl, apiProxy) {
12055
11984
  apiBaseUrl ??= utils.getConfigValueOrUndef(constants.CONFIG_KEY_API_BASE_URL) ?? undefined;
12056
- apiProxy ??= utils.getConfigValueOrUndef(constants.CONFIG_KEY_API_PROXY) ?? undefined;
11985
+ apiProxy ||= utils.getDefaultProxyUrl();
12057
11986
  const apiTokenInput = await prompts.password({
12058
11987
  message: `Enter your ${utils.socketDocsLink('/docs/api-keys', 'Socket.dev API token')} (leave blank to use a limited public token)`
12059
11988
  });
@@ -12171,6 +12100,18 @@ async function attemptLogin(apiBaseUrl, apiProxy) {
12171
12100
  const previousPersistedToken = utils.getConfigValueOrUndef(constants.CONFIG_KEY_API_TOKEN);
12172
12101
  try {
12173
12102
  applyLogin(apiToken, enforcedOrgs, apiBaseUrl, apiProxy);
12103
+ if (constants.default.socketAppDataPath && !utils.isConfigFromFlag()) {
12104
+ try {
12105
+ const result = await utils.logoutOAuth(constants.default.socketAppDataPath, {
12106
+ apiProxy
12107
+ });
12108
+ if (result.warning) {
12109
+ logger.logger.warn(result.warning);
12110
+ }
12111
+ } catch {
12112
+ logger.logger.warn('The previous browser sign-in could not be removed.');
12113
+ }
12114
+ }
12174
12115
  logger.logger.success(`API credentials ${previousPersistedToken === apiToken ? 'refreshed' : previousPersistedToken ? 'updated' : 'set'}`);
12175
12116
  if (utils.isConfigFromFlag()) {
12176
12117
  logger.logger.log('');
@@ -12178,12 +12119,103 @@ async function attemptLogin(apiBaseUrl, apiProxy) {
12178
12119
  }
12179
12120
  } catch {
12180
12121
  process.exitCode = 1;
12181
- logger.logger.fail(`API login failed`);
12122
+ logger.logger.fail('API login failed');
12123
+ }
12124
+ }
12125
+ async function attemptOAuthLogin(apiBaseUrl, apiProxy) {
12126
+ if (utils.isConfigFromFlag()) {
12127
+ throw new Error('Browser sign-in cannot be saved while --config or SOCKET_CLI_CONFIG is active. Remove the config override and run `socket login` again.');
12128
+ }
12129
+ const configuredBaseUrl = apiBaseUrl || utils.getDefaultApiBaseUrl();
12130
+ if (configuredBaseUrl) {
12131
+ let configuredOrigin;
12132
+ try {
12133
+ configuredOrigin = new URL(configuredBaseUrl).origin;
12134
+ } catch {
12135
+ throw new Error('The configured Socket API URL is invalid for OAuth.');
12136
+ }
12137
+ if (configuredOrigin !== utils.OAUTH_ISSUER) {
12138
+ throw new Error('Browser sign-in is only available with Socket’s trusted API URL. Use `socket login --api-token --api-base-url <url>` for a custom API URL.');
12139
+ }
12140
+ }
12141
+ const existingToken = utils.getConfigValueOrUndef(constants.CONFIG_KEY_API_TOKEN);
12142
+ const activeAuthType = utils.getConfigValueOrUndef(constants.CONFIG_KEY_AUTH_TYPE);
12143
+ if (existingToken && activeAuthType !== 'oauth') {
12144
+ const choice = await prompts.select({
12145
+ message: 'An API token is already saved. Keep using it or switch to read-only browser sign-in?',
12146
+ choices: [{
12147
+ name: 'Keep the saved API token',
12148
+ value: 'keep'
12149
+ }, {
12150
+ name: 'Switch to browser sign-in (read-only)',
12151
+ value: 'oauth'
12152
+ }]
12153
+ });
12154
+ if (choice === undefined) {
12155
+ logger.logger.fail('Canceled by user');
12156
+ return {
12157
+ ok: false,
12158
+ message: 'Canceled',
12159
+ cause: 'Canceled by user'
12160
+ };
12161
+ }
12162
+ if (choice === 'keep') {
12163
+ logger.logger.success('Kept the saved API token login');
12164
+ return {
12165
+ ok: true
12166
+ };
12167
+ }
12168
+ }
12169
+ apiProxy ||= utils.getDefaultProxyUrl();
12170
+ const result = await utils.beginOAuthLogin({
12171
+ apiProxy,
12172
+ openBrowser: true,
12173
+ onAuthorizationUrl(url) {
12174
+ logger.logger.log('Open this URL in your browser to sign in:');
12175
+ logger.logger.log(url);
12176
+ }
12177
+ });
12178
+ const baseUrl = new URL('/v0/', result.apiOrigin).href;
12179
+ const sdkResult = await utils.setupSdk({
12180
+ apiBaseUrl: baseUrl,
12181
+ apiProxy,
12182
+ apiToken: result.accessToken
12183
+ });
12184
+ if (!sdkResult.ok) {
12185
+ process.exitCode = 1;
12186
+ logger.logger.fail(utils.failMsgWithBadge(sdkResult.message, sdkResult.cause));
12187
+ return;
12188
+ }
12189
+ const orgsResult = await utils.fetchOrganization({
12190
+ description: 'browser sign-in validation',
12191
+ sdk: sdkResult.data
12192
+ });
12193
+ if (!orgsResult.ok) {
12194
+ process.exitCode = 1;
12195
+ logger.logger.fail(utils.failMsgWithBadge(orgsResult.message, orgsResult.cause));
12196
+ return;
12197
+ }
12198
+ const orgSlugs = utils.getOrgSlugs(orgsResult.data.organizations);
12199
+ if (orgSlugs.length === 0) {
12200
+ process.exitCode = 1;
12201
+ logger.logger.fail('Browser sign-in succeeded, but no accessible organization was returned. Check your organization membership and try again; existing credentials were kept.');
12202
+ return;
12203
+ }
12204
+ await utils.saveOAuthCredentials(constants.default.socketAppDataPath, result);
12205
+ utils.updateConfigValue(constants.CONFIG_KEY_API_TOKEN, null);
12206
+ utils.updateConfigValue(constants.CONFIG_KEY_DEFAULT_ORG, orgSlugs[0]);
12207
+ utils.updateConfigValue(constants.CONFIG_KEY_API_BASE_URL, null);
12208
+ utils.updateConfigValue(constants.CONFIG_KEY_API_PROXY, apiProxy);
12209
+ utils.updateConfigValue(constants.CONFIG_KEY_AUTH_TYPE, 'oauth');
12210
+ logger.logger.success(`Browser sign-in saved for ${arrays.joinAnd(orgSlugs)}`);
12211
+ logger.logger.warn('This sign-in is read-only and supports package, alert, and threat-feed queries. Write and scan commands require API-token credentials; use `socket login --api-token` or SOCKET_CLI_API_TOKEN.');
12212
+ if (constants.default.ENV.SOCKET_CLI_API_TOKEN) {
12213
+ logger.logger.warn('SOCKET_CLI_API_TOKEN remains active and takes precedence over browser sign-in for this session.');
12182
12214
  }
12183
12215
  }
12184
12216
 
12185
12217
  const CMD_NAME$r = 'login';
12186
- const description$x = 'Setup Socket CLI with an API token and defaults';
12218
+ const description$x = 'Sign in to Socket and configure the CLI';
12187
12219
  const hidden$r = false;
12188
12220
  const cmdLogin = {
12189
12221
  description: description$x,
@@ -12199,10 +12231,15 @@ async function run$L(argv, importMeta, {
12199
12231
  hidden: hidden$r,
12200
12232
  flags: {
12201
12233
  ...flags.commonFlags,
12234
+ apiToken: {
12235
+ type: 'boolean',
12236
+ default: false,
12237
+ description: 'Use masked API-token login for scans and write commands'
12238
+ },
12202
12239
  apiBaseUrl: {
12203
12240
  type: 'string',
12204
12241
  default: '',
12205
- description: 'API server to connect to for login'
12242
+ description: 'API server for API-token login'
12206
12243
  },
12207
12244
  apiProxy: {
12208
12245
  type: 'string',
@@ -12217,13 +12254,16 @@ async function run$L(argv, importMeta, {
12217
12254
  API Token Requirements
12218
12255
  ${utils.getFlagApiRequirementsOutput(`${parentName}:${CMD_NAME$r}`)}
12219
12256
 
12220
- Logs into the Socket API by prompting for an API token
12257
+ By default, opens a browser for read-only package, alert, and threat-feed
12258
+ access. Use --api-token for scans and write operations. A sign-in URL is
12259
+ printed so you can open it manually if the browser does not launch.
12221
12260
 
12222
12261
  Options
12223
12262
  ${utils.getFlagListOutput(config.flags)}
12224
12263
 
12225
12264
  Examples
12226
12265
  $ ${command}
12266
+ $ ${command} --api-token
12227
12267
  $ ${command} --api-proxy=http://localhost:1234
12228
12268
  `
12229
12269
  };
@@ -12238,33 +12278,53 @@ async function run$L(argv, importMeta, {
12238
12278
  logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
12239
12279
  return;
12240
12280
  }
12281
+ const apiTokenMode = !!cli.flags['apiToken'];
12241
12282
  if (!vendor.isInteractiveExports()) {
12242
- throw new utils.InputError('Cannot prompt for credentials in a non-interactive shell. Use SOCKET_CLI_API_TOKEN environment variable instead');
12283
+ throw new utils.InputError('Login requires an interactive terminal. Set SOCKET_CLI_API_TOKEN for automation.');
12243
12284
  }
12244
12285
  const {
12245
12286
  apiBaseUrl,
12246
12287
  apiProxy
12247
12288
  } = cli.flags;
12248
- await attemptLogin(apiBaseUrl, apiProxy);
12289
+ await attemptLogin(apiBaseUrl, apiProxy, apiTokenMode);
12249
12290
  }
12250
12291
 
12251
12292
  function applyLogout() {
12252
12293
  utils.updateConfigValue(constants.CONFIG_KEY_API_TOKEN, null);
12294
+ utils.updateConfigValue(constants.CONFIG_KEY_AUTH_TYPE, null);
12253
12295
  utils.updateConfigValue(constants.CONFIG_KEY_API_BASE_URL, null);
12254
12296
  utils.updateConfigValue(constants.CONFIG_KEY_API_PROXY, null);
12255
12297
  utils.updateConfigValue(constants.CONFIG_KEY_ENFORCED_ORGS, null);
12256
12298
  }
12257
12299
 
12258
- function attemptLogout() {
12300
+ async function attemptLogout() {
12301
+ let warning;
12259
12302
  try {
12260
- applyLogout();
12303
+ const directory = constants.default.socketAppDataPath;
12304
+ try {
12305
+ if (directory && !utils.isConfigFromFlag()) {
12306
+ const result = await utils.logoutOAuth(directory, {
12307
+ apiProxy: utils.getDefaultProxyUrl()
12308
+ });
12309
+ warning = result.warning;
12310
+ }
12311
+ } finally {
12312
+ applyLogout();
12313
+ }
12261
12314
  logger.logger.success('Successfully logged out');
12315
+ if (warning) {
12316
+ logger.logger.warn(`Local sign-out completed. ${warning}`);
12317
+ }
12318
+ if (constants.default.ENV.SOCKET_CLI_API_TOKEN) {
12319
+ logger.logger.warn('SOCKET_CLI_API_TOKEN remains active and takes precedence until it is unset.');
12320
+ }
12262
12321
  if (utils.isConfigFromFlag()) {
12263
12322
  logger.logger.log('');
12264
12323
  logger.logger.warn('Note: config is in read-only mode, at least one key was overridden through flag/env, so the logout was not persisted!');
12265
12324
  }
12266
- } catch {
12325
+ } catch (error) {
12267
12326
  logger.logger.fail('Failed to complete logout steps');
12327
+ logger.logger.warn(utils.getErrorCause(error));
12268
12328
  }
12269
12329
  }
12270
12330
 
@@ -12304,7 +12364,7 @@ async function run$K(argv, importMeta, {
12304
12364
  logger.logger.log(constants.default.DRY_RUN_BAILING_NOW);
12305
12365
  return;
12306
12366
  }
12307
- attemptLogout();
12367
+ await attemptLogout();
12308
12368
  }
12309
12369
 
12310
12370
  const {
@@ -12705,6 +12765,90 @@ async function run$J(argv, importMeta, context) {
12705
12765
  await spawnPromise;
12706
12766
  }
12707
12767
 
12768
+ function canonicalizeForContainment(filePath) {
12769
+ let existingPath = path.resolve(filePath);
12770
+ const missingSegments = [];
12771
+ while (!u.existsSync(existingPath)) {
12772
+ const parent = path.dirname(existingPath);
12773
+ if (parent === existingPath) {
12774
+ break;
12775
+ }
12776
+ missingSegments.unshift(path.basename(existingPath));
12777
+ existingPath = parent;
12778
+ }
12779
+ return path.join(u.realpathSync(existingPath), ...missingSegments);
12780
+ }
12781
+ function readSetting(cliValue, socketJsonValue, flag, trustSocketJson, refusedFields, socketJsonDefaults) {
12782
+ if (typeof cliValue === 'string' && cliValue.length > 0) {
12783
+ return cliValue;
12784
+ }
12785
+ if (typeof socketJsonValue !== 'string' || socketJsonValue.length === 0) {
12786
+ return undefined;
12787
+ }
12788
+ if (!trustSocketJson) {
12789
+ refusedFields.push(flag);
12790
+ return undefined;
12791
+ }
12792
+ socketJsonDefaults.push({
12793
+ flag,
12794
+ value: socketJsonValue
12795
+ });
12796
+ return socketJsonValue;
12797
+ }
12798
+ function resolveBazelManifestSettings({
12799
+ cli,
12800
+ cwd,
12801
+ socketJson,
12802
+ trustSocketJson
12803
+ }) {
12804
+ const config = socketJson.defaults?.manifest?.bazel ?? {};
12805
+ const refusedFields = [];
12806
+ const socketJsonDefaults = [];
12807
+ const bazel = readSetting(cli.bazel, config.bazel ?? config.bin, '--bazel', trustSocketJson, refusedFields, socketJsonDefaults);
12808
+ const bazelFlags = readSetting(cli.bazelFlags, config.bazelFlags, '--bazel-flags', trustSocketJson, refusedFields, socketJsonDefaults);
12809
+ const bazelOutputBase = readSetting(cli.bazelOutputBase, config.bazelOutputBase, '--bazel-output-base', trustSocketJson, refusedFields, socketJsonDefaults);
12810
+ const bazelRc = readSetting(cli.bazelRc, config.bazelRc, '--bazel-rc', trustSocketJson, refusedFields, socketJsonDefaults);
12811
+ if (refusedFields.length > 0) {
12812
+ return {
12813
+ ok: false,
12814
+ message: `Refused bazel settings chosen by ${constants.SOCKET_JSON}`,
12815
+ cause: [`${constants.SOCKET_JSON} in ${cwd} sets ${refusedFields.join(', ')}.`, 'Saw repository-supplied bazel execution settings, wanted none.', 'These values choose the Bazel executable and its options, and the scanned repository controls its own socket.json.', 'Fix: pass --bazel, --bazel-flags, --bazel-rc, or --bazel-output-base on the command line, or re-run with --trust-socket-json.'].join('\n')
12816
+ };
12817
+ }
12818
+ const cliOut = typeof cli.out === 'string' ? cli.out : undefined;
12819
+ const socketJsonOut = config.out;
12820
+ const outFromSocketJson = !cliOut && typeof socketJsonOut === 'string';
12821
+ const outValue = cliOut ?? socketJsonOut;
12822
+ const resolvedOut = outValue ? path.resolve(cwd, outValue) : path.join(cwd, '.socket', 'bazel-manifests');
12823
+ const canonicalCwd = canonicalizeForContainment(cwd);
12824
+ const canonicalOut = canonicalizeForContainment(resolvedOut);
12825
+ const relativeOut = path.relative(canonicalCwd, canonicalOut);
12826
+ if (outFromSocketJson && !trustSocketJson && (relativeOut === '..' || relativeOut.startsWith(`..${path.sep}`) || path.isAbsolute(relativeOut))) {
12827
+ return {
12828
+ ok: false,
12829
+ message: `Refused a bazel output path chosen by ${constants.SOCKET_JSON}`,
12830
+ cause: [`${constants.SOCKET_JSON} in ${cwd} sets defaults.manifest.bazel.out.`, `Saw \`${socketJsonOut}\` (resolves to ${resolvedOut}), wanted a path inside ${cwd}.`, 'The CLI writes generated manifests there, so a scanned repository must not choose an output path outside the project.', `Fix: pass --out=${socketJsonOut} if you trust this path, or re-run with --trust-socket-json to honor the setting.`].join('\n')
12831
+ };
12832
+ }
12833
+ if (outFromSocketJson) {
12834
+ socketJsonDefaults.push({
12835
+ flag: '--out',
12836
+ value: resolvedOut
12837
+ });
12838
+ }
12839
+ return {
12840
+ ok: true,
12841
+ data: {
12842
+ bazel,
12843
+ bazelFlags: bazelFlags ?? '',
12844
+ bazelOutputBase,
12845
+ bazelRc,
12846
+ out: resolvedOut,
12847
+ socketJsonDefaults
12848
+ }
12849
+ };
12850
+ }
12851
+
12708
12852
  // Result shape returned by `validatePypiHub`. Kept local to the PyPI module
12709
12853
  // since validation here is hub-alias-marker based (different from the
12710
12854
  // Maven-side tri-state classifier).
@@ -13714,6 +13858,11 @@ const config$h = {
13714
13858
  type: 'number',
13715
13859
  description: 'Per-hub bazel cquery timeout in milliseconds; default: 120000'
13716
13860
  },
13861
+ trustSocketJson: {
13862
+ type: 'boolean',
13863
+ default: false,
13864
+ description: `Run Bazel settings declared in ${constants.SOCKET_JSON}. Off by default because the scanned repository controls that file.`
13865
+ },
13717
13866
  verbose: {
13718
13867
  type: 'boolean',
13719
13868
  description: 'Emit bounded Bazel diagnostics with argv, duration, exit status, and output sizes'
@@ -13735,6 +13884,11 @@ const config$h = {
13735
13884
  --ecosystem may be repeated to select which ecosystems to extract.
13736
13885
  When omitted, Maven is generated by default. PyPI is explicit opt-in.
13737
13886
 
13887
+ A ${constants.SOCKET_JSON} that sets \`bazel\`/\`bin\`, \`bazelFlags\`, \`bazelRc\`, or
13888
+ \`bazelOutputBase\`, or an \`out\` outside the project, is refused unless
13889
+ you pass --trust-socket-json. Pass the matching command-line flags to
13890
+ override these defaults without trusting ${constants.SOCKET_JSON}.
13891
+
13738
13892
  Note: this command generates dependency manifests for Bazel workspaces.
13739
13893
  It does not run reachability analysis.
13740
13894
 
@@ -13898,53 +14052,48 @@ async function run$I(argv, importMeta, {
13898
14052
  verbose
13899
14053
  } = cli.flags;
13900
14054
  let perRepoTimeout = cli.flags['perRepoTimeout'];
13901
-
13902
- // Set defaults for any flag/arg that is not given. Check socket.json first.
13903
- if (bazel) {
13904
- bazel = path.resolve(process.cwd(), String(bazel));
13905
- } else {
13906
- const defaultBazel = sockJson.defaults?.manifest?.bazel?.bazel ?? sockJson.defaults?.manifest?.bazel?.bin;
13907
- if (defaultBazel) {
13908
- bazel = defaultBazel;
13909
- logger.logger.info(`Using default --bazel from ${constants.SOCKET_JSON}:`, bazel);
13910
- }
13911
- // Otherwise leave undefined; resolveBazelBinary performs the PATH
13912
- // lookup for bazelisk/bazel.
13913
- }
13914
- if (!bazelFlags) {
13915
- if (sockJson.defaults?.manifest?.bazel?.bazelFlags) {
13916
- bazelFlags = sockJson.defaults?.manifest?.bazel?.bazelFlags;
13917
- logger.logger.info(`Using default --bazel-flags from ${constants.SOCKET_JSON}:`, bazelFlags);
14055
+ const settings = resolveBazelManifestSettings({
14056
+ cli: {
14057
+ bazel,
14058
+ bazelFlags,
14059
+ bazelOutputBase,
14060
+ bazelRc,
14061
+ out
14062
+ },
14063
+ cwd,
14064
+ socketJson: sockJson,
14065
+ trustSocketJson: Boolean(cli.flags['trustSocketJson'])
14066
+ });
14067
+ if (!settings.ok) {
14068
+ process.exitCode = 1;
14069
+ if (outputKind === 'json') {
14070
+ logger.logger.log(utils.serializeResultJson(settings));
13918
14071
  } else {
13919
- bazelFlags = '';
13920
- }
13921
- }
13922
- if (!bazelOutputBase) {
13923
- if (sockJson.defaults?.manifest?.bazel?.bazelOutputBase) {
13924
- bazelOutputBase = sockJson.defaults?.manifest?.bazel?.bazelOutputBase;
13925
- logger.logger.info(`Using default --bazel-output-base from ${constants.SOCKET_JSON}:`, bazelOutputBase);
14072
+ logger.logger.fail(utils.failMsgWithBadge(settings.message, settings.cause));
13926
14073
  }
14074
+ return;
13927
14075
  }
13928
- if (!bazelRc) {
13929
- if (sockJson.defaults?.manifest?.bazel?.bazelRc) {
13930
- bazelRc = sockJson.defaults?.manifest?.bazel?.bazelRc;
13931
- logger.logger.info(`Using default --bazel-rc from ${constants.SOCKET_JSON}:`, bazelRc);
13932
- }
14076
+ for (const {
14077
+ flag,
14078
+ value
14079
+ } of settings.data.socketJsonDefaults) {
14080
+ logger.logger.info(`Using default ${flag} from ${constants.SOCKET_JSON}:`, value);
13933
14081
  }
13934
- if (!out) {
13935
- if (sockJson.defaults?.manifest?.bazel?.out) {
13936
- out = sockJson.defaults?.manifest?.bazel?.out;
13937
- logger.logger.info(`Using default --out from ${constants.SOCKET_JSON}:`, out);
13938
- } else {
13939
- out = path.join(cwd, '.socket', 'bazel-manifests');
13940
- }
14082
+ bazel = settings.data.bazel;
14083
+ bazelFlags = settings.data.bazelFlags;
14084
+ bazelOutputBase = settings.data.bazelOutputBase;
14085
+ bazelRc = settings.data.bazelRc;
14086
+ out = settings.data.out;
14087
+
14088
+ // Set defaults for any flag/arg that is not given. Check socket.json first.
14089
+ if (bazel && cli.flags['bazel']) {
14090
+ bazel = path.resolve(process.cwd(), String(bazel));
13941
14091
  }
14092
+ bazelFlags ??= '';
13942
14093
  if (verbose === undefined) {
14094
+ verbose = sockJson.defaults?.manifest?.bazel?.verbose ?? false;
13943
14095
  if (sockJson.defaults?.manifest?.bazel?.verbose !== undefined) {
13944
- verbose = sockJson.defaults?.manifest?.bazel?.verbose;
13945
14096
  logger.logger.info(`Using default --verbose from ${constants.SOCKET_JSON}:`, verbose);
13946
- } else {
13947
- verbose = false;
13948
14097
  }
13949
14098
  }
13950
14099
  if (perRepoTimeout === undefined) {
@@ -25599,7 +25748,7 @@ function addSocketWrapper(file) {
25599
25748
  return new Error(`There was an error setting up the alias: ${err}`);
25600
25749
  }
25601
25750
  logger.logger.success(`The alias was added to ${file}. Running 'npm install' will now be wrapped in Socket's "safe npm" 🎉`);
25602
- logger.logger.log(` If you want to disable it at any time, run \`socket wrapper --disable\``);
25751
+ logger.logger.log(` If you want to disable it at any time, run \`socket wrapper off\``);
25603
25752
  logger.logger.log('');
25604
25753
  logger.logger.info(`This will only be active in new terminal sessions going forward.`);
25605
25754
  logger.logger.log(` You will need to restart your terminal or run this command to activate the alias in the current session:`);
@@ -26143,5 +26292,5 @@ process.on('unhandledRejection', async (reason, promise) => {
26143
26292
  // eslint-disable-next-line n/no-process-exit
26144
26293
  process.exit(1);
26145
26294
  });
26146
- //# debugId=e8c87646-0d74-4bcc-9e38-e86a951f03ae
26295
+ //# debugId=f0475874-f135-4a6b-aa96-64e325d96f5f
26147
26296
  //# sourceMappingURL=cli.js.map