diff --git a/packages/cli/src/commands/scan/perform-reachability-analysis.mts b/packages/cli/src/commands/scan/perform-reachability-analysis.mts index 6afc868d2..67399da3e 100644 --- a/packages/cli/src/commands/scan/perform-reachability-analysis.mts +++ b/packages/cli/src/commands/scan/perform-reachability-analysis.mts @@ -2,6 +2,7 @@ import path from 'node:path' import { getDefaultLogger } from '@socketsecurity/lib/logger' +import { HTTP_STATUS_UNAUTHORIZED } from '../../constants/http.mts' import { DOT_SOCKET_DOT_FACTS_JSON } from '../../constants/paths.mts' import { SOCKET_DEFAULT_BRANCH, @@ -81,6 +82,15 @@ export async function performReachabilityAnalysis( // Check if user has enterprise plan for reachability analysis. const orgsCResult = await fetchOrganization() if (!orgsCResult.ok) { + const httpCode = (orgsCResult as { data?: { code?: number } }).data?.code + if (httpCode === HTTP_STATUS_UNAUTHORIZED) { + return { + ok: false, + message: 'Authentication failed', + cause: + 'Your API token appears to be invalid, expired, or revoked. Please check your token and try again.', + } + } return { ok: false, message: 'Unable to verify plan permissions', diff --git a/packages/cli/src/utils/socket/api.mts b/packages/cli/src/utils/socket/api.mts index 858a7c6ca..7b4606dc9 100644 --- a/packages/cli/src/utils/socket/api.mts +++ b/packages/cli/src/utils/socket/api.mts @@ -163,7 +163,16 @@ export async function getErrorMessageForHttpStatusCode(code: number) { '💡 Try: Check your command syntax and parameter values.' ) } - if (code === HTTP_STATUS_FORBIDDEN || code === HTTP_STATUS_UNAUTHORIZED) { + if (code === HTTP_STATUS_UNAUTHORIZED) { + return ( + '❌ Authentication failed: Your Socket API token appears to be invalid, expired, or revoked.\n' + + '💡 Try:\n' + + ' • Run `socket whoami` to verify your current token\n' + + ' • Run `socket login` to re-authenticate\n' + + ` • Manage tokens at ${SOCKET_SETTINGS_API_TOKENS_URL}` + ) + } + if (code === HTTP_STATUS_FORBIDDEN) { return ( '❌ Access denied: Your API token lacks required permissions or organization access.\n' + '💡 Try:\n' + diff --git a/packages/cli/test/unit/utils/socket/api.test.mts b/packages/cli/test/unit/utils/socket/api.test.mts index 297a88a76..670782b4e 100644 --- a/packages/cli/test/unit/utils/socket/api.test.mts +++ b/packages/cli/test/unit/utils/socket/api.test.mts @@ -173,7 +173,10 @@ describe('api utilities', () => { it('returns message for 401 Unauthorized', async () => { const result = await getErrorMessageForHttpStatusCode(401) - expect(result).toContain('permissions') + // 401 is now distinct from 403: it's an auth/token problem, not + // a permissions problem. Callers get actionable "re-auth" guidance. + expect(result).toContain('Authentication failed') + expect(result).toContain('token') }) it('returns message for 403 Forbidden', async () => {