From 49eeea3786941a29c2260a8920f69d084461b8c1 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Tue, 19 May 2026 18:00:00 -0700 Subject: [PATCH 1/2] feat(api): add User-Agent header to raw apiFetch calls Add getCliUserAgent() to sdk.mts that builds the CLI's full user agent string for direct (non-SDK) API calls, including the CLI product token, Node.js version, and OS platform/arch: socket/1.1.96 node/v22.0.0 linux/arm64 Add User-Agent header to queryApi() and sendApiRequest() which both go through apiFetch() bypassing the SDK. For SDK-routed calls the userAgent option already passes just the CLI product token (socket/1.1.96 ...) and the SDK constructs the full UA by prepending its own base including node/OS. --- src/utils/api.mts | 4 +++- src/utils/sdk.mts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) 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/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 From 3778a647c87e384cc27b974cf754cbf797ca0324 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Tue, 19 May 2026 18:05:35 -0700 Subject: [PATCH 2/2] feat(dlx): pass SOCKET_CALLER_USER_AGENT when spawning coana Adds the CLI's full user agent string to the env vars forwarded to the coana subprocess. Coana reads SOCKET_CALLER_USER_AGENT and appends it to its own base UA, producing a chain like: coana-tech-cli/15.3.0 node/v22.0.0 linux/arm64 socket/1.1.96 node/v22.0.0 linux/arm64 --- src/utils/dlx.mts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) {