From e5734208e95e748b3dff2a56238bde244171f8a6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 8 May 2026 12:08:45 +0200 Subject: [PATCH 01/10] Hardened tb-js-executor sandbox script invocation (JVN#16937365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The args array passed into the sandbox carried the host realm prototype chain, so a script could reach the host Function constructor via args.constructor.constructor and execute arbitrary code in the host process (read files, run shell commands, dump env vars). Construct args inside the sandbox context using vm.runInContext('[]'), then populate with string primitives. The resulting array's prototype chain belongs to the sandbox realm, so constructor traversal cannot escape. Strings are primitives and safe to cross the realm boundary. Affects use_sandbox=true path only. The use_sandbox=false path (invokeFunction) is intentionally left as-is and explicitly marked as dangerous-by-design — it compiles and runs user-supplied scripts in the host realm via vm.compileFunction (parsingContext only isolates parsing, not execution). It remains as a documented performance trade-off for trusted, non-public clusters; a startup WARN is logged when script.use_sandbox=false, and an operator-facing yaml comment sits next to the setting in config/default.yml. Reported by Hiroki Imai, LAC Co., Ltd. --- msa/js-executor/api/jsExecutor.ts | 25 ++++++++++++++++++++++--- msa/js-executor/config/default.yml | 6 ++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/msa/js-executor/api/jsExecutor.ts b/msa/js-executor/api/jsExecutor.ts index 855ad99932..ab916a2760 100644 --- a/msa/js-executor/api/jsExecutor.ts +++ b/msa/js-executor/api/jsExecutor.ts @@ -15,14 +15,22 @@ /// import vm, { Script } from 'vm'; +import { _logger } from '../config/logger'; export type TbScript = Script | Function; export class JsExecutor { useSandbox: boolean; + private logger = _logger('JsExecutor'); constructor(useSandbox: boolean) { this.useSandbox = useSandbox; + if (!useSandbox) { + this.logger.warn( + 'script.use_sandbox=false: dangerous by design — user-supplied scripts run in the host realm with no isolation. ' + + 'Use only as a performance trade-off in trusted, non-public clusters.' + ); + } } compileScript(code: string): Promise { @@ -56,9 +64,15 @@ export class JsExecutor { private invokeScript(script: Script, args: string[], timeout: number | undefined): Promise { return new Promise((resolve, reject) => { try { - const sandbox = Object.create(null); - sandbox.args = args; - const result = script.runInNewContext(sandbox, {timeout: timeout}); + const sandbox = vm.createContext(Object.create(null)); + // Construct args inside the sandbox context so it inherits sandbox-realm + // prototypes; prevents prototype-based escapes from the host realm. + const ctxArgs = vm.runInContext('[]', sandbox) as string[]; + for (let i = 0; i < args.length; i++) { + ctxArgs[i] = String(args[i]); + } + sandbox.args = ctxArgs; + const result = script.runInContext(sandbox, {timeout: timeout}); resolve(result); } catch (err) { reject(err); @@ -67,6 +81,11 @@ export class JsExecutor { } + // DANGEROUS BY DESIGN: the non-sandbox path. vm.compileFunction's + // parsingContext only isolates *parsing*, not *execution* — the resulting + // function runs in the host realm with full access to host globals + // (process, require, etc.). Enabled only via script.use_sandbox=false as + // a performance trade-off in trusted clusters. private createFunction(code: string): Promise { return new Promise((resolve, reject) => { try { diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 5939b0e29a..9a33190069 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -50,6 +50,12 @@ logger: filename: "tb-js-executor-%DATE%.log" script: + # WARNING: setting this to "false" is DANGEROUS BY DESIGN. The non-sandbox + # path compiles and runs user-supplied scripts in the host realm via + # vm.compileFunction; it provides no isolation and exposes the host process + # (file system, environment variables, child_process, etc.) to script + # authors. Use "false" only as a performance trade-off in trusted, + # non-public clusters where every script author is fully trusted. use_sandbox: "true" memory_usage_trace_frequency: "1000" script_body_trace_frequency: "10000" From e329c8d162b52dd29618bbaae8e0ed92338584ce Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 8 May 2026 12:08:56 +0200 Subject: [PATCH 02/10] Added Node unit tests for tb-js-executor sandbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four test cases under describe('js-executor'): - sandbox isolates args from host realm (JVN#16937365 — regression guard) - sandbox passes string args through unchanged - non-sandbox path does not isolate from host realm (documented contract) - non-sandbox path passes string args through unchanged Tests use Node's built-in node:test + node:assert (zero new devDependencies; ts-node was already there). Two npm scripts: test — spec output for local dev test:ci — spec to stdout + Node's built-in junit reporter to target/surefire-reports/TEST-js-executor.xml Wired 'yarn test:ci' into the Maven 'test' phase via frontend-maven-plugin, so 'mvn test -pl=msa/js-executor' produces JUnit XML that TeamCity's Maven runner auto-discovers under the 'js-executor' suite name. TEST_FAST.md picks up the same step. tsconfig excludes test/ from the production pkg bundle. --- TEST_FAST.md | 1 + msa/js-executor/package.json | 2 +- msa/js-executor/pom.xml | 11 ++++ msa/js-executor/test/jsExecutor.test.ts | 83 +++++++++++++++++++++++++ msa/js-executor/tsconfig.json | 2 +- 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 msa/js-executor/test/jsExecutor.test.ts diff --git a/TEST_FAST.md b/TEST_FAST.md index eb2013c601..fbea72db9c 100644 --- a/TEST_FAST.md +++ b/TEST_FAST.md @@ -10,6 +10,7 @@ export SUREFIRE_JAVA_OPTS="-Xmx1200m -Xss256k -XX:+ExitOnOutOfMemoryError" mvn clean install -T6 -DskipTests -Dpkg.skip=true mvn test -pl='!application,!dao,!ui-ngx,!msa/js-executor,!msa/web-ui' -T4 +mvn test -pl='msa/js-executor' mvn test -pl dao -Dparallel=packages -DforkCount=4 mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5 diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 13b7c01d81..9cd29e35d5 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -7,7 +7,7 @@ "bin": "server.js", "scripts": { "pkg": "tsc && pkg -t node22-linux-x64 --output ./target/thingsboard-js-executor-linux ./target/src && pkg -t node22-win-x64 --no-bytecode --public-packages \"*\" --public --output ./target/thingsboard-js-executor-win.exe ./target/src && node install.js", - "test": "echo \"Error: no test specified\" && exit 1", + "test": "mkdir -p target/surefire-reports && node --require ts-node/register --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=target/surefire-reports/TEST-js-executor.xml test/jsExecutor.test.ts", "start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'", "start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'", "build": "tsc" diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 58404c0152..2f7434b472 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -116,6 +116,17 @@ --mutex network run pkg + + yarn test + + yarn + + test + + ${maven.test.skip} + --mutex network run test + + diff --git a/msa/js-executor/test/jsExecutor.test.ts b/msa/js-executor/test/jsExecutor.test.ts new file mode 100644 index 0000000000..7777030ce9 --- /dev/null +++ b/msa/js-executor/test/jsExecutor.test.ts @@ -0,0 +1,83 @@ +/// +/// Copyright © 2016-2026 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { JsExecutor } from '../api/jsExecutor'; + +// describe('js-executor') groups all cases under +// in the JUnit XML so they show up under that suite in TeamCity's Tests tab, +// alongside thousands of Java tests. +describe('js-executor', () => { + +test('sandbox isolates args from host realm (JVN#16937365)', async () => { + const exec = new JsExecutor(true); + const script = await exec.compileScript(`function(msg, metadata, msgType){ + var F = args.constructor.constructor; + var p = F("return process")(); + return p && p.mainModule ? 'reached-host' : 'isolated'; + }`); + await assert.rejects( + exec.executeScript(script, ['{}', '{}', 'POST_TELEMETRY_REQUEST'], 5000), + /process is not defined/, + 'host process must not be reachable from inside the sandbox', + ); +}); + +test('sandbox passes string args through unchanged', async () => { + const exec = new JsExecutor(true); + const script = await exec.compileScript(`function(msg, metadata, msgType){ + return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] }; + }`); + const out = await exec.executeScript(script, ['hello', '{}', 'X'], 5000); + // Field-by-field: the returned object is owned by the sandbox realm, so + // its prototype is not the host Object.prototype and deepStrictEqual would + // reject it on prototype mismatch even when the values match. + assert.equal(out.msgIsString, true); + assert.equal(out.count, 3); + assert.equal(out.first, 'hello'); +}); + +// The use_sandbox=false path is intentionally non-isolating: scripts compile +// and run in the host realm via vm.compileFunction. The two tests below codify +// that documented contract so any future behavior change shows up as a test +// failure and forces a deliberate update of the docs and threat model. + +test('non-sandbox path does not isolate from host realm (documented contract)', async () => { + const exec = new JsExecutor(false); + const script = await exec.compileScript(`function(msg, metadata, msgType){ + // Non-destructive host-reach probe: typeof process.platform is 'string' + // only if the host process object is reachable. + var F = args.constructor.constructor; + return F('return typeof process.platform')(); + }`); + const out = await exec.executeScript(script, ['{}', '{}', 'X']); + assert.equal(out, 'string', + 'use_sandbox=false is documented as non-isolating; if this fails, the path was changed and docs/threat model must be updated'); +}); + +test('non-sandbox path passes string args through unchanged', async () => { + const exec = new JsExecutor(false); + const script = await exec.compileScript(`function(msg, metadata, msgType){ + return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] }; + }`); + const out = await exec.executeScript(script, ['hello', '{}', 'X']); + assert.equal(out.msgIsString, true); + assert.equal(out.count, 3); + assert.equal(out.first, 'hello'); +}); + +}); // describe('js-executor') diff --git a/msa/js-executor/tsconfig.json b/msa/js-executor/tsconfig.json index b633ffc768..a1f7b25466 100644 --- a/msa/js-executor/tsconfig.json +++ b/msa/js-executor/tsconfig.json @@ -9,5 +9,5 @@ "skipLibCheck": true, "strictPropertyInitialization": false }, - "exclude": ["node_modules", "target"] + "exclude": ["node_modules", "target", "test"] } From 6d00e0432a2316750da2c3c3f639e7c944991f88 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 8 May 2026 12:09:08 +0200 Subject: [PATCH 03/10] Added regression tests for /api/ruleChain/testScript Two test layers covering the controller surface that the JVN PoC uses: Java unit (Spring MockMvc): RuleChainControllerTest#testScriptForbiddenForCustomer asserts a customer JWT against POST /api/ruleChain/testScript returns 403, locking in the existing @PreAuthorize('TENANT_ADMIN') guard. Black-box (live docker-compose): JsExecutorSandboxIsolationTest#testRuleChainScriptCannotReachHostProcess posts the JVN exploit payload as a tenant admin and asserts the response carries error='process is not defined'. End-to-end through tb-node -> Kafka -> tb-js-executor with use_sandbox=true. Registered the new org.thingsboard.server.msa.security package in the connectivity TestNG suite so the black-box runner picks it up. Added a thin TestRestClient.testRuleChainScript() helper. --- .../controller/RuleChainControllerTest.java | 17 +++++ .../server/msa/TestRestClient.java | 10 +++ .../JsExecutorSandboxIsolationTest.java | 73 +++++++++++++++++++ .../src/test/resources/connectivity.xml | 1 + 4 files changed, 101 insertions(+) create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java diff --git a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java index f8cd8e0f33..a485c02bb8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java @@ -405,4 +405,21 @@ public class RuleChainControllerTest extends AbstractControllerTest { return doPost("/api/ruleChain", ruleChain, RuleChain.class); } + @Test + public void testScriptForbiddenForCustomer() throws Exception { + loginCustomerUser(); + + doPost("/api/ruleChain/testScript", (Object) """ + { + "script": "return msg;", + "scriptType": "update", + "argNames": ["msg", "metadata", "msgType"], + "msg": "{}", + "metadata": {}, + "msgType": "POST_TELEMETRY_REQUEST" + } + """) + .andExpect(status().isForbidden()); + } + } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java index 7b2cfc68d9..1e14dbd70b 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java @@ -328,6 +328,16 @@ public class TestRestClient { .statusCode(HTTP_OK); } + public JsonNode testRuleChainScript(Object body) { + return given().spec(requestSpec) + .body(body) + .post("/api/ruleChain/testScript") + .then() + .statusCode(HTTP_OK) + .extract() + .as(JsonNode.class); + } + private String getUrlParams(PageLink pageLink) { String urlParams = "pageSize={pageSize}&page={page}"; if (!isEmpty(pageLink.getTextSearch())) { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java new file mode 100644 index 0000000000..1328382c5d --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2026 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.msa.security; + +import com.fasterxml.jackson.databind.JsonNode; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.thingsboard.server.msa.AbstractContainerTest; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JsExecutorSandboxIsolationTest extends AbstractContainerTest { + + @BeforeClass + public void beforeClass() { + testRestClient.login("tenant@thingsboard.org", "tenant"); + } + + @AfterClass + public void afterClass() { + testRestClient.resetToken(); + } + + /** + * Black-box regression for JVN#16937365: a tenant admin must not be able + * to escape the tb-js-executor sandbox via the host-realm prototype chain + * exposed through the script's `args` argument. Runs against the live + * docker-compose deployment, which uses script.use_sandbox=true and + * JS_EVALUATOR=remote (Kafka -> tb-js-executor). + */ + @Test + public void testRuleChainScriptCannotReachHostProcess() { + JsonNode response = testRestClient.testRuleChainScript(""" + { + "script": "var F = args.constructor.constructor; var p = F('return process')(); return { reachedHost: !!(p && p.mainModule) };", + "scriptType": "update", + "argNames": ["msg", "metadata", "msgType"], + "msg": "{}", + "metadata": {}, + "msgType": "POST_TELEMETRY_REQUEST" + } + """); + + // The sandboxed run must reject the escape attempt: the host `process` + // global is not defined inside the sandbox realm, so executing the + // synthesized function `F("return process")` throws. + assertThat(response.has("error")).isTrue(); + String error = response.get("error").asText(); + assertThat(error) + .as("sandbox must block host-realm reach via args.constructor.constructor; full error: %s", error) + .contains("process is not defined"); + + // Defense in depth: even if the script somehow returned, output must + // not indicate that the host process was reached. + if (response.hasNonNull("output")) { + assertThat(response.get("output").asText()).doesNotContain("\"reachedHost\":true"); + } + } +} diff --git a/msa/black-box-tests/src/test/resources/connectivity.xml b/msa/black-box-tests/src/test/resources/connectivity.xml index a1cbaf4af6..7cd96bb429 100644 --- a/msa/black-box-tests/src/test/resources/connectivity.xml +++ b/msa/black-box-tests/src/test/resources/connectivity.xml @@ -25,6 +25,7 @@ + \ No newline at end of file From 3aaa4d2fd28ffc498e478708800b82ea90c5f4fd Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 14 May 2026 12:00:09 +0200 Subject: [PATCH 04/10] Remove unnecessary DomSanitizer bypass in photo camera input widget --- .../widget/lib/photo-camera-input.component.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts index 060a74aebd..541b6ab384 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts @@ -24,7 +24,7 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; + import { ImageService } from '@app/core/public-api'; import { AppState } from '@core/core.state'; import { AttributeService } from '@core/http/attribute.service'; @@ -63,8 +63,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On protected store: Store, private imageService: ImageService, private utils: UtilsService, - private attributeService: AttributeService, - private sanitizer: DomSanitizer + private attributeService: AttributeService ) { super(store); } @@ -115,8 +114,8 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On isLoading = false; singleDevice = true; updatePhoto = false; - previewPhoto: SafeUrl; - lastPhoto: SafeUrl; + previewPhoto: string; + lastPhoto: string; datasourceDetected = false; private mimeType: string; @@ -176,7 +175,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On private updateWidgetData(data: Array) { const keyData = data[0].data; if (keyData?.length && isString(keyData[0][1])) { - this.lastPhoto = keyData[0][1].startsWith('data:image/') ? this.sanitizer.bypassSecurityTrustUrl(keyData[0][1]) : keyData[0][1]; + this.lastPhoto = keyData[0][1]; } } @@ -309,7 +308,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On const file = new File([blob], fileName, { type: this.mimeType }); return this.imageService.uploadImage(file, fileName); }), - map((imageInfo) => + map((imageInfo) => this.settings.usePublicGalleryLink ? imageInfo.publicLink : imageInfo.link ) ); From bd0427e8756be9c6acca78516e1f5b5a4b8200fd Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 18 May 2026 10:46:36 +0300 Subject: [PATCH 05/10] Bump tomcat.version from 10.1.54 to 10.1.55 to fix CVE-2026-41284 and CVE-2026-43512 --- pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pom.xml b/pom.xml index 24f8f9e1c2..4a8958ffa3 100755 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,7 @@ 3.18.0 42.7.11 4.1.133.Final + 10.1.55 2.4.0-b180830.0359 0.12.5 0.10 @@ -1016,6 +1017,23 @@ import + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + + + org.apache.tomcat.embed + tomcat-embed-el + ${tomcat.version} + + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcat.version} + + org.springframework.boot spring-boot-dependencies From 6079cbb1808a434cd2a757a87e6b981141a2874f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 19 May 2026 10:31:46 +0300 Subject: [PATCH 06/10] fixed openapi descriptions for arrays --- .../server/common/data/ai/dto/TbUserMessage.java | 10 ++++------ .../common/data/query/AvailableEntityKeys.java | 12 ++++++++---- .../common/data/query/AvailableEntityKeysV2.java | 14 ++++++-------- .../data/sync/ie/WidgetsBundleExportData.java | 3 ++- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java index e1f0b84a4d..9e91adb2c0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java @@ -25,12 +25,10 @@ import java.util.List; public record TbUserMessage( @NotEmpty @Valid - @ArraySchema( - arraySchema = @Schema( - requiredMode = Schema.RequiredMode.REQUIRED, - description = "A list of content parts that make up the complete user prompt" - ), - schema = @Schema(ref = "#/components/schemas/TbContent") + @Schema( + requiredMode = Schema.RequiredMode.REQUIRED, + description = "A list of content parts that make up the complete user prompt" ) + @ArraySchema(schema = @Schema(ref = "#/components/schemas/TbContent")) List contents ) {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java index a20d0dca55..532f7f2e1a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java @@ -37,17 +37,21 @@ public record AvailableEntityKeys( ) Set entityTypes, - @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Schema( + requiredMode = Schema.RequiredMode.REQUIRED, + description = "List of unique time series key names available on the matched entities." + ) @ArraySchema( - arraySchema = @Schema(description = "List of unique time series key names available on the matched entities."), schema = @Schema(implementation = String.class, example = "temperature"), uniqueItems = true ) List timeseries, - @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + @Schema( + requiredMode = Schema.RequiredMode.REQUIRED, + description = "List of unique attribute key names available on the matched entities." + ) @ArraySchema( - arraySchema = @Schema(description = "List of unique attribute key names available on the matched entities."), schema = @Schema(implementation = String.class, example = "serialNumber"), uniqueItems = true ) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java index 536b70e4ff..608177b9ef 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java @@ -41,15 +41,13 @@ public record AvailableEntityKeysV2( ) Set entityTypes, - @ArraySchema( - arraySchema = @Schema( - description = """ - List of unique time series keys available on the matched entities, sorted alphabetically. - Omitted when timeseries keys were not requested.""", - nullable = true - ), - schema = @Schema(implementation = KeyInfo.class) + @Schema( + description = """ + List of unique time series keys available on the matched entities, sorted alphabetically. + Omitted when timeseries keys were not requested.""", + nullable = true ) + @ArraySchema(schema = @Schema(implementation = KeyInfo.class)) @Nullable List timeseries, @Schema( diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java index 62140abdd1..6883fff6d4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java @@ -36,7 +36,8 @@ public class WidgetsBundleExportData extends EntityExportData { @Override public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; } - @ArraySchema(arraySchema = @Schema(description = "List of widgets in the bundle"), schema = @Schema(implementation = JsonNode.class)) + @Schema(description = "List of widgets in the bundle") + @ArraySchema(schema = @Schema(implementation = JsonNode.class)) @JsonProperty(index = 3) private List widgets; From 98f19e3162bff0a2b268027f5786898f744e21d1 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 20 May 2026 10:26:57 +0200 Subject: [PATCH 07/10] Fix CVE-2026-45149 --- ui-ngx/package.json | 3 ++- ui-ngx/yarn.lock | 26 ++++---------------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 076e2d316a..b343638845 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -142,6 +142,7 @@ "esbuild": "0.28.0", "rollup": "4.59.0", "jquery.terminal/**/form-data": ">=4.0.4", - "js-beautify/**/minimatch": "^9.0.7" + "js-beautify/**/minimatch": "^9.0.7", + "brace-expansion": "5.0.6" } } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index e9564f5279..258f852eea 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -4248,11 +4248,6 @@ babel-plugin-polyfill-regenerator@^0.6.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.6.5" -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - balanced-match@^4.0.2: version "4.0.4" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" @@ -4348,18 +4343,10 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -brace-expansion@^1.1.7: - version "1.1.13" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6" - integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^5.0.2: - version "5.0.5" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" - integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== +brace-expansion@5.0.6, brace-expansion@^1.1.7, brace-expansion@^5.0.2: + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== dependencies: balanced-match "^4.0.2" @@ -4725,11 +4712,6 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - confbox@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" From 5343453cc22a56b42eeb87949b61573291376bd6 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 20 May 2026 10:29:01 +0200 Subject: [PATCH 08/10] Fix CVE-2026-6322 --- ui-ngx/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 258f852eea..289e71b943 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -6095,9 +6095,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-uri@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" - integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec" + integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ== fastq@^1.6.0: version "1.17.1" From 18bd3ac5f76387e7a8bc501d148d3deba162ea28 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 20 May 2026 10:31:34 +0200 Subject: [PATCH 09/10] Fix CVE-2026-44728 --- ui-ngx/yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 289e71b943..0c6bdf45a2 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -987,14 +987,14 @@ "@babel/helper-plugin-utils" "^7.28.6" "@babel/plugin-transform-modules-systemjs@^7.27.1": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" - integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== + version "7.29.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz#f621105da99919c15cf4bde6fcc7346ef95e7b20" + integrity sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w== dependencies: - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.5" + "@babel/traverse" "^7.29.0" "@babel/plugin-transform-modules-umd@^7.27.1": version "7.27.1" @@ -1298,7 +1298,7 @@ "@babel/parser" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6": +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== From 561d9dea5d6eeea3ca30b3462d7995875ec6ac21 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 20 May 2026 10:47:35 +0200 Subject: [PATCH 10/10] Minor fix --- ui-ngx/package.json | 3 +-- ui-ngx/yarn.lock | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index b343638845..076e2d316a 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -142,7 +142,6 @@ "esbuild": "0.28.0", "rollup": "4.59.0", "jquery.terminal/**/form-data": ">=4.0.4", - "js-beautify/**/minimatch": "^9.0.7", - "brace-expansion": "5.0.6" + "js-beautify/**/minimatch": "^9.0.7" } } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 0c6bdf45a2..6b5f48283b 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -4248,6 +4248,11 @@ babel-plugin-polyfill-regenerator@^0.6.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.6.5" +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + balanced-match@^4.0.2: version "4.0.4" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" @@ -4343,7 +4348,15 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -brace-expansion@5.0.6, brace-expansion@^1.1.7, brace-expansion@^5.0.2: +brace-expansion@^1.1.7: + version "1.1.14" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b" + integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^5.0.2: version "5.0.6" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== @@ -4712,6 +4725,11 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + confbox@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"