@@ -14,6 +14,7 @@ import {
1414 TextMapSetter ,
1515 INVALID_SPAN_CONTEXT ,
1616 SpanStatusCode ,
17+ ROOT_CONTEXT ,
1718} from '@opentelemetry/api' ;
1819import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' ;
1920import { Instrumentation } from '@opentelemetry/instrumentation' ;
@@ -692,6 +693,60 @@ describe('InstrumentationPatchTest', () => {
692693 } ) ;
693694 } ) ;
694695
696+ it ( 'Prevents recursion when credentials provider makes STS calls' , async ( ) => {
697+ // Track credentials provider calls and skip flag state
698+ let credentialsProviderCallCount = 0 ;
699+ let skipCredentialCaptureValue = false ;
700+
701+ // Mock span for attribute capture
702+ const mockSpan = { setAttribute : sinon . stub ( ) } ;
703+ sinon . stub ( trace , 'getSpan' ) . returns ( mockSpan as unknown as Span ) ;
704+
705+ // Patch ROOT_CONTEXT to capture setValue calls
706+ const originalSetValue = ROOT_CONTEXT . setValue ;
707+ ROOT_CONTEXT . setValue = ( key : symbol , value : any ) => {
708+ if ( key . toString ( ) . includes ( 'skip-credential-capture' ) ) {
709+ skipCredentialCaptureValue = value ;
710+ }
711+ return ROOT_CONTEXT ;
712+ } ;
713+ sinon . stub ( context , 'active' ) . returns ( ROOT_CONTEXT ) ;
714+
715+ // Capture middleware added to stack
716+ const middlewareStack : any [ ] = [ ] ;
717+ const recursiveSdkSend = extractAwsSdkInstrumentation ( PATCHED_INSTRUMENTATIONS )
718+ [ '_getV3SmithyClientSendPatch' ] ( ( ) => Promise . resolve ( ) )
719+ . bind ( {
720+ middlewareStack : { add : ( middleware : any , config : any ) => middlewareStack . push ( [ middleware , config ] ) } ,
721+ config : {
722+ // Credentials provider that recursively calls SDK (simulates STS)
723+ credentials : async ( ) => {
724+ credentialsProviderCallCount ++ ;
725+ await recursiveSdkSend ( { } , null ) ;
726+ return { accessKeyId : 'test-access-key' } ;
727+ } ,
728+ region : ( ) => Promise . resolve ( 'us-west-2' ) ,
729+ } ,
730+ } ) ;
731+
732+ // Initial SDK call triggers middleware setup
733+ await recursiveSdkSend ( { } , null ) ;
734+
735+ // Execute credentials extraction middleware
736+ await middlewareStack [ 1 ] [ 0 ] ( ( ) => Promise . resolve ( ) , null ) ( { } ) ;
737+
738+ // Verify recursion prevention: only one credentials call despite recursive setup
739+ expect ( credentialsProviderCallCount ) . toBe ( 1 ) ;
740+ expect ( skipCredentialCaptureValue ) . toBe ( true ) ;
741+ expect (
742+ mockSpan . setAttribute . calledWith ( AWS_ATTRIBUTE_KEYS . AWS_AUTH_ACCOUNT_ACCESS_KEY , 'test-access-key' )
743+ ) . toBeTruthy ( ) ;
744+
745+ // Restore original setValue
746+ ROOT_CONTEXT . setValue = originalSetValue ;
747+ sinon . restore ( ) ;
748+ } ) ;
749+
695750 it ( 'injects trace context header into request via propagator' , async ( ) => {
696751 lambda = new Lambda ( {
697752 region : region ,
0 commit comments