@@ -394,6 +394,54 @@ describe('RunTurnUseCase.handleTurn', () => {
394394 await expect ( useCase . handleTurn ( input as any ) ) . rejects . not . toThrow ( 'No pending approval' ) ;
395395 } ) ;
396396
397+ it ( 'rejects a normal turn on a session the user does not own, before creating a turn' , async ( ) => {
398+ const { useCase, sessions, llm } = buildUseCase ( {
399+ sessionOwnerPk : 'someone-else' ,
400+ streamFor : ( ) => streamOf ( text ( 'secret' ) ) ,
401+ } ) ;
402+ const { input } = makeInput ( ) ;
403+
404+ await expect ( useCase . handleTurn ( input as any ) ) . rejects . toThrow ( / d o e s n o t b e l o n g / ) ;
405+ // Nothing about the victim's session may be touched or streamed back.
406+ expect ( sessions . calls . createNewTurn ) . toHaveLength ( 0 ) ;
407+ expect ( llm . calls ) . toHaveLength ( 0 ) ;
408+ } ) ;
409+
410+ it ( 'rejects a turn on a session that does not exist' , async ( ) => {
411+ const { useCase } = buildUseCase ( { sessionOwnerPk : null } ) ;
412+ const { input } = makeInput ( ) ;
413+
414+ await expect ( useCase . handleTurn ( input as any ) ) . rejects . toThrow ( / n o t f o u n d / ) ;
415+ } ) ;
416+
417+ it ( 'rejects an approval resume on a session the user does not own, before resolving interrupts' , async ( ) => {
418+ const { useCase, llm } = buildUseCase ( {
419+ sessionOwnerPk : 'someone-else' ,
420+ hasPersistentCheckpointer : true ,
421+ pendingInterrupts : [ { id : 'int-1' , count : 1 } ] ,
422+ } ) ;
423+ const { input } = makeInput ( { prompt : '' , approvalDecision : 'approve' } ) ;
424+
425+ // An attacker must not be able to approve a dangerous tool call pending in
426+ // someone else's session.
427+ await expect ( useCase . handleTurn ( input as any ) ) . rejects . toThrow ( / d o e s n o t b e l o n g / ) ;
428+ expect ( llm . getPendingInterruptsCalls ) . toHaveLength ( 0 ) ;
429+ expect ( llm . calls ) . toHaveLength ( 0 ) ;
430+ } ) ;
431+
432+ it ( 'allows a chat-surface turn whose session is shared with other linked users' , async ( ) => {
433+ const { useCase, llm } = buildUseCase ( {
434+ sessionOwnerPk : 'someone-else' ,
435+ streamFor : ( ) => streamOf ( text ( 'ok' ) ) ,
436+ } ) ;
437+ const { input } = makeInput ( { chatSurface : 'telegram' } ) ;
438+
439+ const result = await useCase . handleTurn ( input as any ) ;
440+
441+ expect ( result . text ) . toBe ( 'ok' ) ;
442+ expect ( llm . calls ) . toHaveLength ( 1 ) ;
443+ } ) ;
444+
397445 it ( 'rejects (does not swallow) an approval with no pending interrupt, after emitting turn-started' , async ( ) => {
398446 const { useCase } = buildUseCase ( ) ;
399447 const { input, events } = makeInput ( { prompt : '' , approvalDecision : 'approve' } ) ;
0 commit comments