From a9a7f5f960be7c9ee88efb897ace17f2101ff723 Mon Sep 17 00:00:00 2001 From: Marcelo Date: Fri, 6 Nov 2026 15:57:13 -0300 Subject: [PATCH 1/3] fix(security): bind every request to the authenticated instance Backport of #2549 (7a55a2b on develop) onto 2.3.7: an instance's API key could act on other instances of the same installation. --- src/api/abstract/abstract.router.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/api/abstract/abstract.router.ts b/src/api/abstract/abstract.router.ts index e8449a8c8a..2a64296ded 100644 --- a/src/api/abstract/abstract.router.ts +++ b/src/api/abstract/abstract.router.ts @@ -17,6 +17,21 @@ type DataValidate = { const logger = new Logger('Validate'); +const PROTECTED_INSTANCE_FIELDS = ['instanceName', 'instanceId'] as const; + +function sanitizeUntrustedInput(source: Record | undefined): Record { + if (!source || typeof source !== 'object') return {}; + const sanitized: Record = {}; + for (const [key, value] of Object.entries(source)) { + if ((PROTECTED_INSTANCE_FIELDS as readonly string[]).includes(key)) { + logger.warn(`Ignoring attempt to override protected field "${key}" via untrusted input`); + continue; + } + sanitized[key] = value; + } + return sanitized; +} + export abstract class RouterBroker { constructor() {} public routerPath(path: string, param = true) { @@ -34,11 +49,11 @@ export abstract class RouterBroker { const instance = request.params as unknown as InstanceDto; if (request?.query && Object.keys(request.query).length > 0) { - Object.assign(instance, request.query); + Object.assign(instance, sanitizeUntrustedInput(request.query as Record)); } if (request.originalUrl.includes('/instance/create')) { - Object.assign(instance, body); + Object.assign(instance, sanitizeUntrustedInput(body)); } Object.assign(ref, body); From 9ea7c0ef21844a9b16ef7a413f2e3253809d5fcc Mon Sep 17 00:00:00 2001 From: Marcelo Date: Fri, 6 Nov 2026 15:57:32 -0300 Subject: [PATCH 2/3] fix(security): keep the body name on instance create The backport also dropped the name from the body of /instance/create, the only place a new instance's name comes from, so create answered 400 and stored nothing. Create only passes the guard with the global key, so its body is trusted. --- src/api/abstract/abstract.router.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/abstract/abstract.router.ts b/src/api/abstract/abstract.router.ts index 2a64296ded..6287d858f4 100644 --- a/src/api/abstract/abstract.router.ts +++ b/src/api/abstract/abstract.router.ts @@ -52,8 +52,10 @@ export abstract class RouterBroker { Object.assign(instance, sanitizeUntrustedInput(request.query as Record)); } + // Create has no :instanceName in the path and only passes the guard with the + // global key, so the body is where the new instance's name legitimately comes from. if (request.originalUrl.includes('/instance/create')) { - Object.assign(instance, sanitizeUntrustedInput(body)); + Object.assign(instance, body); } Object.assign(ref, body); From b97d333587dc426455a53175518b9fad5dc11ca4 Mon Sep 17 00:00:00 2001 From: Marcelo Date: Fri, 6 Nov 2026 16:26:19 -0300 Subject: [PATCH 3/3] chore(release): 2.3.8 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5102b5375c..11264ebf0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 2.3.8 (2026-11-06) + +### Security + +* **Instance isolation**: an instance's API key could act on other instances of the same installation + ([#2435](https://github.com/evolution-foundation/evolution-api/issues/2435)). Every request is now bound to the + instance it was authenticated for. Installations that host more than one instance should upgrade. + # 2.3.7 (2025-12-05) ### Features diff --git a/package-lock.json b/package-lock.json index c45e8fef38..82f8e1c111 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "evolution-api", - "version": "2.3.7", + "version": "2.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "evolution-api", - "version": "2.3.7", + "version": "2.3.8", "license": "Apache-2.0", "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", diff --git a/package.json b/package.json index 56e32fcc8a..e7d48b8fda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "2.3.7", + "version": "2.3.8", "description": "Rest api for communication with WhatsApp", "main": "./dist/main.js", "type": "commonjs",