🛡️ fix: Deep Clone MCPOptions for User MCP Connections (#7247)

* Fix: Prevent side effects in `processMCPEnv` by deep cloning MCPOptions

The `processMCPEnv` function was modifying the original `MCPOptions` object, leading to unintended side effects where `LIBRECHAT_USER_ID` could be incorrectly shared across different users. This commit addresses this issue by performing a deep clone of the `MCPOptions` object before processing, ensuring that modifications are isolated and do not affect other users.

* ci: Add tests for processMCPEnv to ensure deep cloning, user ID isolation and environment variable processing

---------

Co-authored-by: Alex C <viennadd@users.noreply.github.com>
This commit is contained in:
Danny Avila 2025-05-06 10:29:05 -04:00 committed by GitHub
parent 7c92cef2b7
commit 8e1012c5aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 136 additions and 9 deletions

View file

@ -96,28 +96,30 @@ export type MCPOptions = z.infer<typeof MCPOptionsSchema>;
* @param {string} [userId] - The user ID
* @returns {MCPOptions} - The processed object with environment variables replaced
*/
export function processMCPEnv(obj: MCPOptions, userId?: string): MCPOptions {
export function processMCPEnv(obj: Readonly<MCPOptions>, userId?: string): MCPOptions {
if (obj === null || obj === undefined) {
return obj;
}
if ('env' in obj && obj.env) {
const newObj: MCPOptions = structuredClone(obj);
if ('env' in newObj && newObj.env) {
const processedEnv: Record<string, string> = {};
for (const [key, value] of Object.entries(obj.env)) {
for (const [key, value] of Object.entries(newObj.env)) {
processedEnv[key] = extractEnvVariable(value);
}
obj.env = processedEnv;
} else if ('headers' in obj && obj.headers) {
newObj.env = processedEnv;
} else if ('headers' in newObj && newObj.headers) {
const processedHeaders: Record<string, string> = {};
for (const [key, value] of Object.entries(obj.headers)) {
for (const [key, value] of Object.entries(newObj.headers)) {
if (value === '{{LIBRECHAT_USER_ID}}' && userId != null && userId) {
processedHeaders[key] = userId;
continue;
}
processedHeaders[key] = extractEnvVariable(value);
}
obj.headers = processedHeaders;
newObj.headers = processedHeaders;
}
return obj;
return newObj;
}