From f2e1a74cbca8da13b634426c579e9f3085463812 Mon Sep 17 00:00:00 2001 From: Oleksandra Matviienko Date: Thu, 16 Apr 2026 12:12:20 +0200 Subject: [PATCH 1/5] fix: resolve REINIT crash when switching alarm duration from static to dynamic Move fetchArguments() and state.update() before state.init() in initState() so that arguments are available when init() triggers reeval for DURATION conditions with active tracking (firstEventTs > 0). --- ...CalculatedFieldEntityMessageProcessor.java | 2 +- .../thingsboard/server/cf/AlarmRulesTest.java | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java index a2dc9b50f4..b69719f1a0 100644 --- a/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java @@ -485,7 +485,6 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM private void initState(CalculatedFieldState state, CalculatedFieldCtx ctx) { state.setCtx(ctx, actorCtx); - state.init(false); if (ctx.getCfType() == CalculatedFieldType.GEOFENCING && ctx.isCfHasRelationPathQuerySource()) { GeofencingCalculatedFieldState geofencingState = (GeofencingCalculatedFieldState) state; @@ -494,6 +493,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM Map arguments = fetchArguments(ctx); state.update(arguments, ctx); + state.init(false); state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize()); states.put(ctx.getCfId(), state); diff --git a/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java b/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java index cf94940e07..3a1446fb46 100644 --- a/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java @@ -402,6 +402,54 @@ public class AlarmRulesTest extends AbstractControllerTest { }); } + @Test + public void testChangeDurationConditionFromStaticToDynamic() throws Exception { + Argument temperatureArgument = new Argument(); + temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); + temperatureArgument.setDefaultValue("0"); + Map arguments = new HashMap<>(Map.of( + "temperature", temperatureArgument + )); + + long staticDurationMs = 5000L; + Map createRules = Map.of( + AlarmSeverity.CRITICAL, new Condition("return temperature >= 50;", null, staticDurationMs) + ); + + CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm", + arguments, createRules, null); + + // post telemetry to trigger condition, so that firstEventTs > 0 in AlarmRuleState + postTelemetry(deviceId, "{\"temperature\":50}"); + Thread.sleep(1000); + + // update CF: add attribute argument and switch duration from static to dynamic + AlarmCalculatedFieldConfiguration configuration = + (AlarmCalculatedFieldConfiguration) calculatedField.getConfiguration(); + + Argument durationArgument = new Argument(); + durationArgument.setRefEntityKey(new ReferencedEntityKey("durationThreshold", + ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE)); + durationArgument.setDefaultValue("-1"); + configuration.getArguments().put("durationThreshold", durationArgument); + + DurationAlarmCondition durationCondition = (DurationAlarmCondition) + configuration.getCreateRules().get(AlarmSeverity.CRITICAL).getCondition(); + durationCondition.setValue(new AlarmConditionValue<>(null, "durationThreshold")); + + calculatedField = saveCalculatedField(calculatedField); + + long dynamicDurationMs = 3000L; + postAttributes(deviceId, AttributeScope.SERVER_SCOPE, + "{\"durationThreshold\":" + dynamicDurationMs + "}"); + + checkAlarmResult(calculatedField, alarmResult -> { + assertThat(alarmResult.isCreated()).isTrue(); + assertThat(alarmResult.getAlarm().getSeverity()).isEqualTo(AlarmSeverity.CRITICAL); + assertThat(alarmResult.getAlarm().getStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK); + }); + } + @Test public void testCreateAlarm_currentOwnerArgument() throws Exception { Argument temperatureArgument = new Argument(); From 7131e4018d9204499c552192b162d9399f02f986 Mon Sep 17 00:00:00 2001 From: Oleksandra Matviienko Date: Mon, 4 May 2026 10:21:40 +0200 Subject: [PATCH 2/5] test: replace Thread.sleep with await on debug event before REINIT Guarantees firstEventTs > 0 in AlarmRuleState before saveCalculatedField triggers REINIT, so the test reliably exercises the buggy reeval path on slow CI; otherwise ruleState.isEmpty() may stay true and the alarm gets created via the fallback path even without the fix. --- .../org/thingsboard/server/cf/AlarmRulesTest.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java b/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java index 3a1446fb46..e13872e46e 100644 --- a/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/AlarmRulesTest.java @@ -407,9 +407,9 @@ public class AlarmRulesTest extends AbstractControllerTest { Argument temperatureArgument = new Argument(); temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); temperatureArgument.setDefaultValue("0"); - Map arguments = new HashMap<>(Map.of( + Map arguments = Map.of( "temperature", temperatureArgument - )); + ); long staticDurationMs = 5000L; Map createRules = Map.of( @@ -419,9 +419,13 @@ public class AlarmRulesTest extends AbstractControllerTest { CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm", arguments, createRules, null); - // post telemetry to trigger condition, so that firstEventTs > 0 in AlarmRuleState + // post telemetry to trigger condition and wait for the static-phase eval to produce a debug event, + // which guarantees firstEventTs > 0 in AlarmRuleState before we trigger REINIT postTelemetry(deviceId, "{\"temperature\":50}"); - Thread.sleep(1000); + CalculatedFieldId cfId = calculatedField.getId(); + await().atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> getDebugEvents(cfId, 1), + events -> !events.isEmpty() && !events.get(0).getId().equals(latestEventId)); // update CF: add attribute argument and switch duration from static to dynamic AlarmCalculatedFieldConfiguration configuration = From 52d547162aa018b210ac35c1b825fa2def61224c Mon Sep 17 00:00:00 2001 From: Oleksandra Matviienko Date: Mon, 20 Apr 2026 10:55:19 +0200 Subject: [PATCH 3/5] Fixed CVE-2026-40477, CVE-2026-40478, CVE-2026-5588, CVE-2026-5598, CVE-2025-14813, CVE-2026-35554, CVE-2026-27314 --- pom.xml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 905d22c522..47a971b920 100755 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 0.10 4.17.0 4.2.25 - 5.0.4 + 5.0.7 33.1.0-jre 10.1.54 3.18.0 @@ -102,7 +102,8 @@ 2.2.30 0.8 1.19.0 - 1.78.1 + 1.84 + 3.1.4.RELEASE 2.0.1 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* @@ -112,8 +113,8 @@ - 3.9.1 - 1.10.1 + 3.9.2 + 1.10.1 8.10.1 3.5.3 1.12.701 @@ -1021,6 +1022,20 @@ ${tomcat.version} + + + org.thymeleaf + thymeleaf + ${thymeleaf.version} + + + org.thymeleaf + thymeleaf-spring6 + ${thymeleaf.version} + + org.springframework.boot spring-boot-dependencies From ef9985f81121f43c5ebf10164da773f3f43e4bd7 Mon Sep 17 00:00:00 2001 From: Oleksandra Matviienko Date: Mon, 20 Apr 2026 12:41:40 +0200 Subject: [PATCH 4/5] Address review comments: group Spring Boot BOM overrides, drop thymeleaf + lz4 plumbing - Group tomcat, commons-lang3 version properties under spring-boot.version - Drop thymeleaf override (PE-only dependency, not present in CE) - Drop lz4 plumbing: kafka-clients 3.9.2 and cassandra-all 5.0.7 now transitively ship at.yawk.lz4:lz4-java, making the Dec 2025 CVE hack obsolete --- common/queue/pom.xml | 4 --- pom.xml | 39 ++-------------------- rule-engine/rule-engine-components/pom.xml | 4 --- tools/pom.xml | 4 --- 4 files changed, 3 insertions(+), 48 deletions(-) diff --git a/common/queue/pom.xml b/common/queue/pom.xml index a7d4d5b568..1cf8320468 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -68,10 +68,6 @@ org.apache.kafka kafka-clients - - at.yawk.lz4 - lz4-java - com.google.cloud google-cloud-pubsub diff --git a/pom.xml b/pom.xml index 47a971b920..1769b212f8 100755 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,8 @@ /var/log/${pkg.name} /usr/share/${pkg.name} 3.5.13 + 10.1.54 + 3.18.0 2.4.0-b180830.0359 0.12.5 0.10 @@ -70,8 +72,6 @@ 4.2.25 5.0.7 33.1.0-jre - 10.1.54 - 3.18.0 2.16.1 1.3.1 1.10.0 @@ -103,7 +103,6 @@ 0.8 1.19.0 1.84 - 3.1.4.RELEASE 2.0.1 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* @@ -113,8 +112,7 @@ - 3.9.2 - 1.10.1 + 3.9.2 8.10.1 3.5.3 1.12.701 @@ -1022,20 +1020,6 @@ ${tomcat.version} - - - org.thymeleaf - thymeleaf - ${thymeleaf.version} - - - org.thymeleaf - thymeleaf-spring6 - ${thymeleaf.version} - - org.springframework.boot spring-boot-dependencies @@ -1286,17 +1270,6 @@ org.apache.kafka kafka-clients ${kafka.version} - - - org.lz4 - lz4-java - - - - - at.yawk.lz4 - lz4-java - ${lz4.version} com.github.springtestdbunit @@ -1572,12 +1545,6 @@ org.apache.cassandra cassandra-all ${cassandra-all.version} - - - org.lz4 - lz4-java - - org.testng diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index a156bb22a2..1ac0938163 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -96,10 +96,6 @@ org.apache.kafka kafka-clients - - at.yawk.lz4 - lz4-java - com.amazonaws aws-java-sdk-sns diff --git a/tools/pom.xml b/tools/pom.xml index 46c56c634b..6376db6d9f 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -73,10 +73,6 @@ - - at.yawk.lz4 - lz4-java - commons-io commons-io From 463ac4b1acd539ddcfbdd141174b67a9857409ef Mon Sep 17 00:00:00 2001 From: Oleksandra Matviienko Date: Mon, 20 Apr 2026 14:00:42 +0200 Subject: [PATCH 5/5] Used Hex.toHexString in EncryptionUtil (bouncycastle 1.84 dropped pqc.legacy) --- .../org/thingsboard/server/common/msg/EncryptionUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index 0f50284d6c..2b3f273c70 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.msg; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.crypto.digests.SHA3Digest; -import org.bouncycastle.pqc.legacy.math.linearalgebra.ByteUtils; +import org.bouncycastle.util.encoders.Hex; /** * @author Valerii Sosliuk @@ -66,7 +66,7 @@ public class EncryptionUtil { md.update(dataBytes, 0, dataBytes.length); byte[] hashedBytes = new byte[256 / 8]; md.doFinal(hashedBytes, 0); - String sha3Hash = ByteUtils.toHexString(hashedBytes); + String sha3Hash = Hex.toHexString(hashedBytes); return sha3Hash; }