diff --git a/src/utils/api.mts b/src/utils/api.mts index 26f1085ca..26915afea 100644 --- a/src/utils/api.mts +++ b/src/utils/api.mts @@ -39,7 +39,7 @@ import constants, { HTTP_STATUS_UNAUTHORIZED, } from '../constants.mts' import { getRequirements, getRequirementsKey } from './requirements.mts' -import { getDefaultApiToken, getExtraCaCerts } from './sdk.mts' +import { getCliUserAgent, getDefaultApiToken, getExtraCaCerts } from './sdk.mts' import type { CResult } from '../types.mts' import type { Spinner } from '@socketsecurity/registry/lib/spinner' @@ -437,6 +437,7 @@ async function queryApi(path: string, apiToken: string) { method: 'GET', headers: { Authorization: `Basic ${btoa(`${apiToken}:`)}`, + 'User-Agent': getCliUserAgent(), }, }) return result @@ -622,6 +623,7 @@ export async function sendApiRequest( headers: { Authorization: `Basic ${btoa(`${apiToken}:`)}`, 'Content-Type': 'application/json', + 'User-Agent': getCliUserAgent(), }, ...(body ? { body: JSON.stringify(body) } : {}), } diff --git a/src/utils/dlx.mts b/src/utils/dlx.mts index acb167af6..4c1fe6b6d 100644 --- a/src/utils/dlx.mts +++ b/src/utils/dlx.mts @@ -33,7 +33,7 @@ import constants, { } from '../constants.mts' import { getErrorCause } from './errors.mts' import { findUp } from './fs.mts' -import { getDefaultApiToken, getDefaultProxyUrl } from './sdk.mts' +import { getCliUserAgent, getDefaultApiToken, getDefaultProxyUrl } from './sdk.mts' import { isYarnBerry } from './yarn-version.mts' import type { ShadowBinOptions, ShadowBinResult } from '../shadow/npm-base.mts' @@ -207,6 +207,8 @@ export async function spawnCoanaDlx( const mixinsEnv: Record = { SOCKET_CLI_VERSION: constants.ENV.INLINED_SOCKET_CLI_VERSION, + // Pass the CLI's full user agent so coana can append it to its own UA. + SOCKET_CALLER_USER_AGENT: getCliUserAgent(), } const defaultApiToken = getDefaultApiToken() if (defaultApiToken) { diff --git a/src/utils/sdk.mts b/src/utils/sdk.mts index ac72b0dc2..dae24773d 100644 --- a/src/utils/sdk.mts +++ b/src/utils/sdk.mts @@ -50,6 +50,26 @@ import { trackCliEvent } from './telemetry/integration.mts' import type { CResult } from '../types.mts' import type { RequestInfo, ResponseInfo } from '@socketsecurity/sdk' +// Lazy-evaluated full CLI user agent for direct (non-SDK) API calls. +// Includes the CLI product token, Node.js version, and OS platform/arch. +// e.g. "socket/1.1.96 node/v22.0.0 linux/arm64" +let _cliUserAgent: string | undefined +export function getCliUserAgent(): string { + if (!_cliUserAgent) { + const name = constants.ENV.INLINED_SOCKET_CLI_NAME.replace('@', '').replace( + '/', + '-', + ) + const version = constants.ENV.INLINED_SOCKET_CLI_VERSION + _cliUserAgent = [ + `${name}/${version}`, + `node/${process.version}`, + `${process.platform}/${process.arch}`, + ].join(' ') + } + return _cliUserAgent +} + const TOKEN_PREFIX = 'sktsec_' const TOKEN_PREFIX_LENGTH = TOKEN_PREFIX.length const TOKEN_VISIBLE_LENGTH = 5