@@ -988,6 +988,69 @@ describe('Automatic Internal Route Detection', () => {
988988 Object . assign ( tools , originalTools )
989989 } )
990990
991+ it ( "should forward a tool's stripAuthOnRedirect to secureFetchWithPinnedIP" , async ( ) => {
992+ // Tools whose endpoint redirects to a signed third-party URL (GitHub's
993+ // Actions log download) must not have their API credential replayed to the
994+ // redirect target. This fetch path follows redirects itself rather than
995+ // through the fetch spec, so nothing strips the header without the flag.
996+ const mockTool = {
997+ id : 'test_redirecting_download' ,
998+ name : 'Test Redirecting Download Tool' ,
999+ description : 'A test tool whose endpoint redirects to another origin' ,
1000+ version : '1.0.0' ,
1001+ params : { } ,
1002+ request : {
1003+ url : 'https://api.example.com/download' ,
1004+ method : 'GET' ,
1005+ headers : ( ) => ( { Authorization : 'Bearer secret-token' } ) ,
1006+ stripAuthOnRedirect : true ,
1007+ } ,
1008+ transformResponse : vi . fn ( ) . mockResolvedValue ( { success : true , output : { } } ) ,
1009+ }
1010+
1011+ const originalTools = { ...tools }
1012+ ; ( tools as any ) . test_redirecting_download = mockTool
1013+
1014+ await executeTool ( 'test_redirecting_download' , { } )
1015+
1016+ expect ( mockSecureFetchWithPinnedIP ) . toHaveBeenCalledWith (
1017+ 'https://api.example.com/download' ,
1018+ '93.184.216.34' ,
1019+ expect . objectContaining ( { stripAuthOnRedirect : true } )
1020+ )
1021+
1022+ Object . assign ( tools , originalTools )
1023+ } )
1024+
1025+ it ( 'should leave stripAuthOnRedirect unset for tools that do not opt in' , async ( ) => {
1026+ const mockTool = {
1027+ id : 'test_plain_external' ,
1028+ name : 'Test Plain External Tool' ,
1029+ description : 'A test tool with no redirect handling' ,
1030+ version : '1.0.0' ,
1031+ params : { } ,
1032+ request : {
1033+ url : 'https://api.example.com/plain' ,
1034+ method : 'GET' ,
1035+ headers : ( ) => ( { Authorization : 'Bearer secret-token' } ) ,
1036+ } ,
1037+ transformResponse : vi . fn ( ) . mockResolvedValue ( { success : true , output : { } } ) ,
1038+ }
1039+
1040+ const originalTools = { ...tools }
1041+ ; ( tools as any ) . test_plain_external = mockTool
1042+
1043+ await executeTool ( 'test_plain_external' , { } )
1044+
1045+ expect ( mockSecureFetchWithPinnedIP ) . toHaveBeenCalledWith (
1046+ 'https://api.example.com/plain' ,
1047+ '93.184.216.34' ,
1048+ expect . objectContaining ( { stripAuthOnRedirect : undefined } )
1049+ )
1050+
1051+ Object . assign ( tools , originalTools )
1052+ } )
1053+
9911054 it ( 'should throw when the proxyUrl param fails validation' , async ( ) => {
9921055 inputValidationMockFns . mockValidateAndPinProxyUrl . mockResolvedValue ( {
9931056 isValid : false ,
0 commit comments