@@ -15,6 +15,7 @@ const mocks = vi.hoisted(() => ({
1515 getActor : vi . fn ( ) ,
1616 deleteRecord : vi . fn ( ) ,
1717 capture : vi . fn ( ) ,
18+ environment : vi . fn ( ) ,
1819} ) )
1920
2021vi . mock ( '@sim/audit' , ( ) => auditMock )
@@ -47,11 +48,13 @@ vi.mock('@/lib/credentials/access', () => ({
4748 } ,
4849} ) )
4950vi . mock ( '@/lib/posthog/server' , ( ) => ( { captureServerEvent : mocks . capture } ) )
51+ vi . mock ( '@/lib/environment/utils' , ( ) => ( { getEffectiveDecryptedEnv : mocks . environment } ) )
5052
5153import {
5254 createServiceAccountCredentialUseCase ,
5355 deleteCredentialUseCase ,
5456} from '@/lib/credentials/application/service-account'
57+ import { executeCopilotCredentialUseCase } from '@/lib/mothership/application/execute-credential-use-case'
5558
5659const WORKSPACE_ID = 'workspace-1'
5760const workspace = {
@@ -106,6 +109,7 @@ describe('credential service-account application operations', () => {
106109 providerId : 'zoom-service-account' ,
107110 available : true ,
108111 } )
112+ mocks . environment . mockResolvedValue ( { SIGNING : 'secret-signing' , BOT : 'secret-bot' } )
109113 } )
110114
111115 it ( 'rejects workspace keys before canonical loading on create' , async ( ) => {
@@ -158,6 +162,111 @@ describe('credential service-account application operations', () => {
158162 )
159163 } )
160164
165+ const storedInput = {
166+ workspaceId : WORKSPACE_ID ,
167+ displayName : 'Support bot' ,
168+ storedSlackSecrets : { signingSecretEnvVar : 'SIGNING' , botTokenEnvVar : 'BOT' } ,
169+ }
170+ const copilotContext = {
171+ userId : 'user-1' ,
172+ workspaceId : WORKSPACE_ID ,
173+ toolCallId : 'tool-1' ,
174+ copilotToolExecution : true ,
175+ }
176+ const connectStored = ( context = copilotContext , input = storedInput ) =>
177+ executeCopilotCredentialUseCase ( context , createServiceAccountCredentialUseCase , input )
178+
179+ it ( 'resolves stored Slack secrets as the delegated user and audits only credential metadata' , async ( ) => {
180+ await connectStored ( )
181+ expect ( mocks . environment ) . toHaveBeenCalledWith ( 'user-1' , WORKSPACE_ID )
182+ expect ( mocks . create ) . toHaveBeenCalledWith (
183+ expect . objectContaining ( {
184+ workspaceId : WORKSPACE_ID ,
185+ userId : 'user-1' ,
186+ providerId : 'slack-custom-bot' ,
187+ signingSecret : 'secret-signing' ,
188+ botToken : 'secret-bot' ,
189+ displayName : 'Support bot' ,
190+ } )
191+ )
192+ expect ( mocks . create . mock . calls [ 0 ] [ 0 ] ) . not . toHaveProperty ( 'storedSlackSecrets' )
193+ expect ( auditMockFns . mockRecordAudit ) . toHaveBeenCalledWith (
194+ expect . objectContaining ( {
195+ actorId : 'user-1' ,
196+ metadata : expect . objectContaining ( {
197+ operation : 'credentials.service_accounts.create' ,
198+ actor : expect . objectContaining ( {
199+ kind : 'delegated' ,
200+ serviceId : 'copilot' ,
201+ subjectUserId : 'user-1' ,
202+ } ) ,
203+ } ) ,
204+ } )
205+ )
206+ expect ( JSON . stringify ( auditMockFns . mockRecordAudit . mock . calls ) ) . not . toContain ( 'secret-signing' )
207+ expect ( JSON . stringify ( mocks . capture . mock . calls ) ) . not . toContain ( 'secret-bot' )
208+ } )
209+
210+ it ( 'rejects forged execution context before canonical loading' , async ( ) => {
211+ await expect ( async ( ) =>
212+ connectStored ( { ...copilotContext , copilotToolExecution : false } )
213+ ) . rejects . toThrow ( 'trusted' )
214+ expect ( mocks . loadWorkspace ) . not . toHaveBeenCalled ( )
215+ expect ( mocks . environment ) . not . toHaveBeenCalled ( )
216+ } )
217+
218+ it ( 'rejects cross-workspace delegation before resolving stored secrets' , async ( ) => {
219+ mocks . loadWorkspace . mockResolvedValueOnce ( { ...workspace , workspaceId : 'other' } )
220+ await expect (
221+ connectStored ( copilotContext , { ...storedInput , workspaceId : 'other' } )
222+ ) . rejects . toMatchObject ( { code : 'forbidden' } )
223+ expect ( mocks . environment ) . not . toHaveBeenCalled ( )
224+ expect ( mocks . create ) . not . toHaveBeenCalled ( )
225+ } )
226+
227+ it ( 'checks current write permission before resolving stored secrets' , async ( ) => {
228+ mocks . resolvePermission . mockResolvedValue ( 'read' )
229+ await expect ( connectStored ( ) ) . rejects . toMatchObject ( { code : 'forbidden' } )
230+ expect ( mocks . environment ) . not . toHaveBeenCalled ( )
231+ expect ( mocks . create ) . not . toHaveBeenCalled ( )
232+ } )
233+
234+ it ( 'checks provider visibility before resolving stored secrets' , async ( ) => {
235+ mocks . requireProvider . mockImplementationOnce ( ( ) => {
236+ throw new OrchestrationError ( 'conflict' , 'Provider hidden' )
237+ } )
238+ await expect ( connectStored ( ) ) . rejects . toMatchObject ( { code : 'conflict' } )
239+ expect ( mocks . environment ) . not . toHaveBeenCalled ( )
240+ expect ( mocks . create ) . not . toHaveBeenCalled ( )
241+ } )
242+
243+ it ( 'reports missing names without sending any partial credential to the provider' , async ( ) => {
244+ mocks . environment . mockResolvedValue ( { SIGNING : 'secret-signing' } )
245+ await expect ( connectStored ( ) ) . rejects . toThrow ( 'Stored secrets unavailable: BOT' )
246+ expect ( mocks . create ) . not . toHaveBeenCalled ( )
247+ expect ( auditMockFns . mockRecordAudit ) . not . toHaveBeenCalled ( )
248+ } )
249+
250+ it ( 'does not let delegated callers supply inline credential values' , async ( ) => {
251+ await expect (
252+ executeCopilotCredentialUseCase ( copilotContext , createServiceAccountCredentialUseCase , {
253+ workspaceId : WORKSPACE_ID ,
254+ providerId : 'slack-custom-bot' ,
255+ signingSecret : 'raw-signing' ,
256+ botToken : 'raw-token' ,
257+ } )
258+ ) . rejects . toThrow ( 'must reference existing Sim secrets' )
259+ expect ( mocks . environment ) . not . toHaveBeenCalled ( )
260+ expect ( mocks . create ) . not . toHaveBeenCalled ( )
261+ } )
262+
263+ it ( 'does not duplicate creation audit or analytics when the primitive reuses a credential' , async ( ) => {
264+ mocks . create . mockResolvedValue ( { success : true , credential, created : false } )
265+ expect ( ( await connectStored ( ) ) . created ) . toBe ( false )
266+ expect ( auditMockFns . mockRecordAudit ) . not . toHaveBeenCalled ( )
267+ expect ( mocks . capture ) . not . toHaveBeenCalled ( )
268+ } )
269+
161270 it ( 'rejects service-account providers hidden by workspace policy' , async ( ) => {
162271 mocks . requireProvider . mockImplementation ( ( ) => {
163272 throw new OrchestrationError (
0 commit comments