File tree Expand file tree Collapse file tree
extensions/voice-call/src/providers Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -8,10 +8,24 @@ const TWILIO_API_HOSTNAME_BY_REGION = {
88 au1 : "api.sydney.au1.twilio.com" ,
99} satisfies Record < TwilioRegion , string > ;
1010
11+ const TWILIO_API_HOSTNAMES = new Set ( Object . values ( TWILIO_API_HOSTNAME_BY_REGION ) ) ;
12+
13+ export function resolveTwilioApiHostname ( region ?: TwilioRegion ) : string {
14+ return TWILIO_API_HOSTNAME_BY_REGION [ region ?? "us1" ] ;
15+ }
16+
1117export function resolveTwilioApiBaseUrl ( params : {
1218 accountSid : string ;
1319 region ?: TwilioRegion ;
1420} ) : string {
15- const hostname = TWILIO_API_HOSTNAME_BY_REGION [ params . region ?? "us1" ] ;
21+ const hostname = resolveTwilioApiHostname ( params . region ) ;
1622 return `https://${ hostname } /2010-04-01/Accounts/${ params . accountSid } ` ;
1723}
24+
25+ export function requireSupportedTwilioApiHostname ( baseUrl : string ) : string {
26+ const hostname = new URL ( baseUrl ) . hostname ;
27+ if ( ! TWILIO_API_HOSTNAMES . has ( hostname ) ) {
28+ throw new Error ( `Unsupported Twilio API hostname: ${ hostname } ` ) ;
29+ }
30+ return hostname ;
31+ }
Original file line number Diff line number Diff line change @@ -126,6 +126,19 @@ describe("twilioApiRequest", () => {
126126 expect ( release ) . toHaveBeenCalledTimes ( 1 ) ;
127127 } ) ;
128128
129+ it ( "rejects unsupported API hosts before the SSRF guard" , async ( ) => {
130+ await expect (
131+ twilioApiRequest ( {
132+ baseUrl : "https://metadata.google.internal/2010-04-01/Accounts/AC123" ,
133+ accountSid : "AC123" ,
134+ authToken : "secret" ,
135+ endpoint : "/Calls.json" ,
136+ body : { } ,
137+ } ) ,
138+ ) . rejects . toThrow ( "Unsupported Twilio API hostname: metadata.google.internal" ) ;
139+ expect ( fetchWithSsrFGuardMock ) . not . toHaveBeenCalled ( ) ;
140+ } ) ;
141+
129142 it ( "maps AU1 to Twilio's Sydney regional hostname" , ( ) => {
130143 expect ( resolveTwilioApiBaseUrl ( { accountSid : "AC123" , region : "au1" } ) ) . toBe (
131144 "https://api.sydney.au1.twilio.com/2010-04-01/Accounts/AC123" ,
Original file line number Diff line number Diff line change 55 readProviderErrorResponseSnippet ,
66 readProviderJsonResponseText ,
77} from "../shared/response-body.js" ;
8+ import { requireSupportedTwilioApiHostname } from "../twilio-region.js" ;
89
910// Guarded Twilio REST API client helpers.
1011
@@ -74,6 +75,7 @@ export async function twilioApiRequest<T = unknown>(params: {
7475 } , new URLSearchParams ( ) ) ;
7576
7677 const requestUrl = `${ params . baseUrl } ${ params . endpoint } ` ;
78+ const allowedHostname = requireSupportedTwilioApiHostname ( params . baseUrl ) ;
7779 const { response, release } = await fetchWithSsrFGuard ( {
7880 url : requestUrl ,
7981 init : {
@@ -84,7 +86,7 @@ export async function twilioApiRequest<T = unknown>(params: {
8486 } ,
8587 body : bodyParams ,
8688 } ,
87- policy : { allowedHostnames : [ new URL ( requestUrl ) . hostname ] } ,
89+ policy : { allowedHostnames : [ allowedHostname ] } ,
8890 timeoutMs : TWILIO_API_TIMEOUT_MS ,
8991 auditContext : "voice-call.twilio.api" ,
9092 } ) ;
You can’t perform that action at this time.
0 commit comments