From 95530cc7b7a20df41288670a7ccc933539e3f5e6 Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Fri, 16 Jan 2026 02:40:45 +0530 Subject: [PATCH 1/2] fix(api): escape json body in api calls to prevent injection --- packages/api/src/EmbeddedChatApi.ts | 86 ++++++++++++++++------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index f55f55d58..6e09095c6 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -73,21 +73,21 @@ export default class EmbeddedChatApi { const payload = acsCode ? JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - totp: { - code: acsPayload, - }, - }) + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + totp: { + code: acsPayload, + }, + }) : JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - scope: "profile", - }); + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + scope: "profile", + }); try { const req = await fetch(`${this.host}/api/v1/login`, { @@ -363,7 +363,7 @@ export default class EmbeddedChatApi { typingHandlerLock = 0; }, 2000); // eslint-disable-next-line no-empty - while (typingHandlerLock) {} + while (typingHandlerLock) { } typingHandlerLock = 1; // move user to front if typing else remove it. const idx = this.typingUsers.indexOf(typingUser); @@ -414,7 +414,10 @@ export default class EmbeddedChatApi { if (suggestedUsername.success) { const response2 = await fetch(`${this.host}/api/v1/users.update`, { - body: `{"userId": "${userid}", "data": { "username": "${suggestedUsername.result}" }}`, + body: JSON.stringify({ + userId: userid, + data: { username: suggestedUsername.result }, + }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -439,7 +442,10 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/users.update`, { - body: `{"userId": "${userid}", "data": { "username": "${newUserName}" }}`, + body: JSON.stringify({ + userId: userid, + data: { username: newUserName }, + }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -555,9 +561,9 @@ export default class EmbeddedChatApi { query?: object | undefined; field?: object | undefined; } = { - query: undefined, - field: undefined, - }, + query: undefined, + field: undefined, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -594,10 +600,10 @@ export default class EmbeddedChatApi { field?: object | undefined; offset?: number; } = { - query: undefined, - field: undefined, - offset: 50, - }, + query: undefined, + field: undefined, + offset: 50, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -745,13 +751,13 @@ export default class EmbeddedChatApi { const messageObj = typeof message === "string" ? { - rid: this.rid, - msg: message, - } + rid: this.rid, + msg: message, + } : { - ...message, - rid: this.rid, - }; + ...message, + rid: this.rid, + }; if (threadId) { messageObj.tmid = threadId; } @@ -776,7 +782,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.delete`, { - body: `{"roomId": "${this.rid}", "msgId": "${msgId}"}`, + body: JSON.stringify({ roomId: this.rid, msgId }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -794,7 +800,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.update`, { - body: `{"roomId": "${this.rid}", "msgId": "${msgId}","text" : "${text}" }`, + body: JSON.stringify({ roomId: this.rid, msgId, text }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -854,7 +860,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.starMessage`, { - body: `{"messageId": "${mid}"}`, + body: JSON.stringify({ messageId: mid }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -872,7 +878,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.unStarMessage`, { - body: `{"messageId": "${mid}"}`, + body: JSON.stringify({ messageId: mid }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -950,7 +956,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.pinMessage`, { - body: `{"messageId": "${mid}"}`, + body: JSON.stringify({ messageId: mid }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -970,7 +976,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.unPinMessage`, { - body: `{"messageId": "${mid}"}`, + body: JSON.stringify({ messageId: mid }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -988,7 +994,11 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.react`, { - body: `{"messageId": "${messageId}", "emoji": "${emoji}", "shouldReact": ${shouldReact}}`, + body: JSON.stringify({ + messageId, + emoji, + shouldReact, + }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, @@ -1006,7 +1016,7 @@ export default class EmbeddedChatApi { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; const response = await fetch(`${this.host}/api/v1/chat.reportMessage`, { - body: `{"messageId": "${messageId}", "description": "${description}"}`, + body: JSON.stringify({ messageId, description }), headers: { "Content-Type": "application/json", "X-Auth-Token": authToken, From ce480d00b0fcf7f703838ae9989debdf90a95b47 Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Fri, 16 Jan 2026 19:34:39 +0530 Subject: [PATCH 2/2] fix(api): replace manual JSON string construction with JSON.stringify to prevent injection - Replaced template literal JSON construction with JSON.stringify() in all API methods - Fixes vulnerability where user input containing quotes would break JSON structure - Affected methods: updateMessage, deleteMessage, updateUserUsername, reactToMessage, reportMessage, starMessage, unstarMessage, pinMessage, unpinMessage, updateUserNameThroughSuggestion --- packages/api/src/EmbeddedChatApi.ts | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index 6e09095c6..72e25a046 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -73,21 +73,21 @@ export default class EmbeddedChatApi { const payload = acsCode ? JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - totp: { - code: acsPayload, - }, - }) + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + totp: { + code: acsPayload, + }, + }) : JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - scope: "profile", - }); + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + scope: "profile", + }); try { const req = await fetch(`${this.host}/api/v1/login`, { @@ -363,7 +363,7 @@ export default class EmbeddedChatApi { typingHandlerLock = 0; }, 2000); // eslint-disable-next-line no-empty - while (typingHandlerLock) { } + while (typingHandlerLock) {} typingHandlerLock = 1; // move user to front if typing else remove it. const idx = this.typingUsers.indexOf(typingUser); @@ -561,9 +561,9 @@ export default class EmbeddedChatApi { query?: object | undefined; field?: object | undefined; } = { - query: undefined, - field: undefined, - }, + query: undefined, + field: undefined, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -600,10 +600,10 @@ export default class EmbeddedChatApi { field?: object | undefined; offset?: number; } = { - query: undefined, - field: undefined, - offset: 50, - }, + query: undefined, + field: undefined, + offset: 50, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -751,13 +751,13 @@ export default class EmbeddedChatApi { const messageObj = typeof message === "string" ? { - rid: this.rid, - msg: message, - } + rid: this.rid, + msg: message, + } : { - ...message, - rid: this.rid, - }; + ...message, + rid: this.rid, + }; if (threadId) { messageObj.tmid = threadId; }