@socketsecurity/cli-with-sentry 1.14.0 → 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 +18 -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/utils.js CHANGED
@@ -26,7 +26,7 @@ var require$$11 = require('node:zlib');
26
26
  var fs = require('../external/@socketsecurity/registry/lib/fs');
27
27
  var url = require('../external/@socketsecurity/registry/lib/url');
28
28
  var require$$1 = require('node:module');
29
- var agent = require('../external/@socketsecurity/registry/lib/agent');
29
+ var agent$1 = require('../external/@socketsecurity/registry/lib/agent');
30
30
  var bin = require('../external/@socketsecurity/registry/lib/bin');
31
31
  var packages = require('../external/@socketsecurity/registry/lib/packages');
32
32
  var os = require('node:os');
@@ -674,7 +674,7 @@ function normalizeConfigKey(key) {
674
674
  // property apiKey, we'll copy that to apiToken and delete the old property.
675
675
  // We added `org` as a convenience alias for `defaultOrg`
676
676
  const normalizedKey = key === 'apiKey' ? constants.CONFIG_KEY_API_TOKEN : key === constants.CONFIG_KEY_ORG ? constants.CONFIG_KEY_DEFAULT_ORG : key;
677
- if (!isSupportedConfigKey(normalizedKey)) {
677
+ if (normalizedKey !== constants.CONFIG_KEY_AUTH_TYPE && !isSupportedConfigKey(normalizedKey)) {
678
678
  return {
679
679
  ok: false,
680
680
  message: `Invalid config key: ${normalizedKey}`,
@@ -969,6 +969,610 @@ function getRequirementsKey(cmdPath) {
969
969
  return cmdPath.replace(/^socket[: ]/, '').replace(/ +/g, ':');
970
970
  }
971
971
 
972
+ // Cached extra CA certificates for SSL_CERT_FILE support.
973
+ let extraCaCerts;
974
+ let extraCaCertsResolved = false;
975
+
976
+ // Node reads NODE_EXTRA_CA_CERTS only at process start. If only SSL_CERT_FILE
977
+ // is set, pass the extra certificates to each outbound TLS agent explicitly.
978
+ function getExtraCaCerts() {
979
+ if (extraCaCertsResolved) {
980
+ return extraCaCerts;
981
+ }
982
+ extraCaCertsResolved = true;
983
+ if (process.env['NODE_EXTRA_CA_CERTS']) {
984
+ return undefined;
985
+ }
986
+ const certPath = constants.default.ENV.NODE_EXTRA_CA_CERTS;
987
+ if (!certPath) {
988
+ return undefined;
989
+ }
990
+ try {
991
+ const extraCerts = u.readFileSync(certPath, 'utf-8');
992
+ extraCaCerts = [...node_tls.rootCertificates, extraCerts];
993
+ return extraCaCerts;
994
+ } catch (error) {
995
+ debug$2.debugFn('warn', `Failed to read certificate file: ${certPath}`, error);
996
+ return undefined;
997
+ }
998
+ }
999
+
1000
+ const OAUTH_ISSUER = 'https://api.socket.dev';
1001
+ const OAUTH_RESOURCE = 'https://mcp.socket.dev/';
1002
+ const RESOURCE_METADATA = `${OAUTH_RESOURCE}.well-known/oauth-protected-resource`;
1003
+ const ISSUER_METADATA = `${OAUTH_ISSUER}/.well-known/oauth-authorization-server`;
1004
+ const REQUIRED_SCOPES = ['packages:list', 'alerts:list', 'threat-feed:list'];
1005
+ const FLOW_TIMEOUT = 5 * 60_000;
1006
+ const REQUEST_TIMEOUT = 20_000;
1007
+ const MAX_RESPONSE = 256 * 1024;
1008
+ const LOCK_TIMEOUT = 60_000;
1009
+ const FILE = 'oauth.json';
1010
+ const error = message => new Error(`Socket browser sign-in: ${message}`);
1011
+ const random = (n = 32) => require$$0.randomBytes(n).toString('base64url');
1012
+ const form = data => new URLSearchParams(data).toString();
1013
+ function trustedEndpoint(value) {
1014
+ if (typeof value !== 'string') {
1015
+ throw error('OAuth metadata is invalid.');
1016
+ }
1017
+ let u;
1018
+ try {
1019
+ u = new URL(value);
1020
+ } catch {
1021
+ throw error('OAuth metadata is invalid.');
1022
+ }
1023
+ if (u.protocol !== 'https:' || u.origin !== OAUTH_ISSUER || u.username || u.password || u.search || u.hash) {
1024
+ throw error('OAuth metadata contains an untrusted endpoint.');
1025
+ }
1026
+ return u.href;
1027
+ }
1028
+ function agent(proxy, timeoutMs = REQUEST_TIMEOUT) {
1029
+ const ca = getExtraCaCerts();
1030
+ return proxy ? new vendor.HttpsProxyAgent({
1031
+ proxy,
1032
+ timeout: timeoutMs,
1033
+ ...(ca ? {
1034
+ ca,
1035
+ proxyRequestOptions: {
1036
+ ca
1037
+ }
1038
+ } : {})
1039
+ }) : ca ? new require$$13.Agent({
1040
+ ca
1041
+ }) : undefined;
1042
+ }
1043
+ function oauthCancellationError(signal) {
1044
+ return signal?.reason instanceof Error ? signal.reason : error('Sign-in was cancelled.');
1045
+ }
1046
+ function http(url, opts = {}) {
1047
+ const target = new URL(url);
1048
+ if (target.protocol !== 'https:' || target.origin !== OAUTH_ISSUER && target.origin !== new URL(OAUTH_RESOURCE).origin) {
1049
+ throw error('Refusing to contact an untrusted OAuth endpoint.');
1050
+ }
1051
+ return new Promise((resolve, reject) => {
1052
+ if (opts.signal?.aborted) {
1053
+ reject(opts.signal.reason ?? error('Sign-in was cancelled.'));
1054
+ return;
1055
+ }
1056
+ let done = false;
1057
+ const finish = fn => {
1058
+ if (!done) {
1059
+ done = true;
1060
+ clearTimeout(deadline);
1061
+ opts.signal?.removeEventListener('abort', abort);
1062
+ requestAgent?.destroy();
1063
+ fn();
1064
+ }
1065
+ };
1066
+ const timeoutMs = Math.min(opts.timeoutMs ?? REQUEST_TIMEOUT, REQUEST_TIMEOUT);
1067
+ const requestAgent = agent(opts.proxy, timeoutMs);
1068
+ const req = require$$13.request(target, {
1069
+ method: opts.method ?? 'GET',
1070
+ timeout: timeoutMs,
1071
+ ...(requestAgent ? {
1072
+ agent: requestAgent
1073
+ } : {}),
1074
+ headers: {
1075
+ accept: 'application/json',
1076
+ ...opts.headers,
1077
+ ...(opts.body ? {
1078
+ 'content-length': String(Buffer.byteLength(opts.body))
1079
+ } : {})
1080
+ }
1081
+ }, res => {
1082
+ const chunks = [];
1083
+ let size = 0;
1084
+ res.on('data', chunk => {
1085
+ size += chunk.length;
1086
+ if (size > MAX_RESPONSE) {
1087
+ req.destroy(error('OAuth response was too large.'));
1088
+ return;
1089
+ }
1090
+ chunks.push(Buffer.from(chunk));
1091
+ });
1092
+ res.on('error', () => finish(() => reject(error('OAuth response could not be read.'))));
1093
+ res.on('aborted', () => finish(() => reject(error('OAuth response ended unexpectedly.'))));
1094
+ res.on('end', () => {
1095
+ const text = Buffer.concat(chunks).toString('utf8');
1096
+ if (!text && opts.allowEmpty) {
1097
+ finish(() => resolve({
1098
+ status: res.statusCode ?? 0,
1099
+ body: {}
1100
+ }));
1101
+ return;
1102
+ }
1103
+ try {
1104
+ const body = JSON.parse(text);
1105
+ finish(() => resolve({
1106
+ status: res.statusCode ?? 0,
1107
+ body
1108
+ }));
1109
+ } catch {
1110
+ finish(() => reject(error('OAuth server returned an invalid response.')));
1111
+ }
1112
+ });
1113
+ });
1114
+ const abort = () => {
1115
+ finish(() => reject(error('Sign-in was cancelled.')));
1116
+ req.destroy();
1117
+ };
1118
+ const deadline = setTimeout(() => {
1119
+ finish(() => reject(error('OAuth request timed out.')));
1120
+ req.destroy();
1121
+ }, timeoutMs);
1122
+ opts.signal?.addEventListener('abort', abort, {
1123
+ once: true
1124
+ });
1125
+ req.on('error', () => finish(() => reject(error('OAuth request failed.'))));
1126
+ if (opts.body) {
1127
+ req.write(opts.body);
1128
+ }
1129
+ req.end();
1130
+ });
1131
+ }
1132
+ async function discover(opts) {
1133
+ const r = await http(RESOURCE_METADATA, {
1134
+ signal: opts.signal,
1135
+ proxy: opts.apiProxy
1136
+ });
1137
+ const rb = r.body;
1138
+ if (r.status !== 200 || !objects.isObjectObject(rb) || rb['resource'] !== OAUTH_RESOURCE || !Array.isArray(rb['authorization_servers']) || !rb['authorization_servers'].some(server => server === OAUTH_ISSUER) || !Array.isArray(rb['scopes_supported']) || rb['scopes_supported'].length !== REQUIRED_SCOPES.length || REQUIRED_SCOPES.some(s => !rb['scopes_supported'].includes(s))) {
1139
+ throw error('Socket OAuth resource metadata could not be verified.');
1140
+ }
1141
+ const ir = await http(ISSUER_METADATA, {
1142
+ signal: opts.signal,
1143
+ proxy: opts.apiProxy
1144
+ });
1145
+ const m = ir.body;
1146
+ if (ir.status !== 200 || !objects.isObjectObject(m) || m['issuer'] !== OAUTH_ISSUER || !Array.isArray(m['grant_types_supported']) || !m['grant_types_supported'].includes('authorization_code') || !m['grant_types_supported'].includes('refresh_token') || !Array.isArray(m['code_challenge_methods_supported']) || !m['code_challenge_methods_supported'].includes('S256') || !Array.isArray(m['token_endpoint_auth_methods_supported']) || !m['token_endpoint_auth_methods_supported'].includes('none')) {
1147
+ throw error('Socket OAuth issuer metadata is invalid.');
1148
+ }
1149
+ return {
1150
+ authorize: trustedEndpoint(m['authorization_endpoint']),
1151
+ register: trustedEndpoint(m['registration_endpoint']),
1152
+ token: trustedEndpoint(m['token_endpoint']),
1153
+ ...(m['revocation_endpoint'] ? {
1154
+ revoke: trustedEndpoint(m['revocation_endpoint'])
1155
+ } : {})
1156
+ };
1157
+ }
1158
+ function parseToken(r, fallback, meta) {
1159
+ const b = r.body;
1160
+ if (r.status < 200 || r.status >= 300 || !objects.isObjectObject(b) || typeof b['access_token'] !== 'string' || !b['access_token'].trim() || typeof b['token_type'] !== 'string' || b['token_type'].toLowerCase() !== 'bearer' || typeof b['expires_in'] !== 'number' || !Number.isFinite(b['expires_in']) || b['expires_in'] <= 0 || !Number.isSafeInteger(Date.now() + b['expires_in'] * 1000) || b['refresh_token'] !== undefined && (typeof b['refresh_token'] !== 'string' || !b['refresh_token'])) {
1161
+ const code = objects.isObjectObject(b) && b['error'] === 'invalid_grant';
1162
+ throw error(code ? 'The saved session expired or was revoked. Run `socket login` again.' : 'Token exchange failed. Run `socket login` again if the problem continues.');
1163
+ }
1164
+ if (b['scope'] !== undefined && typeof b['scope'] !== 'string') {
1165
+ throw error('The authorization server returned invalid permissions.');
1166
+ }
1167
+ const scopes = typeof b['scope'] === 'string' ? b['scope'].split(/\s+/).filter(Boolean) : fallback;
1168
+ if (scopes.some(s => !REQUIRED_SCOPES.includes(s))) {
1169
+ throw error('The authorization server returned unsupported permissions.');
1170
+ }
1171
+ if (REQUIRED_SCOPES.some(scope => !scopes.includes(scope))) {
1172
+ throw error('The authorization server did not grant the required read-only permissions.');
1173
+ }
1174
+ return {
1175
+ version: 1,
1176
+ issuer: OAUTH_ISSUER,
1177
+ resource: OAUTH_RESOURCE,
1178
+ apiOrigin: OAUTH_ISSUER,
1179
+ clientId: meta.clientId,
1180
+ accessToken: b['access_token'],
1181
+ ...(typeof b['refresh_token'] === 'string' ? {
1182
+ refreshToken: b['refresh_token']
1183
+ } : {}),
1184
+ expiresAt: Date.now() + b['expires_in'] * 1000,
1185
+ scopes,
1186
+ tokenEndpoint: meta.tokenEndpoint,
1187
+ ...(meta.revocationEndpoint ? {
1188
+ revocationEndpoint: meta.revocationEndpoint
1189
+ } : {})
1190
+ };
1191
+ }
1192
+ function same(a, b) {
1193
+ const x = Buffer.from(a);
1194
+ const y = Buffer.from(b);
1195
+ return x.length === y.length && require$$0.timingSafeEqual(x, y);
1196
+ }
1197
+ async function beginOAuthLogin(options) {
1198
+ const controller = new AbortController();
1199
+ const abort = () => controller.abort(options.signal?.reason);
1200
+ if (options.signal?.aborted) {
1201
+ abort();
1202
+ }
1203
+ options.signal?.addEventListener('abort', abort, {
1204
+ once: true
1205
+ });
1206
+ const timeout = setTimeout(() => controller.abort(error('Sign-in timed out. Run `socket login` to try again.')), FLOW_TIMEOUT);
1207
+ const signal = controller.signal;
1208
+ let server;
1209
+ try {
1210
+ const meta = await discover({
1211
+ signal,
1212
+ apiProxy: options.apiProxy
1213
+ });
1214
+ const state = random();
1215
+ const verifier = random(48);
1216
+ const challenge = require$$0.createHash('sha256').update(verifier).digest('base64url');
1217
+ let resolveCode;
1218
+ let rejectCode;
1219
+ let settled = false;
1220
+ const codePromise = new Promise((resolve, reject) => {
1221
+ resolveCode = resolve;
1222
+ rejectCode = reject;
1223
+ });
1224
+ // Attach a rejection observer immediately: an OAuth error callback can arrive while DCR is pending.
1225
+ void codePromise.catch(() => {});
1226
+ server = require$$12.createServer((req, res) => {
1227
+ let u;
1228
+ try {
1229
+ u = new URL(req.url ?? '/', 'http://127.0.0.1');
1230
+ } catch {
1231
+ res.writeHead(400).end('Invalid callback request.');
1232
+ return;
1233
+ }
1234
+ if (req.method !== 'GET' || u.pathname !== '/oauth/callback') {
1235
+ res.writeHead(404).end();
1236
+ return;
1237
+ }
1238
+ if (settled) {
1239
+ res.writeHead(410).end();
1240
+ return;
1241
+ }
1242
+ if (!same(u.searchParams.get('state') ?? '', state)) {
1243
+ res.writeHead(400).end('Sign-in state did not match.');
1244
+ return;
1245
+ }
1246
+ if (u.searchParams.has('error') || !u.searchParams.get('code')) {
1247
+ settled = true;
1248
+ res.writeHead(400).end('Sign-in was not completed.');
1249
+ rejectCode(error('Authorization was declined.'));
1250
+ return;
1251
+ }
1252
+ settled = true;
1253
+ res.writeHead(200, {
1254
+ 'content-type': 'text/html; charset=utf-8',
1255
+ 'cache-control': 'no-store'
1256
+ }).end('<!doctype html><title>Socket CLI</title><p>Sign-in complete. Return to your terminal.</p>');
1257
+ resolveCode(u.searchParams.get('code'));
1258
+ });
1259
+ signal.addEventListener('abort', () => {
1260
+ if (!settled) {
1261
+ settled = true;
1262
+ rejectCode(oauthCancellationError(signal));
1263
+ }
1264
+ }, {
1265
+ once: true
1266
+ });
1267
+ if (signal.aborted && !settled) {
1268
+ settled = true;
1269
+ rejectCode(oauthCancellationError(signal));
1270
+ }
1271
+ const redirectUri = await new Promise((resolve, reject) => {
1272
+ server.once('error', () => reject(error('Could not start the local callback listener.')));
1273
+ server.listen(0, '127.0.0.1', () => {
1274
+ const a = server.address();
1275
+ if (!a || typeof a === 'string') {
1276
+ reject(error('Could not start the local callback listener.'));
1277
+ } else {
1278
+ resolve(`http://127.0.0.1:${a.port}/oauth/callback`);
1279
+ }
1280
+ });
1281
+ });
1282
+ const registration = await http(meta.register, {
1283
+ method: 'POST',
1284
+ signal,
1285
+ proxy: options.apiProxy,
1286
+ headers: {
1287
+ 'content-type': 'application/json'
1288
+ },
1289
+ body: JSON.stringify({
1290
+ client_name: 'Socket CLI',
1291
+ redirect_uris: [redirectUri],
1292
+ grant_types: ['authorization_code', 'refresh_token'],
1293
+ response_types: ['code'],
1294
+ token_endpoint_auth_method: 'none'
1295
+ })
1296
+ });
1297
+ const reg = registration.body;
1298
+ if (registration.status < 200 || registration.status >= 300 || !objects.isObjectObject(reg) || typeof reg['client_id'] !== 'string' || !reg['client_id'].trim() || reg['token_endpoint_auth_method'] !== undefined && reg['token_endpoint_auth_method'] !== 'none' || reg['redirect_uris'] !== undefined && (!Array.isArray(reg['redirect_uris']) || !reg['redirect_uris'].some(value => value === redirectUri)) || reg['grant_types'] !== undefined && (!Array.isArray(reg['grant_types']) || !['authorization_code', 'refresh_token'].every(grant => reg['grant_types'].some(value => value === grant))) || reg['response_types'] !== undefined && (!Array.isArray(reg['response_types']) || !reg['response_types'].some(value => value === 'code'))) {
1299
+ throw error('The OAuth client could not be registered.');
1300
+ }
1301
+ const clientId = reg['client_id'];
1302
+ const url = new URL(meta.authorize);
1303
+ url.search = form({
1304
+ response_type: 'code',
1305
+ client_id: clientId,
1306
+ redirect_uri: redirectUri,
1307
+ scope: REQUIRED_SCOPES.join(' '),
1308
+ state,
1309
+ code_challenge: challenge,
1310
+ code_challenge_method: 'S256',
1311
+ resource: OAUTH_RESOURCE
1312
+ });
1313
+ options.onAuthorizationUrl(url.href);
1314
+ if (options.openBrowser !== false) {
1315
+ try {
1316
+ await vendor.open(url.href);
1317
+ } catch {
1318
+ /* URL was printed; manual browser completion remains available. */
1319
+ }
1320
+ }
1321
+ const code = await codePromise;
1322
+ const token = await http(meta.token, {
1323
+ method: 'POST',
1324
+ signal,
1325
+ proxy: options.apiProxy,
1326
+ headers: {
1327
+ 'content-type': 'application/x-www-form-urlencoded'
1328
+ },
1329
+ body: form({
1330
+ grant_type: 'authorization_code',
1331
+ code,
1332
+ client_id: clientId,
1333
+ redirect_uri: redirectUri,
1334
+ code_verifier: verifier,
1335
+ resource: OAUTH_RESOURCE
1336
+ })
1337
+ });
1338
+ return parseToken(token, REQUIRED_SCOPES, {
1339
+ clientId,
1340
+ tokenEndpoint: meta.token,
1341
+ ...(meta.revoke ? {
1342
+ revocationEndpoint: meta.revoke
1343
+ } : {})
1344
+ });
1345
+ } finally {
1346
+ clearTimeout(timeout);
1347
+ options.signal?.removeEventListener('abort', abort);
1348
+ server?.close();
1349
+ server?.closeAllConnections();
1350
+ }
1351
+ }
1352
+ const filePath = dir => path.join(dir, FILE);
1353
+ function validCredentials(value) {
1354
+ if (!objects.isObjectObject(value) || value['version'] !== 1 || value['issuer'] !== OAUTH_ISSUER || value['resource'] !== OAUTH_RESOURCE || value['apiOrigin'] !== OAUTH_ISSUER || typeof value['clientId'] !== 'string' || !value['clientId'] || typeof value['accessToken'] !== 'string' || !value['accessToken'] || typeof value['expiresAt'] !== 'number' || !Number.isFinite(value['expiresAt']) || value['expiresAt'] <= 0 || !Array.isArray(value['scopes']) || value['scopes'].some(s => typeof s !== 'string' || !REQUIRED_SCOPES.includes(s)) || value['refreshToken'] !== undefined && (typeof value['refreshToken'] !== 'string' || !value['refreshToken'])) {
1355
+ return false;
1356
+ }
1357
+ try {
1358
+ trustedEndpoint(value['tokenEndpoint']);
1359
+ if (value['revocationEndpoint'] !== undefined) {
1360
+ trustedEndpoint(value['revocationEndpoint']);
1361
+ }
1362
+ return true;
1363
+ } catch {
1364
+ return false;
1365
+ }
1366
+ }
1367
+ function readOAuthCredentials(directory) {
1368
+ try {
1369
+ const x = JSON.parse(u.readFileSync(filePath(directory), 'utf8'));
1370
+ if (!validCredentials(x)) {
1371
+ throw error('Saved OAuth credentials are invalid. Run `socket login` again.');
1372
+ }
1373
+ return x;
1374
+ } catch (e) {
1375
+ if (e.code === 'ENOENT') {
1376
+ return undefined;
1377
+ }
1378
+ if (e instanceof SyntaxError) {
1379
+ throw error('Saved OAuth credentials are corrupt. Run `socket login` again.');
1380
+ }
1381
+ throw e;
1382
+ }
1383
+ }
1384
+ async function locked(directory, action, signal) {
1385
+ if (signal?.aborted) {
1386
+ throw oauthCancellationError(signal);
1387
+ }
1388
+ await u.promises.mkdir(directory, {
1389
+ recursive: true,
1390
+ mode: 0o700
1391
+ });
1392
+ const lock = `${filePath(directory)}.lock`;
1393
+ const until = Date.now() + LOCK_TIMEOUT;
1394
+ while (true) {
1395
+ try {
1396
+ // This await performs the inter-process mutex acquisition.
1397
+ // eslint-disable-next-line no-await-in-loop
1398
+ await u.promises.mkdir(lock, {
1399
+ mode: 0o700
1400
+ });
1401
+ break;
1402
+ } catch (e) {
1403
+ if (e.code !== 'EEXIST') {
1404
+ throw e;
1405
+ }
1406
+ if (Date.now() >= until) {
1407
+ throw error(`Could not acquire sign-in lock ${lock}. After confirming no other Socket CLI process is running, remove this lock directory and retry.`);
1408
+ }
1409
+ if (signal?.aborted) {
1410
+ throw oauthCancellationError(signal);
1411
+ }
1412
+ // eslint-disable-next-line no-await-in-loop
1413
+ await new Promise((resolve, reject) => {
1414
+ const timer = setTimeout(() => {
1415
+ signal?.removeEventListener('abort', onAbort);
1416
+ resolve();
1417
+ }, 100);
1418
+ const onAbort = () => {
1419
+ clearTimeout(timer);
1420
+ reject(oauthCancellationError(signal));
1421
+ };
1422
+ signal?.addEventListener('abort', onAbort, {
1423
+ once: true
1424
+ });
1425
+ if (signal?.aborted) {
1426
+ onAbort();
1427
+ }
1428
+ });
1429
+ }
1430
+ }
1431
+ try {
1432
+ return await action();
1433
+ } finally {
1434
+ await u.promises.rm(lock, {
1435
+ recursive: true,
1436
+ force: true
1437
+ });
1438
+ }
1439
+ }
1440
+ async function writeAtomic(directory, credentials) {
1441
+ await u.promises.chmod(directory, 0o700).catch(() => {});
1442
+ const target = filePath(directory);
1443
+ const tmp = `${target}.${process.pid}.${random(6)}.tmp`;
1444
+ try {
1445
+ await u.promises.writeFile(tmp, `${JSON.stringify(credentials, null, 2)}\n`, {
1446
+ mode: 0o600,
1447
+ flag: 'wx'
1448
+ });
1449
+ await u.promises.chmod(tmp, 0o600);
1450
+ await u.promises.rename(tmp, target);
1451
+ } finally {
1452
+ await u.promises.rm(tmp, {
1453
+ force: true
1454
+ }).catch(() => {});
1455
+ }
1456
+ }
1457
+ async function saveOAuthCredentials(directory, credentials) {
1458
+ if (!validCredentials(credentials)) {
1459
+ throw error('Refusing to save invalid OAuth credentials.');
1460
+ }
1461
+ await locked(directory, () => writeAtomic(directory, credentials));
1462
+ }
1463
+ async function resolveOAuthAccessToken(directory, options = {}) {
1464
+ if (options.signal?.aborted) {
1465
+ throw oauthCancellationError(options.signal);
1466
+ }
1467
+ const current = readOAuthCredentials(directory);
1468
+ if (!current) {
1469
+ return undefined;
1470
+ }
1471
+ if (current.expiresAt > Date.now() + 30_000) {
1472
+ return current.accessToken;
1473
+ }
1474
+ return await locked(directory, async () => {
1475
+ if (options.signal?.aborted) {
1476
+ throw oauthCancellationError(options.signal);
1477
+ }
1478
+ const latest = readOAuthCredentials(directory);
1479
+ if (!latest) {
1480
+ throw error('The saved session was removed. Run `socket login` again.');
1481
+ }
1482
+ if (latest.expiresAt > Date.now() + 30_000) {
1483
+ return latest.accessToken;
1484
+ }
1485
+ if (!latest.refreshToken) {
1486
+ throw error('The saved session expired. Run `socket login` again.');
1487
+ }
1488
+ if (options.signal?.aborted) {
1489
+ throw oauthCancellationError(options.signal);
1490
+ }
1491
+ // Durably discard the rotating token before sending it. A timeout/crash can
1492
+ // never cause another process to replay a token the server may have used.
1493
+ const refreshToken = latest.refreshToken;
1494
+ await writeAtomic(directory, {
1495
+ ...latest,
1496
+ refreshToken: undefined
1497
+ });
1498
+ const result = await http(latest.tokenEndpoint, {
1499
+ method: 'POST',
1500
+ signal: options.signal,
1501
+ proxy: options.apiProxy,
1502
+ timeoutMs: options.requestTimeoutMs,
1503
+ headers: {
1504
+ 'content-type': 'application/x-www-form-urlencoded'
1505
+ },
1506
+ body: form({
1507
+ grant_type: 'refresh_token',
1508
+ client_id: latest.clientId,
1509
+ refresh_token: refreshToken,
1510
+ resource: OAUTH_RESOURCE
1511
+ })
1512
+ });
1513
+ const fresh = parseToken(result, latest.scopes, {
1514
+ clientId: latest.clientId,
1515
+ tokenEndpoint: latest.tokenEndpoint,
1516
+ ...(latest.revocationEndpoint ? {
1517
+ revocationEndpoint: latest.revocationEndpoint
1518
+ } : {})
1519
+ });
1520
+ await writeAtomic(directory, fresh);
1521
+ return fresh.accessToken;
1522
+ }, options.signal);
1523
+ }
1524
+ async function logoutOAuth(directory, options = {}) {
1525
+ return await locked(directory, async () => {
1526
+ let creds;
1527
+ let warning;
1528
+ try {
1529
+ creds = readOAuthCredentials(directory);
1530
+ } catch {
1531
+ warning = 'Saved credentials were invalid; local sign-in data was removed.';
1532
+ }
1533
+ let revoked = false;
1534
+ if (creds) {
1535
+ if (!creds.revocationEndpoint) {
1536
+ warning = 'Remote session revocation was not confirmed.';
1537
+ } else {
1538
+ try {
1539
+ const token = creds.refreshToken ?? creds.accessToken;
1540
+ const hint = creds.refreshToken ? 'refresh_token' : 'access_token';
1541
+ const r = await http(creds.revocationEndpoint, {
1542
+ method: 'POST',
1543
+ signal: options.signal,
1544
+ proxy: options.apiProxy,
1545
+ allowEmpty: true,
1546
+ headers: {
1547
+ 'content-type': 'application/x-www-form-urlencoded'
1548
+ },
1549
+ body: form({
1550
+ client_id: creds.clientId,
1551
+ token,
1552
+ token_type_hint: hint
1553
+ })
1554
+ });
1555
+ revoked = r.status >= 200 && r.status < 300;
1556
+ if (!revoked) {
1557
+ warning = 'Remote session revocation was not confirmed.';
1558
+ }
1559
+ } catch {
1560
+ warning = 'Remote session revocation was not confirmed.';
1561
+ }
1562
+ }
1563
+ }
1564
+ await u.promises.rm(filePath(directory), {
1565
+ force: true
1566
+ });
1567
+ return {
1568
+ revoked,
1569
+ ...(warning ? {
1570
+ warning
1571
+ } : {})
1572
+ };
1573
+ });
1574
+ }
1575
+
972
1576
  /**
973
1577
  * Telemetry service for Socket CLI.
974
1578
  * Manages event collection, batching, and submission to Socket API.
@@ -1879,76 +2483,117 @@ function getDefaultProxyUrl() {
1879
2483
  const apiProxy = constants.default.ENV.SOCKET_CLI_API_PROXY || getConfigValueOrUndef(constants.CONFIG_KEY_API_PROXY);
1880
2484
  return url.isUrl(apiProxy) ? apiProxy : undefined;
1881
2485
  }
1882
-
1883
- // Cached extra CA certificates for SSL_CERT_FILE support.
1884
- let _extraCaCerts;
1885
- let _extraCaCertsResolved = false;
1886
-
1887
- // Returns combined root and extra CA certificates when SSL_CERT_FILE is set
1888
- // but NODE_EXTRA_CA_CERTS is not. Node.js loads NODE_EXTRA_CA_CERTS at process
1889
- // startup, so setting SSL_CERT_FILE alone does not affect the current process.
1890
- // This function reads the certificate file manually and combines it with the
1891
- // default root certificates for use in HTTPS agents.
1892
- function getExtraCaCerts() {
1893
- if (_extraCaCertsResolved) {
1894
- return _extraCaCerts;
1895
- }
1896
- _extraCaCertsResolved = true;
1897
- // Node.js already loaded extra CA certs at startup.
1898
- if (process.env['NODE_EXTRA_CA_CERTS']) {
2486
+ function getDefaultApiToken() {
2487
+ if (constants.default.ENV.SOCKET_CLI_NO_API_TOKEN) {
1899
2488
  return undefined;
1900
2489
  }
1901
- // Check for SSL_CERT_FILE fallback via constants.
1902
- const certPath = constants.default.ENV.NODE_EXTRA_CA_CERTS;
1903
- if (!certPath) {
1904
- return undefined;
2490
+ const environmentToken = constants.default.ENV.SOCKET_CLI_API_TOKEN;
2491
+ if (strings.isNonEmptyString(environmentToken)) {
2492
+ return environmentToken;
1905
2493
  }
1906
- try {
1907
- const extraCerts = u.readFileSync(certPath, 'utf-8');
1908
- // Combine default root certificates with extra certificates. Specifying ca
1909
- // in an agent replaces the default trust store, so both must be included.
1910
- _extraCaCerts = [...node_tls.rootCertificates, extraCerts];
1911
- return _extraCaCerts;
1912
- } catch (e) {
1913
- debug$2.debugFn('warn', `Failed to read certificate file: ${certPath}`, e);
2494
+ if (getConfigValueOrUndef(constants.CONFIG_KEY_AUTH_TYPE) === 'oauth') {
1914
2495
  return undefined;
1915
2496
  }
2497
+ const key = getConfigValueOrUndef(constants.CONFIG_KEY_API_TOKEN);
2498
+ return strings.isNonEmptyString(key) ? key : undefined;
1916
2499
  }
1917
2500
 
1918
- // This Socket API token should be stored globally for the duration of the CLI execution.
1919
- let _defaultToken;
1920
- function getDefaultApiToken() {
1921
- if (constants.default.ENV.SOCKET_CLI_NO_API_TOKEN) {
1922
- _defaultToken = undefined;
1923
- return _defaultToken;
1924
- }
1925
- const key = constants.default.ENV.SOCKET_CLI_API_TOKEN || getConfigValueOrUndef(constants.CONFIG_KEY_API_TOKEN) || _defaultToken;
1926
- _defaultToken = strings.isNonEmptyString(key) ? key : undefined;
1927
- return _defaultToken;
2501
+ /** Whether saved OAuth auth is active instead of a flag or explicit token. */
2502
+ function isOAuthLoginActive() {
2503
+ return !isConfigFromFlag() && !constants.default.ENV.SOCKET_CLI_NO_API_TOKEN && !constants.default.ENV.SOCKET_CLI_API_TOKEN && getConfigValueOrUndef(constants.CONFIG_KEY_AUTH_TYPE) === 'oauth';
1928
2504
  }
1929
- function getPublicApiToken() {
1930
- return getDefaultApiToken() || constants.default.ENV.SOCKET_CLI_API_TOKEN || constants.default.SOCKET_PUBLIC_API_TOKEN;
2505
+ async function resolvePublicApiToken(options) {
2506
+ const token = await resolveDefaultApiToken(options);
2507
+ if (token) {
2508
+ return token;
2509
+ }
2510
+ if (!constants.default.ENV.SOCKET_CLI_NO_API_TOKEN && getConfigValueOrUndef(constants.CONFIG_KEY_AUTH_TYPE) === 'oauth') {
2511
+ throw new Error('Browser sign-in has no usable access token. Run `socket login` to sign in again.');
2512
+ }
2513
+ return constants.default.SOCKET_PUBLIC_API_TOKEN;
1931
2514
  }
1932
2515
  function getVisibleTokenPrefix() {
2516
+ if (!constants.default.ENV.SOCKET_CLI_NO_API_TOKEN && getConfigValueOrUndef(constants.CONFIG_KEY_AUTH_TYPE) === 'oauth' && !constants.default.ENV.SOCKET_CLI_API_TOKEN) {
2517
+ return 'oauth';
2518
+ }
1933
2519
  const apiToken = getDefaultApiToken();
1934
2520
  return apiToken ? apiToken.slice(TOKEN_PREFIX_LENGTH, TOKEN_PREFIX_LENGTH + TOKEN_VISIBLE_LENGTH) : '';
1935
2521
  }
1936
2522
  function hasDefaultApiToken() {
1937
- return !!getDefaultApiToken();
2523
+ if (getDefaultApiToken()) {
2524
+ return true;
2525
+ }
2526
+ const directory = constants.default.socketAppDataPath;
2527
+ return Boolean(isOAuthLoginActive() && directory && hasOAuthCredentialFile(directory));
2528
+ }
2529
+ function hasOAuthCredentialFile(directory) {
2530
+ try {
2531
+ return Boolean(readOAuthCredentials(directory));
2532
+ } catch {
2533
+ // Keep preflight from crashing; async token resolution reports a useful
2534
+ // error and must not fall back to another credential.
2535
+ return true;
2536
+ }
2537
+ }
2538
+ async function resolveDefaultApiToken(options) {
2539
+ if (constants.default.ENV.SOCKET_CLI_NO_API_TOKEN) {
2540
+ return undefined;
2541
+ }
2542
+ const environmentToken = constants.default.ENV.SOCKET_CLI_API_TOKEN;
2543
+ if (strings.isNonEmptyString(environmentToken)) {
2544
+ return environmentToken;
2545
+ }
2546
+ if (getConfigValueOrUndef(constants.CONFIG_KEY_AUTH_TYPE) !== 'oauth') {
2547
+ const configured = getConfigValueOrUndef(constants.CONFIG_KEY_API_TOKEN);
2548
+ return strings.isNonEmptyString(configured) ? configured : undefined;
2549
+ }
2550
+
2551
+ // Explicit config is read-only and must never consult persisted OAuth state.
2552
+ if (isConfigFromFlag()) {
2553
+ return undefined;
2554
+ }
2555
+ const directory = constants.default.socketAppDataPath;
2556
+ if (!directory) {
2557
+ throw new Error('OAuth credentials are unavailable in this environment. Run `socket login` in a supported user session.');
2558
+ }
2559
+ const credentials = readOAuthCredentials(directory);
2560
+ if (!credentials) {
2561
+ throw new Error('Browser sign-in is selected but its credentials are missing or invalid. Run `socket login` to sign in again.');
2562
+ }
2563
+ const apiBaseUrl = options?.apiBaseUrl ?? getDefaultApiBaseUrl$1();
2564
+ if (apiBaseUrl) {
2565
+ let origin;
2566
+ try {
2567
+ origin = new URL(apiBaseUrl).origin;
2568
+ } catch {
2569
+ throw new Error('The configured Socket API URL is invalid for OAuth.');
2570
+ }
2571
+ if (origin !== credentials.apiOrigin) {
2572
+ throw new Error('Browser sign-in can only be used with Socket’s trusted API origin. Use `socket login --api-token` for a custom API URL.');
2573
+ }
2574
+ }
2575
+ return await resolveOAuthAccessToken(directory, {
2576
+ apiProxy: options?.apiProxy ?? getDefaultProxyUrl(),
2577
+ signal: options?.signal
2578
+ });
1938
2579
  }
1939
2580
  async function setupSdk(options) {
1940
2581
  const opts = {
1941
2582
  __proto__: null,
1942
2583
  ...options
1943
2584
  };
1944
- let {
1945
- apiToken = getDefaultApiToken()
1946
- } = opts;
1947
- if (typeof apiToken !== 'string' && vendor.isInteractiveExports()) {
1948
- apiToken = await prompts.password({
1949
- message: 'Enter your Socket.dev API token (not saved, use socket login to persist)'
1950
- });
1951
- _defaultToken = apiToken;
2585
+ let apiToken;
2586
+ try {
2587
+ apiToken = opts.apiToken ?? (await resolveDefaultApiToken({
2588
+ apiBaseUrl: opts.apiBaseUrl,
2589
+ apiProxy: opts.apiProxy
2590
+ }));
2591
+ } catch (error) {
2592
+ return {
2593
+ ok: false,
2594
+ message: 'Auth Error',
2595
+ cause: getErrorCause(error)
2596
+ };
1952
2597
  }
1953
2598
  if (!apiToken) {
1954
2599
  return {
@@ -2095,6 +2740,18 @@ async function setupSdk(options) {
2095
2740
 
2096
2741
  const MAX_REDIRECTS = 20;
2097
2742
  const NO_ERROR_MESSAGE = 'No error message returned';
2743
+ function getApiErrorText(value) {
2744
+ if (typeof value === 'string') {
2745
+ return value;
2746
+ }
2747
+ if (!value || typeof value !== 'object') {
2748
+ return '';
2749
+ }
2750
+ const record = value;
2751
+ const details = record['details'];
2752
+ const nestedCode = details && typeof details === 'object' ? details['code'] : undefined;
2753
+ return [record['code'], nestedCode, record['message'], record['error']].filter(part => typeof part === 'string').join(' ');
2754
+ }
2098
2755
 
2099
2756
  // Cached HTTPS agent for direct API calls. Undefined only until the first
2100
2757
  // getHttpsAgent() call lazily creates it.
@@ -2243,14 +2900,18 @@ function _httpsRequestFetch(url, init, agent, redirectCount) {
2243
2900
  * API gives in its `{ error: { message } }` body when there is one.
2244
2901
  */
2245
2902
  async function describeApiFailure(response, path) {
2246
- let serverMessage;
2903
+ let serverMessage = '';
2904
+ let serverCode = '';
2247
2905
  try {
2248
- serverMessage = JSON.parse(await response.text())?.error?.message;
2906
+ const payload = JSON.parse(await response.text());
2907
+ const serverError = payload?.error;
2908
+ serverMessage = getApiErrorText(serverError);
2909
+ serverCode = [payload?.code, serverError?.code, serverError?.details?.code].filter(part => typeof part === 'string').join(' ');
2249
2910
  } catch {
2250
2911
  // A body that isn't JSON carries no message.
2251
2912
  }
2252
- const reason = await getErrorMessageForHttpStatusCode(response.status);
2253
- return typeof serverMessage === 'string' && serverMessage ? `${response.statusText}: ${serverMessage} (reason: ${reason}) (path: ${path})` : `${response.statusText} (reason: ${reason}) (path: ${path})`;
2913
+ const reason = await getErrorMessageForHttpStatusCode(response.status, [serverCode, serverMessage].filter(Boolean).join(' '));
2914
+ return serverMessage ? `${response.statusText}: ${serverMessage} (reason: ${reason}) (path: ${path})` : `${response.statusText} (reason: ${reason}) (path: ${path})`;
2254
2915
  }
2255
2916
  async function apiFetch(url, init = {}) {
2256
2917
  return await _httpsRequestFetch(url, init, getHttpsAgent(), 0);
@@ -2270,7 +2931,13 @@ function getCommandRequirements(cmdPath) {
2270
2931
  /**
2271
2932
  * Log required permissions for a command when encountering 403 errors.
2272
2933
  */
2273
- function logPermissionsFor403(cmdPath) {
2934
+ function logPermissionsFor403(cmdPath, apiError) {
2935
+ if (isOAuthLoginActive()) {
2936
+ const guidance = getOAuth403Guidance(apiError);
2937
+ if (guidance) {
2938
+ logger.logger.error(guidance);
2939
+ }
2940
+ }
2274
2941
  const requirements = getCommandRequirements(cmdPath);
2275
2942
  if (!requirements?.permissions?.length) {
2276
2943
  return;
@@ -2279,7 +2946,9 @@ function logPermissionsFor403(cmdPath) {
2279
2946
  for (const permission of requirements.permissions) {
2280
2947
  logger.logger.error(` - ${permission}`);
2281
2948
  }
2282
- logger.logger.error('Please ensure your API token has the required permissions.');
2949
+ if (!isOAuthLoginActive()) {
2950
+ logger.logger.error('Please ensure your API token has the required permissions.');
2951
+ }
2283
2952
  }
2284
2953
 
2285
2954
  // The Socket API server that should be used for operations.
@@ -2295,7 +2964,16 @@ function getDefaultApiBaseUrl() {
2295
2964
  /**
2296
2965
  * Get user-friendly error message for HTTP status codes.
2297
2966
  */
2298
- async function getErrorMessageForHttpStatusCode(code) {
2967
+ function getOAuth403Guidance(apiError) {
2968
+ if (/missing[_ -]capability/i.test(apiError ?? '')) {
2969
+ return 'This organization does not grant the capability required for this command. Ask an organization admin to enable it.';
2970
+ }
2971
+ if (/missing[_ -]scope/i.test(apiError ?? '')) {
2972
+ return 'Browser sign-in is read-only. Use `socket login --api-token` or SOCKET_CLI_API_TOKEN for scans and write operations.';
2973
+ }
2974
+ return undefined;
2975
+ }
2976
+ async function getErrorMessageForHttpStatusCode(code, apiError) {
2299
2977
  if (code === constants.HTTP_STATUS_BAD_REQUEST) {
2300
2978
  return 'One of the options passed might be incorrect';
2301
2979
  }
@@ -2303,6 +2981,13 @@ async function getErrorMessageForHttpStatusCode(code) {
2303
2981
  return 'Your Socket API token appears to be invalid, expired, or revoked. Please verify your token is correct and active';
2304
2982
  }
2305
2983
  if (code === constants.HTTP_STATUS_FORBIDDEN) {
2984
+ if (isOAuthLoginActive()) {
2985
+ const guidance = getOAuth403Guidance(apiError);
2986
+ if (guidance) {
2987
+ return guidance;
2988
+ }
2989
+ return 'Browser sign-in is read-only and does not support this operation. Use `socket login --api-token` or SOCKET_CLI_API_TOKEN';
2990
+ }
2306
2991
  return 'Your Socket API token may not have the required permissions for this command or you might be trying to access (data from) an organization that is not linked to the API token you are logged in with';
2307
2992
  }
2308
2993
  if (code === constants.HTTP_STATUS_NOT_FOUND) {
@@ -2395,15 +3080,16 @@ async function handleApiCall(value, options) {
2395
3080
  debug$2.debugDir('inspect', {
2396
3081
  sdkResult
2397
3082
  });
2398
- const errStr = sdkResult.error ? String(sdkResult.error).trim() : '';
3083
+ const errStr = getApiErrorText(sdkResult.error);
2399
3084
  const message = errStr || NO_ERROR_MESSAGE;
2400
- const reason = sdkResult.cause || NO_ERROR_MESSAGE;
3085
+ const reason = getApiErrorText(sdkResult.cause) || NO_ERROR_MESSAGE;
3086
+ const statusReason = sdkResult.status === constants.HTTP_STATUS_FORBIDDEN && isOAuthLoginActive() ? await getErrorMessageForHttpStatusCode(sdkResult.status, `${errStr} ${reason}`) : undefined;
2401
3087
  const baseCause = reason && message !== reason ? `${message} (reason: ${reason})` : message;
2402
3088
  const cause = sdkResult.url ? `${baseCause} (url: ${sdkResult.url})` : baseCause;
2403
3089
  const socketSdkErrorResult = {
2404
3090
  ok: false,
2405
3091
  message: 'Socket API error',
2406
- cause,
3092
+ cause: statusReason ? `${cause} (reason: ${statusReason})` : cause,
2407
3093
  data: {
2408
3094
  code: sdkResult.status
2409
3095
  }
@@ -2411,7 +3097,7 @@ async function handleApiCall(value, options) {
2411
3097
 
2412
3098
  // Log required permissions for 403 errors when in a command context.
2413
3099
  if (commandPath && sdkResult.status === 403) {
2414
- logPermissionsFor403(commandPath);
3100
+ logPermissionsFor403(commandPath, `${errStr} ${reason}`);
2415
3101
  }
2416
3102
  return socketSdkErrorResult;
2417
3103
  }
@@ -2424,56 +3110,6 @@ async function handleApiCall(value, options) {
2424
3110
  };
2425
3111
  return socketSdkSuccessResult;
2426
3112
  }
2427
- async function handleApiCallNoSpinner(value, description) {
2428
- let sdkResult;
2429
- try {
2430
- sdkResult = await value;
2431
- } catch (e) {
2432
- debug$2.debugFn('error', `API request failed: ${description}`);
2433
- debug$2.debugDir('error', e);
2434
- const errStr = e ? String(e).trim() : '';
2435
- const message = 'Socket API error';
2436
- const rawCause = errStr || NO_ERROR_MESSAGE;
2437
- const cause = message !== rawCause ? rawCause : '';
2438
- return {
2439
- ok: false,
2440
- message,
2441
- ...(cause ? {
2442
- cause
2443
- } : {})
2444
- };
2445
- }
2446
-
2447
- // The `success` discriminant is a concrete boolean literal in both branches,
2448
- // so this narrows the result to its error branch regardless of `R`.
2449
- if (sdkResult.success === false) {
2450
- debug$2.debugFn('error', `fail: ${description} bad response`);
2451
- debug$2.debugDir('inspect', {
2452
- sdkResult
2453
- });
2454
- const errStr = sdkResult.error ? String(sdkResult.error).trim() : '';
2455
- const message = errStr || NO_ERROR_MESSAGE;
2456
- const reason = sdkResult.cause || NO_ERROR_MESSAGE;
2457
- const baseCause = reason && message !== reason ? `${message} (reason: ${reason})` : message;
2458
- const cause = sdkResult.url ? `${baseCause} (url: ${sdkResult.url})` : baseCause;
2459
- return {
2460
- ok: false,
2461
- message: 'Socket API error',
2462
- cause,
2463
- data: {
2464
- code: sdkResult.status
2465
- }
2466
- };
2467
- } else {
2468
- // Narrowing a type parameter by its `success` discriminant resolves `.data`
2469
- // through the constraint's success branch (`unknown`); re-apply the precise
2470
- // per-call success type the return contract already guarantees.
2471
- return {
2472
- ok: true,
2473
- data: sdkResult.data
2474
- };
2475
- }
2476
- }
2477
3113
  async function queryApi(path, apiToken) {
2478
3114
  const baseUrl = getDefaultApiBaseUrl();
2479
3115
  const url = `${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`;
@@ -2493,12 +3129,23 @@ async function queryApi(path, apiToken) {
2493
3129
  * status-dependent flows such as the cached-scan 202 poll loop.
2494
3130
  */
2495
3131
  async function queryApiSafeTextWithStatus(path, description, commandPath) {
2496
- const apiToken = getDefaultApiToken();
3132
+ let apiToken;
3133
+ try {
3134
+ apiToken = await resolveDefaultApiToken({
3135
+ apiBaseUrl: getDefaultApiBaseUrl()
3136
+ });
3137
+ } catch (error) {
3138
+ return {
3139
+ ok: false,
3140
+ message: 'Authentication Error',
3141
+ cause: getErrorCause(error)
3142
+ };
3143
+ }
2497
3144
  if (!apiToken) {
2498
3145
  return {
2499
3146
  ok: false,
2500
3147
  message: 'Authentication Error',
2501
- cause: 'User must be authenticated to run this command. Run `socket login` and enter your Socket API token.'
3148
+ cause: 'User must be authenticated to run this command. Run `socket login` or set SOCKET_CLI_API_TOKEN.'
2502
3149
  };
2503
3150
  }
2504
3151
  const {
@@ -2605,15 +3252,26 @@ async function queryApiSafeJson(path, description = '') {
2605
3252
  * Send POST/PUT request to Socket API with JSON response handling.
2606
3253
  */
2607
3254
  async function sendApiRequest(path, options) {
2608
- const apiToken = getDefaultApiToken();
3255
+ const baseUrl = getDefaultApiBaseUrl();
3256
+ let apiToken;
3257
+ try {
3258
+ apiToken = await resolveDefaultApiToken({
3259
+ apiBaseUrl: baseUrl
3260
+ });
3261
+ } catch (error) {
3262
+ return {
3263
+ ok: false,
3264
+ message: 'Authentication Error',
3265
+ cause: getErrorCause(error)
3266
+ };
3267
+ }
2609
3268
  if (!apiToken) {
2610
3269
  return {
2611
3270
  ok: false,
2612
3271
  message: 'Authentication Error',
2613
- cause: 'User must be authenticated to run this command. To log in, run the command `socket login` and enter your Socket API token.'
3272
+ cause: 'User must be authenticated to run this command. Run `socket login` or set SOCKET_CLI_API_TOKEN.'
2614
3273
  };
2615
3274
  }
2616
- const baseUrl = getDefaultApiBaseUrl();
2617
3275
  const {
2618
3276
  body,
2619
3277
  commandPath,
@@ -5472,7 +6130,16 @@ async function spawnCoana(args, orgSlug, options) {
5472
6130
  // outbound axios User-Agent header.
5473
6131
  SOCKET_CALLER_USER_AGENT: getCliUserAgent()
5474
6132
  };
5475
- const defaultApiToken = getDefaultApiToken();
6133
+ let defaultApiToken;
6134
+ try {
6135
+ defaultApiToken = await resolveDefaultApiToken();
6136
+ } catch (error) {
6137
+ return {
6138
+ ok: false,
6139
+ message: 'Socket authentication failed',
6140
+ cause: error instanceof Error ? error.message : String(error)
6141
+ };
6142
+ }
5476
6143
  if (defaultApiToken) {
5477
6144
  mixinsEnv['SOCKET_CLI_API_TOKEN'] = defaultApiToken;
5478
6145
  }
@@ -6653,6 +7320,14 @@ function isHelpFlag(cmdArg) {
6653
7320
  return helpFlags.has(cmdArg);
6654
7321
  }
6655
7322
 
7323
+ /**
7324
+ * Check if npm command requires lockfile scanning.
7325
+ * npm uses: install, i, update
7326
+ */
7327
+ function isNpmLockfileScanCommand(command) {
7328
+ return command === 'install' || command === 'i' || command === 'ci' || command === 'update';
7329
+ }
7330
+
6656
7331
  /**
6657
7332
  * Check if pnpm command requires lockfile scanning.
6658
7333
  * pnpm uses: install, i, update, up
@@ -6726,10 +7401,10 @@ function parseReachEcosystems(raw) {
6726
7401
  * within an ecosystem (e.g. only PNPM artifacts in a mixed pnpm/yarn/npm repo).
6727
7402
  *
6728
7403
  * Mirrors the list returned by Coana's `getFilterablePackageManagers()` in
6729
- * packages/web-compat-utils/src/package-manager-utils.ts.
7404
+ * packages/utils/src/package-manager-utils.ts.
6730
7405
  */
6731
7406
 
6732
- const ALL_PACKAGE_MANAGERS = ['CARGO', 'COMPOSER', 'GO', 'GRADLE', 'MAVEN', 'NPM', 'NUGET', 'PIPENV', 'PIP_REQUIREMENTS', 'PNPM', 'POETRY', 'RUBYGEMS', 'RUSH', 'SBT', 'YARN'];
7407
+ const ALL_PACKAGE_MANAGERS = ['CARGO', 'COMPOSER', 'GO', 'GRADLE', 'MAVEN', 'NPM', 'NUGET', 'PIPENV', 'PIP_REQUIREMENTS', 'PNPM', 'POETRY', 'RUBYGEMS', 'RUSH', 'SBT', 'UV', 'YARN'];
6733
7408
  const ALL_PACKAGE_MANAGERS_SET = new Set(ALL_PACKAGE_MANAGERS);
6734
7409
  function isValidPackageManager(value) {
6735
7410
  return ALL_PACKAGE_MANAGERS_SET.has(value);
@@ -7185,10 +7860,10 @@ function shadowNpmInstall(options) {
7185
7860
  const useDebug = debug$2.isDebug('stdio');
7186
7861
  const terminatorPos = args.indexOf('--');
7187
7862
  const rawBinArgs = terminatorPos === -1 ? args : args.slice(0, terminatorPos);
7188
- const binArgs = rawBinArgs.filter(a => !agent.isNpmAuditFlag(a) && !agent.isNpmFundFlag(a) && !agent.isNpmProgressFlag(a));
7863
+ const binArgs = rawBinArgs.filter(a => !agent$1.isNpmAuditFlag(a) && !agent$1.isNpmFundFlag(a) && !agent$1.isNpmProgressFlag(a));
7189
7864
  const otherArgs = terminatorPos === -1 ? [] : args.slice(terminatorPos);
7190
- const progressArg = rawBinArgs.findLast(agent.isNpmProgressFlag) !== '--no-progress';
7191
- const isSilent = !useDebug && !binArgs.some(agent.isNpmLoglevelFlag);
7865
+ const progressArg = rawBinArgs.findLast(agent$1.isNpmProgressFlag) !== '--no-progress';
7866
+ const isSilent = !useDebug && !binArgs.some(agent$1.isNpmLoglevelFlag);
7192
7867
  const logLevelArgs = isSilent ? [constants.FLAG_LOGLEVEL, 'silent'] : [];
7193
7868
  const useIpc = objects.isObject(ipc);
7194
7869
 
@@ -7205,7 +7880,7 @@ function shadowNpmInstall(options) {
7205
7880
  } else {
7206
7881
  stdio = useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : 'pipe';
7207
7882
  }
7208
- const spawnPromise = spawn.spawn(constants.default.execPath, [...constants.default.nodeNoWarningsFlags, ...constants.default.nodeDebugFlags, ...constants.default.nodeHardenFlags, ...constants.default.nodeMemoryFlags, ...(constants.default.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD ? ['--require', constants.default.instrumentWithSentryPath] : []), '--require', constants.default.shadowNpmInjectPath, agent.resolveBinPathSync(agentExecPath), 'install',
7883
+ const spawnPromise = spawn.spawn(constants.default.execPath, [...constants.default.nodeNoWarningsFlags, ...constants.default.nodeDebugFlags, ...constants.default.nodeHardenFlags, ...constants.default.nodeMemoryFlags, ...(constants.default.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD ? ['--require', constants.default.instrumentWithSentryPath] : []), '--require', constants.default.shadowNpmInjectPath, agent$1.resolveBinPathSync(agentExecPath), 'install',
7209
7884
  // Avoid code paths for 'audit' and 'fund'.
7210
7885
  '--no-audit', '--no-fund',
7211
7886
  // Add '--no-progress' to fix input being swallowed by the npm spinner.
@@ -9104,8 +9779,10 @@ exports.CVE_FORMAT_REGEXP = CVE_FORMAT_REGEXP;
9104
9779
  exports.GHSA_FORMAT_REGEXP = GHSA_FORMAT_REGEXP;
9105
9780
  exports.IGNORED_DIRS = IGNORED_DIRS;
9106
9781
  exports.InputError = InputError;
9782
+ exports.OAUTH_ISSUER = OAUTH_ISSUER;
9107
9783
  exports.RangeStyles = RangeStyles;
9108
9784
  exports.apiFetch = apiFetch;
9785
+ exports.beginOAuthLogin = beginOAuthLogin;
9109
9786
  exports.cacheFetch = cacheFetch;
9110
9787
  exports.camelToKebab = camelToKebab;
9111
9788
  exports.captureException = captureException;
@@ -9141,7 +9818,9 @@ exports.getBaseBranch = getBaseBranch;
9141
9818
  exports.getBashrcDetails = getBashrcDetails;
9142
9819
  exports.getConfigValue = getConfigValue;
9143
9820
  exports.getConfigValueOrUndef = getConfigValueOrUndef;
9821
+ exports.getDefaultApiBaseUrl = getDefaultApiBaseUrl$1;
9144
9822
  exports.getDefaultOrgSlug = getDefaultOrgSlug;
9823
+ exports.getDefaultProxyUrl = getDefaultProxyUrl;
9145
9824
  exports.getEcosystemChoicesForMeow = getEcosystemChoicesForMeow;
9146
9825
  exports.getEnterpriseOrgs = getEnterpriseOrgs;
9147
9826
  exports.getErrorCause = getErrorCause;
@@ -9160,7 +9839,6 @@ exports.getOrInsert = getOrInsert;
9160
9839
  exports.getOrgSlugs = getOrgSlugs;
9161
9840
  exports.getOutputKind = getOutputKind;
9162
9841
  exports.getPackageFilesForScan = getPackageFilesForScan;
9163
- exports.getPublicApiToken = getPublicApiToken;
9164
9842
  exports.getReachabilityEcosystemChoices = getReachabilityEcosystemChoices;
9165
9843
  exports.getRepoInfo = getRepoInfo;
9166
9844
  exports.getRepoName = getRepoName;
@@ -9184,7 +9862,6 @@ exports.githubApiRequest = githubApiRequest;
9184
9862
  exports.globWithGitIgnore = globWithGitIgnore;
9185
9863
  exports.globWorkspace = globWorkspace;
9186
9864
  exports.handleApiCall = handleApiCall;
9187
- exports.handleApiCallNoSpinner = handleApiCallNoSpinner;
9188
9865
  exports.hasDefaultApiToken = hasDefaultApiToken;
9189
9866
  exports.hasEnterpriseOrgPlan = hasEnterpriseOrgPlan;
9190
9867
  exports.idToNpmPurl = idToNpmPurl;
@@ -9198,6 +9875,7 @@ exports.isErrnoException = isErrnoException;
9198
9875
  exports.isFactsOnlyTypes = isFactsOnlyTypes;
9199
9876
  exports.isGitHubBlockingError = isGitHubBlockingError;
9200
9877
  exports.isHelpFlag = isHelpFlag;
9878
+ exports.isNpmLockfileScanCommand = isNpmLockfileScanCommand;
9201
9879
  exports.isPnpmLockfileScanCommand = isPnpmLockfileScanCommand;
9202
9880
  exports.isReachabilityReportPath = isReachabilityReportPath;
9203
9881
  exports.isReportSupportedFile = isReportSupportedFile;
@@ -9208,6 +9886,7 @@ exports.isValidPackageManager = isValidPackageManager;
9208
9886
  exports.isYarnBerry = isYarnBerry;
9209
9887
  exports.loadSocketConfig = loadSocketConfig;
9210
9888
  exports.logAlertsMap = logAlertsMap;
9889
+ exports.logoutOAuth = logoutOAuth;
9211
9890
  exports.mailtoLink = mailtoLink;
9212
9891
  exports.mapToObject = mapToObject;
9213
9892
  exports.mdTable = mdTable;
@@ -9229,10 +9908,12 @@ exports.readSocketJsonCascade = readSocketJsonCascade;
9229
9908
  exports.readSocketJsonSync = readSocketJsonSync;
9230
9909
  exports.realpathOrResolved = realpathOrResolved;
9231
9910
  exports.resolveAdvisoryIds = resolveAdvisoryIds;
9911
+ exports.resolvePublicApiToken = resolvePublicApiToken;
9232
9912
  exports.runAgentInstall = runAgentInstall;
9233
9913
  exports.runSelfUpdateCheck = runSelfUpdateCheck;
9234
9914
  exports.safeNpa = safeNpa$1;
9235
9915
  exports.safeNpmSpecToPurl = safeNpmSpecToPurl;
9916
+ exports.saveOAuthCredentials = saveOAuthCredentials;
9236
9917
  exports.sendApiRequest = sendApiRequest;
9237
9918
  exports.serializeResultJson = serializeResultJson;
9238
9919
  exports.setGitRemoteGithubRepoUrl = setGitRemoteGithubRepoUrl;
@@ -9260,5 +9941,5 @@ exports.walkNestedMap = walkNestedMap;
9260
9941
  exports.webLink = webLink;
9261
9942
  exports.withTmpDir = withTmpDir;
9262
9943
  exports.writeSocketJson = writeSocketJson;
9263
- //# debugId=871c30cb-bdbb-449e-8766-245047cb9b4d
9944
+ //# debugId=ead18e45-7f35-4138-8864-de9f0791c0e
9264
9945
  //# sourceMappingURL=utils.js.map