- 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
TbRestApiCallNodeTest ran concurrently with SsrfSafeAddressResolverGroupTest,
which toggles the static SsrfProtectionValidator.enabled flag in its
setUp/tearDown. When the flag leaked into the REST test's async HTTP calls,
'localhost' was rejected by SSRF and extra tellFailure invocations broke the
Mockito verify count.
TbHttpClientTest and SsrfSafeAddressResolverGroupTest already declare
@ResourceLock("SsrfProtectionValidator"); apply the same lock to
TbRestApiCallNodeTest so all three SSRF-sensitive tests serialize.
Fixes#15453
Replace the blocking semaphore guard with a non-blocking bounded FIFO queue
+ semaphore pattern:
- No semaphore/queue when maxParallelRequestsCount=0 (default): direct doHttpCall,
identical to the old behavior.
- When a concurrency limit is set, incoming messages are enqueued via non-blocking
offer(); a full queue triggers onFailure immediately.
- tryProcess() acquires one semaphore slot and dispatches the next valid queued task.
Stale tasks (batch deadline expired) are dropped and the slot reused in the same pass.
- doFinally hook releases the semaphore and calls tryProcess() exactly once after any
terminal signal (success, error, cancel), preventing double-release and permit leaks.
- publishOn(externalCallExecutor) moves callbacks off reactor-netty I/O threads.
System-level safety caps are wired through thingsboard.yml → ActorSystemContext →
TbContext → TbHttpClient, scoped to rule-engine services only via @TbRuleEngineComponent:
actors.rule.external.http_client.max_parallel_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PARALLEL_REQUESTS)
actors.rule.external.http_client.max_pending_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PENDING_REQUESTS)
actors.rule.external.http_client.pool_max_connections (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_POOL_MAX_CONNECTIONS)
Backward compat: TB_RE_HTTP_CLIENT_POOL_MAX_CONNECTIONS still honored via yaml fallback.
Observability: five AtomicLong counters (dispatched, success, failure, droppedQueueFull,
droppedStale) with periodic WARN anomaly logging including semaphorePermits for leak detection.
No configuration changes or upgrade scripts required — docker image update is sufficient.
Rename RestApiCallNodeSettings to TbHttpClientSettings
The settings are about HTTP client transport concerns (connection pool,
concurrency, queue depth), not REST API Call node business logic.
The new name matches the consumer (TbHttpClient) and the YAML path
(actors.rule.external.http_client.*).
Replace stream().filter().collect() in resolveAll with single-pass loop
to avoid allocations on the common path (nothing blocked). Remove
redundant isEnabled() checks inside the resolver since it is only wired
when SSRF protection is enabled. Add resolveAll test coverage.
- Extract shared parseHostEntries() to deduplicate setAllowedHosts/setAdditionalBlockedHosts
- Add isHostnameAllowed() and propagate hostname allow-list check in resolver
- Move OAuth2 custom mapper URL SSRF validation to save-time (Oauth2ClientDataValidator)
- Remove runtime SSRF checks from CustomOAuth2ClientMapper and GithubOAuth2ClientMapper
(custom URL now validated at save; GitHub emailUrl is server config, not user input)
- Replace example.com with 8.8.8.8 in resolver test to avoid DNS dependency
Add SsrfSafeAddressResolverGroup that validates resolved IPs at Netty
connection time, eliminating the TOCTOU gap where DNS rebinding domains
resolve to safe IPs during validation but to private/metadata IPs at
connection time. Disable HTTP redirects in TbHttpClient to prevent
redirect-based SSRF bypass.
Add allow-list support (SSRF_ALLOWED_HOSTS) to SsrfProtectionValidator
so customers with IoT devices on private networks can whitelist specific
addresses or CIDR ranges while keeping SSRF protection enabled.
Add SSRF validation to MS Teams webhook, custom OAuth2 mapper, and
GitHub OAuth2 mapper endpoints. Log a warning when SSRF protection is
disabled.
Move TestDbCallbackExecutor from rule-engine test sources to
common/util main sources as DirectListeningExecutor, making it
available to all modules. Convert to an enum singleton since the
executor is stateless. Widen JpaAbstractDaoListeningExecutorService
service field type from JpaExecutorService to ListeningExecutor to
allow injecting DirectListeningExecutor in tests. Fix
AbstractChunkedAggregationTimeseriesDaoTest NPE by injecting the
direct executor into the spy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stack-based check in DefaultTbContext.input() only catches a loop
on the second Kafka round-trip through the same rule chain. When the
target rule chain equals the node's own rule chain (direct self-loop),
the first iteration is always unnecessary and should be rejected
immediately before any enqueue occurs.
Add an eager check in TbRuleChainInputNode.onMsg(): if the resolved
targetRuleChainId equals ctx.getSelf().getRuleChainId() (the rule chain
that contains this node), tellFailure() is called right away.
Together with the existing stack-based check in DefaultTbContext.input()
this forms a two-layer defence:
- Layer 1 (TbRuleChainInputNode): direct loops A→A, caught immediately
- Layer 2 (DefaultTbContext): indirect cycles A→B→A, caught on second pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delay and deduplication rule nodes were creating brand new TbMsg objects
instead of copying the original, which reset the ruleNodeExecCounter to 0.
This allowed bypassing the maxRuleNodeExecutionsPerMessage limit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce TbCallback-based finish notification for submitted jobs,
allowing callers to be notified when a job reaches a terminal state
(COMPLETED, FAILED, CANCELLED) via cluster-wide ComponentLifecycleMsg
broadcast.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deleteRequestWithBody and deleteRequestWithoutBody tests used
time-based synchronization (Thread.sleep) to wait for the async
WebClient response callback. Under CI load, the callback could fire
after the verify() check, causing flaky failures.
Replace the sleep-based approach with Mockito's timeout() on verify,
which properly polls for the async interaction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>