From 5afbd8a31d0a826760ef503c9e5a146878710690 Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Sun, 8 Feb 2026 11:04:26 -0500 Subject: [PATCH 1/3] fix(security-agent): route sandbox analysis to org credits --- .../services/analysis-service.test.ts | 115 ++++++++++++++++++ .../services/analysis-service.ts | 1 + 2 files changed, 116 insertions(+) create mode 100644 src/lib/security-agent/services/analysis-service.test.ts diff --git a/src/lib/security-agent/services/analysis-service.test.ts b/src/lib/security-agent/services/analysis-service.test.ts new file mode 100644 index 0000000000..1efe927488 --- /dev/null +++ b/src/lib/security-agent/services/analysis-service.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, it, jest, beforeAll, beforeEach } from '@jest/globals'; +import type * as securityFindingsModule from '@/lib/security-agent/db/security-findings'; +import type * as securityAnalysisModule from '@/lib/security-agent/db/security-analysis'; +import type * as triageModule from './triage-service'; +import type * as tokensModule from '@/lib/tokens'; +import type { StreamEvent } from '@/components/cloud-agent/types'; + +const mockGetSecurityFindingById = jest.fn() as jest.MockedFunction< + typeof securityFindingsModule.getSecurityFindingById +>; +const mockUpdateAnalysisStatus = jest.fn() as jest.MockedFunction< + typeof securityAnalysisModule.updateAnalysisStatus +>; +const mockTriageSecurityFinding = jest.fn() as jest.MockedFunction< + typeof triageModule.triageSecurityFinding +>; +const mockGenerateApiToken = jest.fn() as jest.MockedFunction; +const mockInitiateSessionStream = jest.fn() as jest.MockedFunction< + (input: unknown) => AsyncGenerator +>; + +jest.mock('@/lib/security-agent/db/security-findings', () => ({ + getSecurityFindingById: mockGetSecurityFindingById, +})); + +jest.mock('@/lib/security-agent/db/security-analysis', () => ({ + updateAnalysisStatus: mockUpdateAnalysisStatus, +})); + +jest.mock('./triage-service', () => ({ + triageSecurityFinding: mockTriageSecurityFinding, +})); + +jest.mock('@/lib/tokens', () => ({ + generateApiToken: mockGenerateApiToken, +})); + +jest.mock('@/lib/cloud-agent/cloud-agent-client', () => ({ + createCloudAgentClient: jest.fn(() => ({ + initiateSessionStream: mockInitiateSessionStream, + })), +})); + +jest.mock('./auto-dismiss-service', () => ({ + maybeAutoDismissAnalysis: jest.fn(() => Promise.resolve()), +})); + +jest.mock('./extraction-service', () => ({ + extractSandboxAnalysis: jest.fn(() => Promise.resolve()), +})); + +let startSecurityAnalysis: typeof import('./analysis-service').startSecurityAnalysis; + +beforeAll(() => { + ({ startSecurityAnalysis } = require('./analysis-service')); +}); + +describe('analysis-service', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('passes organization id to cloud agent sandbox analysis', async () => { + const organizationId = 'org-123'; + const findingId = 'finding-123'; + const user = { id: 'user-1', google_user_email: 'test@example.com' }; + + const mockFinding = { + id: findingId, + analysis_status: 'new', + repo_full_name: 'acme/repo', + package_name: 'lodash', + package_ecosystem: 'npm', + severity: 'high', + dependency_scope: 'runtime', + cve_id: 'CVE-2021-12345', + ghsa_id: 'GHSA-xxxx-yyyy-zzzz', + title: 'Prototype Pollution in lodash', + description: 'A detailed description of the vulnerability', + vulnerable_version_range: '< 4.17.21', + patched_version: '4.17.21', + manifest_path: 'package.json', + }; + + mockGetSecurityFindingById.mockResolvedValue( + mockFinding as Awaited> + ); + mockUpdateAnalysisStatus.mockResolvedValue(undefined); + mockTriageSecurityFinding.mockResolvedValue({ + needsSandboxAnalysis: true, + needsSandboxReasoning: 'Runtime dependency with high severity', + suggestedAction: 'analyze_codebase', + confidence: 'high', + triageAt: new Date().toISOString(), + }); + mockGenerateApiToken.mockReturnValue('test-token'); + mockInitiateSessionStream.mockReturnValue((async function* () {})()); + + const result = await startSecurityAnalysis({ + findingId, + user: user as any, + githubRepo: 'acme/repo', + githubToken: 'gh-token', + model: 'anthropic/claude-sonnet-4', + organizationId, + }); + + expect(result.started).toBe(true); + expect(mockInitiateSessionStream).toHaveBeenCalledWith( + expect.objectContaining({ + kilocodeOrganizationId: organizationId, + }) + ); + }); +}); diff --git a/src/lib/security-agent/services/analysis-service.ts b/src/lib/security-agent/services/analysis-service.ts index c84206ebce..ee2202de36 100644 --- a/src/lib/security-agent/services/analysis-service.ts +++ b/src/lib/security-agent/services/analysis-service.ts @@ -583,6 +583,7 @@ export async function startSecurityAnalysis(params: { const streamGenerator = client.initiateSessionStream({ githubRepo, githubToken, + kilocodeOrganizationId: organizationId, prompt, mode: 'code', // Use code mode so agent can search files model, From 05ad553d228242e5e1f46b44a05bc1449b26f8f6 Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Sun, 8 Feb 2026 14:41:08 -0500 Subject: [PATCH 2/3] test(security-agent): align mocks with lint rules --- .../security-agent/services/analysis-service.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/security-agent/services/analysis-service.test.ts b/src/lib/security-agent/services/analysis-service.test.ts index 1efe927488..4925694943 100644 --- a/src/lib/security-agent/services/analysis-service.test.ts +++ b/src/lib/security-agent/services/analysis-service.test.ts @@ -4,6 +4,8 @@ import type * as securityAnalysisModule from '@/lib/security-agent/db/security-a import type * as triageModule from './triage-service'; import type * as tokensModule from '@/lib/tokens'; import type { StreamEvent } from '@/components/cloud-agent/types'; +import type { User } from '@/db/schema'; +import type { startSecurityAnalysis as startSecurityAnalysisType } from './analysis-service'; const mockGetSecurityFindingById = jest.fn() as jest.MockedFunction< typeof securityFindingsModule.getSecurityFindingById @@ -49,10 +51,10 @@ jest.mock('./extraction-service', () => ({ extractSandboxAnalysis: jest.fn(() => Promise.resolve()), })); -let startSecurityAnalysis: typeof import('./analysis-service').startSecurityAnalysis; +let startSecurityAnalysis: typeof startSecurityAnalysisType; -beforeAll(() => { - ({ startSecurityAnalysis } = require('./analysis-service')); +beforeAll(async () => { + ({ startSecurityAnalysis } = await import('./analysis-service')); }); describe('analysis-service', () => { @@ -63,7 +65,7 @@ describe('analysis-service', () => { it('passes organization id to cloud agent sandbox analysis', async () => { const organizationId = 'org-123'; const findingId = 'finding-123'; - const user = { id: 'user-1', google_user_email: 'test@example.com' }; + const user = { id: 'user-1', google_user_email: 'test@example.com' } as User; const mockFinding = { id: findingId, @@ -98,7 +100,7 @@ describe('analysis-service', () => { const result = await startSecurityAnalysis({ findingId, - user: user as any, + user, githubRepo: 'acme/repo', githubToken: 'gh-token', model: 'anthropic/claude-sonnet-4', From d5c581a232e26293a348152c967efbd79a4b3372 Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Mon, 9 Feb 2026 07:38:58 -0500 Subject: [PATCH 3/3] Update src/lib/security-agent/services/analysis-service.test.ts Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- src/lib/security-agent/services/analysis-service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/security-agent/services/analysis-service.test.ts b/src/lib/security-agent/services/analysis-service.test.ts index 4925694943..ccf801a505 100644 --- a/src/lib/security-agent/services/analysis-service.test.ts +++ b/src/lib/security-agent/services/analysis-service.test.ts @@ -63,7 +63,7 @@ describe('analysis-service', () => { }); it('passes organization id to cloud agent sandbox analysis', async () => { - const organizationId = 'org-123'; + const organizationId = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'; const findingId = 'finding-123'; const user = { id: 'user-1', google_user_email: 'test@example.com' } as User;