@@ -53,6 +53,14 @@ export interface HubChannel {
5353 meta_connection ?: HubMetaConnection | null ;
5454}
5555
56+ // SSRF guard: channel/webhook ids do hub são UUID (o hub faz uuid.Parse). O teste roda
57+ // INLINE em cada método que interpola o id no path da request — recusa path/URL injection
58+ // vinda de req.params/req.body em vez de repassá-la ao control-plane. Extrair o guard para
59+ // um método (`assertHubId`) protege igual em runtime, mas a análise de fluxo do CodeQL não
60+ // o reconhece como barreira através da fronteira de função e o js/request-forgery continua
61+ // acusando o sink; o teste inline é o que satisfaz scanner e runtime ao mesmo tempo.
62+ const HUB_ID = / ^ [ 0 - 9 a - f A - F ] { 8 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 12 } $ / ;
63+
5664// Webhook do hub (WebhookResponse — webhook.go:116). Só os campos que usamos.
5765export interface HubWebhookInfo {
5866 id : string ;
@@ -138,15 +146,6 @@ export class EvoHubClient {
138146 return this . normalizeChannelList ( data ) ;
139147 }
140148
141- // SSRF guard: channel/webhook ids do hub são UUID (o hub faz uuid.Parse). Validamos
142- // ANTES de interpolar o id no path da request ao hub — recusa path/URL injection
143- // vinda de req.params/req.body em vez de repassá-la para o control-plane.
144- private assertHubId ( id : string ) : void {
145- if ( ! / ^ [ 0 - 9 a - f A - F ] { 8 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 12 } $ / . test ( id ) ) {
146- throw new BadRequestException ( `invalid hub id: ${ id } ` ) ;
147- }
148- }
149-
150149 // Normaliza a resposta de lista do hub para HubChannel[] (channels|data|array nu).
151150 private normalizeChannelList ( data : any ) : HubChannel [ ] {
152151 if ( Array . isArray ( data ) ) return data ;
@@ -162,7 +161,7 @@ export class EvoHubClient {
162161 * server-side; o front NUNCA vê o token.
163162 */
164163 async getChannel ( id : string ) : Promise < HubChannel > {
165- this . assertHubId ( id ) ;
164+ if ( ! HUB_ID . test ( id ) ) throw new BadRequestException ( `invalid hub id: ${ id } ` ) ;
166165 const { data } = await this . http . get ( `/channels/${ id } ` ) ;
167166 return data ;
168167 }
@@ -179,7 +178,7 @@ export class EvoHubClient {
179178
180179 /** Webhooks já ASSOCIADOS ao canal: GET /api/v1/channels/:id/webhooks → { webhooks, count }. */
181180 async listChannelWebhooks ( channelId : string ) : Promise < HubWebhookInfo [ ] > {
182- this . assertHubId ( channelId ) ;
181+ if ( ! HUB_ID . test ( channelId ) ) throw new BadRequestException ( `invalid hub id: ${ channelId } ` ) ;
183182 const { data } = await this . http . get ( `/channels/${ channelId } /webhooks` ) ;
184183 return this . normalizeWebhookList ( data ) ;
185184 }
@@ -199,8 +198,8 @@ export class EvoHubClient {
199198
200199 /** POST /api/v1/webhooks/:id/associate — associa um webhook existente ao canal. */
201200 async associateWebhook ( webhookId : string , channelId : string ) : Promise < void > {
202- this . assertHubId ( webhookId ) ;
203- this . assertHubId ( channelId ) ;
201+ if ( ! HUB_ID . test ( webhookId ) ) throw new BadRequestException ( `invalid hub id: ${ webhookId } ` ) ;
202+ if ( ! HUB_ID . test ( channelId ) ) throw new BadRequestException ( `invalid hub id: ${ channelId } ` ) ;
204203 await this . http . post ( `/webhooks/${ webhookId } /associate` , { channel_id : channelId } ) ;
205204 }
206205
@@ -210,13 +209,13 @@ export class EvoHubClient {
210209 * webhook do estado auto-`disabled` (webhook.go:104).
211210 */
212211 async setWebhookStatus ( webhookId : string , status : 'active' | 'inactive' ) : Promise < void > {
213- this . assertHubId ( webhookId ) ;
212+ if ( ! HUB_ID . test ( webhookId ) ) throw new BadRequestException ( `invalid hub id: ${ webhookId } ` ) ;
214213 await this . http . put ( `/webhooks/${ webhookId } /status` , { status } ) ;
215214 }
216215
217216 /** PUT /api/v1/webhooks/:id/secret — grava o secret usado para assinar o inbound. */
218217 async setWebhookSecret ( webhookId : string , secret : string ) : Promise < void > {
219- this . assertHubId ( webhookId ) ;
218+ if ( ! HUB_ID . test ( webhookId ) ) throw new BadRequestException ( `invalid hub id: ${ webhookId } ` ) ;
220219 await this . http . put ( `/webhooks/${ webhookId } /secret` , { secret } ) ;
221220 }
222221
@@ -258,7 +257,7 @@ export class EvoHubClient {
258257 * register-with-own-secret, igual ao provision.
259258 */
260259 async ensureChannelWebhook ( channelId : string , webhookUrl : string ) : Promise < void > {
261- this . assertHubId ( channelId ) ;
260+ if ( ! HUB_ID . test ( channelId ) ) throw new BadRequestException ( `invalid hub id: ${ channelId } ` ) ;
262261
263262 const associated = await this . listChannelWebhooks ( channelId ) ;
264263 const match = associated . find ( ( w ) => w . url === webhookUrl ) ;
@@ -336,7 +335,7 @@ export class EvoHubClient {
336335 * Evolution; 'byo' exige channel_credentials no hub.
337336 */
338337 async connectToMeta ( channelId : string , req : MetaConnectRequest ) : Promise < MetaConnectResponse > {
339- this . assertHubId ( channelId ) ;
338+ if ( ! HUB_ID . test ( channelId ) ) throw new BadRequestException ( `invalid hub id: ${ channelId } ` ) ;
340339 const { data } = await this . http . post ( `/channels/${ channelId } /meta-connect` , req ) ;
341340 return data ;
342341 }
0 commit comments