Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/commands/manifest/bazel/bazel-repo-discovery.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe('bazel-repo-discovery', () => {
probeResult({
code: 2,
stderr:
'ERROR: In extension argument @rules_jvm_external//:extensions.bzl%maven: No module with the apparent repo name @rules_jvm_external exists in the dependency graph. Type \'bazel help mod\' for syntax and help.\n',
"ERROR: In extension argument @rules_jvm_external//:extensions.bzl%maven: No module with the apparent repo name @rules_jvm_external exists in the dependency graph. Type 'bazel help mod' for syntax and help.\n",
}),
0,
),
Expand Down
12 changes: 6 additions & 6 deletions src/commands/manifest/bazel/cmd-manifest-bazel.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -243,18 +243,18 @@ describe('evaluateEcosystemOutcomes (explicit mode)', () => {
})

describe('perRepoTimeout flag wiring', () => {
const importMeta = { url: 'file:///cmd-manifest-bazel.test.mts' } as ImportMeta
const importMeta = {
url: 'file:///cmd-manifest-bazel.test.mts',
} as ImportMeta

beforeEach(() => {
vi.mocked(extractBazelToMaven).mockClear()
})

it('defaults the explicit command to a 120s per-repo timeout', async () => {
await cmdManifestBazel.run(
[FLAG_CONFIG, '{}', '.'],
importMeta,
{ parentName: 'manifest' } as CliCommandContext,
)
await cmdManifestBazel.run([FLAG_CONFIG, '{}', '.'], importMeta, {
parentName: 'manifest',
} as CliCommandContext)
expect(extractBazelToMaven).toHaveBeenCalledTimes(1)
expect(extractBazelToMaven).toHaveBeenCalledWith(
expect.objectContaining({ perRepoTimeoutMs: 120_000 }),
Expand Down
4 changes: 1 addition & 3 deletions src/commands/manifest/bazel/extract_bazel_to_maven.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -1192,9 +1192,7 @@ Fetched repositories:
expect(result.status).toBe('partial')
expect(result.complete).toBe(false)
expect(result.manifestPaths).toHaveLength(1)
const loadFailed = result.workspaceOutcomes.filter(
w => w.load === 'failed',
)
const loadFailed = result.workspaceOutcomes.filter(w => w.load === 'failed')
expect(loadFailed).toHaveLength(1)
})

Expand Down
4 changes: 3 additions & 1 deletion src/utils/api.mts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,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'

Comment thread
jdalton marked this conversation as resolved.
import type { CResult } from '../types.mts'
import type { Spinner } from '@socketsecurity/registry/lib/spinner'
Expand Down Expand Up @@ -455,6 +455,7 @@ async function queryApi(path: string, apiToken: string) {
method: 'GET',
headers: {
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
'User-Agent': getCliUserAgent(),
},
})
return result
Expand Down Expand Up @@ -640,6 +641,7 @@ export async function sendApiRequest<T>(
headers: {
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
'Content-Type': 'application/json',
'User-Agent': getCliUserAgent(),
},
...(body ? { body: JSON.stringify(body) } : {}),
}
Expand Down
6 changes: 6 additions & 0 deletions src/utils/api.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import type { EventEmitter } from 'node:events'
// Mock getExtraCaCerts from sdk.mts.
const mockGetExtraCaCerts = vi.hoisted(() => vi.fn(() => undefined))
const mockGetDefaultApiToken = vi.hoisted(() => vi.fn(() => 'test-api-token'))
const mockGetCliUserAgent = vi.hoisted(() =>
vi.fn(() => 'socket-cli/1.0.0 node/v20.0.0 test/x64'),
)
vi.mock('./sdk.mts', () => ({
getCliUserAgent: mockGetCliUserAgent,
getDefaultApiToken: mockGetDefaultApiToken,
getDefaultApiBaseUrl: vi.fn(() => undefined),
getDefaultProxyUrl: vi.fn(() => undefined),
Expand Down Expand Up @@ -157,6 +161,8 @@ describe('apiFetch with extra CA certificates', () => {
// The request is made with that explicit agent.
const callArgs = mockHttpsRequest.mock.calls[0]
expect(callArgs[1].agent).toMatchObject({ _isHttpsAgent: true })
// The outgoing request carries the CLI's User-Agent header.
expect(callArgs[1].headers['User-Agent']).toBe(mockGetCliUserAgent())
expect(result.ok).toBe(true)
})

Expand Down
11 changes: 7 additions & 4 deletions src/utils/dlx.mts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ 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'
Expand Down Expand Up @@ -437,9 +441,8 @@ export async function spawnCoanaDlx(
const mixinsEnv: Record<string, string> = {
SOCKET_CLI_VERSION: constants.ENV.INLINED_SOCKET_CLI_VERSION,
// Forwarded to the Coana CLI so it can append our product token to its
// outbound axios User-Agent header. Format mirrors Coana's base UA:
// `socket/<version> node/<nodeVersion> <platform>/<arch>`.
SOCKET_CALLER_USER_AGENT: `socket/${constants.ENV.INLINED_SOCKET_CLI_VERSION} node/${process.version} ${process.platform}/${process.arch}`,
// outbound axios User-Agent header.
SOCKET_CALLER_USER_AGENT: getCliUserAgent(),
}
Comment thread
jdalton marked this conversation as resolved.
const defaultApiToken = getDefaultApiToken()
if (defaultApiToken) {
Expand Down
34 changes: 34 additions & 0 deletions src/utils/dlx.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ vi.mock('@socketsecurity/registry/lib/spawn', () => ({
}))

vi.mock('./sdk.mts', () => ({
getCliUserAgent: () => 'socket-cli/1.0.0 node/v20.0.0 test/x64',
getDefaultApiToken: () => undefined,
getDefaultProxyUrl: () => undefined,
}))
Expand Down Expand Up @@ -678,6 +679,39 @@ describe('utils/dlx', () => {
delete process.env['npm_config_registry']
}
})

it('injects SOCKET_CALLER_USER_AGENT from getCliUserAgent into the spawn env', async () => {
const result = await spawnCoanaDlx(['run', '.'], 'acme', {
coanaVersion: nextVersion(),
})
expect(result.ok).toBe(true)

const nodeCall = mockSpawn.mock.calls.find(([cmd]) => cmd === 'node')!
const nodeEnv = (nodeCall[2] as { env: NodeJS.ProcessEnv }).env

expect(nodeEnv['SOCKET_CALLER_USER_AGENT']).toBe(
'socket-cli/1.0.0 node/v20.0.0 test/x64',
)
})

it('lets a caller-supplied env.SOCKET_CALLER_USER_AGENT override the injected default', async () => {
// spawnCoanaDlx builds finalEnv as
// { ...process.env, ...constants.processEnv, ...mixinsEnv, ...spawnEnv }
// — spawnEnv (the caller's options.env) is spread last, so it wins.
const result = await spawnCoanaDlx(['run', '.'], 'acme', {
coanaVersion: nextVersion(),
env: { SOCKET_CALLER_USER_AGENT: 'custom-caller-agent/1.0.0' },
})
expect(result.ok).toBe(true)

const nodeCall = mockSpawn.mock.calls.find(([cmd]) => cmd === 'node')!
const nodeEnv = (nodeCall[2] as { env: NodeJS.ProcessEnv }).env

// caller-supplied env in options.env takes precedence over the injected default.
expect(nodeEnv['SOCKET_CALLER_USER_AGENT']).toBe(
'custom-caller-agent/1.0.0',
)
})
})

describe('spawnCoanaDlx stdio + error surfacing', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/utils/glob.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ describe('glob utilities', () => {
// pyvenv.cfg marker at its root. Manifests inside it must not surface.
mockTestFs({
[`${mockFixturePath}/requirements.txt`]: '',
[`${mockFixturePath}/myenv/pyvenv.cfg`]: 'home = /usr/bin\nversion = 3.11.0\n',
[`${mockFixturePath}/myenv/pyvenv.cfg`]:
'home = /usr/bin\nversion = 3.11.0\n',
[`${mockFixturePath}/myenv/requirements.txt`]: '',
[`${mockFixturePath}/myenv/lib/python3.11/site-packages/foo/setup.py`]:
'',
Expand Down
20 changes: 20 additions & 0 deletions src/utils/sdk.mts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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
}
Comment thread
jdalton marked this conversation as resolved.

const TOKEN_PREFIX = 'sktsec_'
const TOKEN_PREFIX_LENGTH = TOKEN_PREFIX.length
const TOKEN_VISIBLE_LENGTH = 5
Expand Down
19 changes: 18 additions & 1 deletion src/utils/sdk.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import constants from '../constants.mts'
import { getExtraCaCerts, setupSdk } from './sdk.mts'
import { getCliUserAgent, getExtraCaCerts, setupSdk } from './sdk.mts'

import type { RequestInfo, ResponseInfo } from '@socketsecurity/sdk'

Expand Down Expand Up @@ -575,6 +575,23 @@ describe('SDK setup with telemetry hooks', () => {
})
})

describe('getCliUserAgent', () => {
it('formats name/version, node version, and platform/arch', () => {
const userAgent = getCliUserAgent()

expect(userAgent).toBe(
`socket-cli/1.1.34 node/${process.version} ${process.platform}/${process.arch}`,
)
})

it('returns a stable value across repeated calls', () => {
const first = getCliUserAgent()
const second = getCliUserAgent()

expect(second).toBe(first)
})
})

describe('getExtraCaCerts', () => {
const savedEnv = { ...process.env }

Expand Down
Loading