@@ -583,26 +583,37 @@ export function createAgentMCPTools(
583583 } ,
584584 ) ;
585585
586- if ( tokenRes . ok ) {
587- const tokenData = ( await tokenRes . json ( ) ) as {
588- access_token : string ;
586+ const resText = await tokenRes . text ( ) ;
587+ let resJson : Record < string , unknown > ;
588+ try {
589+ resJson = JSON . parse ( resText ) ;
590+ } catch {
591+ if ( storage . removePendingFlow ) await storage . removePendingFlow ( url ) ;
592+ return {
593+ content : [
594+ {
595+ type : "text" as const ,
596+ text : `Device auth returned non-JSON response: ${ resText . slice ( 0 , 200 ) } ` ,
597+ } ,
598+ ] ,
589599 } ;
590- accessToken = tokenData . access_token ;
600+ }
601+
602+ if ( tokenRes . ok ) {
603+ accessToken = resJson . access_token as string ;
591604 break ;
592605 }
593606
594- const errorData = ( await tokenRes . json ( ) ) as {
595- error : string ;
596- } ;
607+ const error = resJson . error as string | undefined ;
597608
598- if ( errorData . error === "authorization_pending" ) {
609+ if ( error === "authorization_pending" ) {
599610 continue ;
600611 }
601- if ( errorData . error === "slow_down" ) {
612+ if ( error === "slow_down" ) {
602613 await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) ) ;
603614 continue ;
604615 }
605- if ( errorData . error === "access_denied" ) {
616+ if ( error === "access_denied" ) {
606617 if ( storage . removePendingFlow ) await storage . removePendingFlow ( url ) ;
607618 return {
608619 content : [
@@ -613,7 +624,7 @@ export function createAgentMCPTools(
613624 ] ,
614625 } ;
615626 }
616- if ( errorData . error === "expired_token" ) {
627+ if ( error === "expired_token" ) {
617628 if ( storage . removePendingFlow ) await storage . removePendingFlow ( url ) ;
618629 return {
619630 content : [
@@ -630,7 +641,7 @@ export function createAgentMCPTools(
630641 content : [
631642 {
632643 type : "text" as const ,
633- text : `Device auth failed: ${ errorData . error } ` ,
644+ text : `Device auth failed: ${ error } ` ,
634645 } ,
635646 ] ,
636647 } ;
@@ -875,15 +886,40 @@ export function createAgentMCPTools(
875886 } ;
876887 }
877888
889+ const fullUrl = reqPath . startsWith ( "http" )
890+ ? reqPath
891+ : `${ connection . appUrl } ${ reqPath } ` ;
892+
893+ // Prevent SSRF: only allow requests to the app the agent connected to
894+ try {
895+ const target = new URL ( fullUrl ) ;
896+ const allowed = new URL ( connection . appUrl ) ;
897+ if ( target . origin !== allowed . origin ) {
898+ return {
899+ content : [
900+ {
901+ type : "text" as const ,
902+ text : `Blocked: request origin ${ target . origin } does not match connected app ${ allowed . origin } .` ,
903+ } ,
904+ ] ,
905+ } ;
906+ }
907+ } catch {
908+ return {
909+ content : [
910+ {
911+ type : "text" as const ,
912+ text : `Invalid URL: ${ fullUrl } ` ,
913+ } ,
914+ ] ,
915+ } ;
916+ }
917+
878918 const jwt = await signAgentJWT ( {
879919 agentId,
880920 privateKey : connection . keypair . privateKey ,
881921 } ) ;
882922
883- const fullUrl = reqPath . startsWith ( "http" )
884- ? reqPath
885- : `${ connection . appUrl } ${ reqPath } ` ;
886-
887923 const headers : Record < string , string > = {
888924 Authorization : `Bearer ${ jwt } ` ,
889925 } ;
@@ -960,29 +996,40 @@ export function createAgentMCPTools(
960996 } ,
961997 ) ;
962998
963- if ( tokenRes . ok ) {
964- const tokenData = ( await tokenRes . json ( ) ) as {
965- access_token : string ;
999+ const resText = await tokenRes . text ( ) ;
1000+ let resJson : Record < string , unknown > ;
1001+ try {
1002+ resJson = JSON . parse ( resText ) ;
1003+ } catch {
1004+ if ( storage . removePendingFlow ) await storage . removePendingFlow ( url ) ;
1005+ return {
1006+ content : [
1007+ {
1008+ type : "text" as const ,
1009+ text : `Device auth returned non-JSON response: ${ resText . slice ( 0 , 200 ) } ` ,
1010+ } ,
1011+ ] ,
9661012 } ;
967- accessToken = tokenData . access_token ;
1013+ }
1014+
1015+ if ( tokenRes . ok ) {
1016+ accessToken = resJson . access_token as string ;
9681017 break ;
9691018 }
9701019
971- const errorData = ( await tokenRes . json ( ) ) as {
972- error : string ;
973- } ;
1020+ const error = resJson . error as string | undefined ;
9741021
975- if ( errorData . error === "authorization_pending" ) {
1022+ if ( error === "authorization_pending" ) {
9761023 await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) ) ;
9771024 continue ;
9781025 }
979- if ( errorData . error === "slow_down" ) {
1026+ if ( error === "slow_down" ) {
9801027 await new Promise ( ( resolve ) =>
9811028 setTimeout ( resolve , pollInterval * 2 ) ,
9821029 ) ;
9831030 continue ;
9841031 }
985- if ( errorData . error === "access_denied" ) {
1032+ if ( error === "access_denied" ) {
9861033 if ( storage . removePendingFlow ) await storage . removePendingFlow ( url ) ;
9871034 return {
9881035 content : [
@@ -993,7 +1040,7 @@ export function createAgentMCPTools(
9931040 ] ,
9941041 } ;
9951042 }
996- if ( errorData . error === "expired_token" ) {
1043+ if ( error === "expired_token" ) {
9971044 if ( storage . removePendingFlow ) await storage . removePendingFlow ( url ) ;
9981045 return {
9991046 content : [
@@ -1010,7 +1057,7 @@ export function createAgentMCPTools(
10101057 content : [
10111058 {
10121059 type : "text" as const ,
1013- text : `Device auth failed: ${ errorData . error } ` ,
1060+ text : `Device auth failed: ${ error } ` ,
10141061 } ,
10151062 ] ,
10161063 } ;
0 commit comments