CVE-2026-59303
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Spring Cloud Stream is the Spring framework for building event-driven microservices that exchange messages over shared messaging systems. It binds the functions and message channels declared by an application to physical destinations on a broker such as Apache Kafka, RabbitMQ or Amazon Kinesis, and it exposes StreamBridge so that application code can send messages to destinations that are resolved at runtime rather than declared up front.
A Denial of Service (DoS) vulnerability (CVE-2026-59303) has been identified in the StreamBridge dynamic destination cache, which allows attackers to grow the heap of a running application without bound by driving traffic through a large number of distinct dynamic destination names, defeating the cache limit that the application configured to prevent exactly that growth.
Per OWASP: The Denial of Service (DoS) attack is focused on making a resource (site, application, server) unavailable for the purpose it was designed. There are many ways to make a service unavailable for legitimate users by manipulating network packets, programming, logical, or resources handling vulnerabilities, among others. If a service receives a very large number of requests, it may cease to be available to legitimate users. In the same way, a service may stop if a programming vulnerability is exploited, or the way the service handles resources it uses.
This issue affects the dynamic destination handling of StreamBridge in the core binding module of Spring Cloud Stream.
Details
Module Info
- Product: Spring Cloud Stream
- Affected packages:
org.springframework.cloud:spring-cloud-stream - Affected versions: >=4.2.0 <=4.2.6, >=4.3.0 <=4.3.3, >=5.0.0 <=5.0.2
- GitHub repository: https://github.com/spring-cloud/spring-cloud-stream
- Published packages: https://central.sonatype.com/artifact/org.springframework.cloud/spring-cloud-stream
- Package manager: Maven
- Fixed in:
- NES for Spring Cloud Stream: v3.1.9, v3.2.15, v4.1.9, v4.2.6, and v4.3.5
- OSS Spring Cloud Stream v5.0.3
Vulnerability Info
This Low-severity vulnerability is found in the org.springframework.cloud:spring-cloud-stream package in the StreamBridge component of Spring Cloud Stream.
When an application sends to a binding name that has no declared binding, Spring Cloud Stream treats that name as a dynamic destination. StreamBridge keeps the resolved message channels in a channelCache that is deliberately bounded: it is a LinkedHashMap whose removeEldestEntry evicts the oldest channel once the map exceeds the configured spring.cloud.stream.dynamic-destination-cache-size.
this.channelCache = new LinkedHashMap<String, MessageChannel>() {
@Override
protected boolean removeEldestEntry(Map.Entry<String, MessageChannel> eldest) {
boolean remove = size() > bindingServiceProperties.getDynamicDestinationCacheSize();
if (remove) {
if (logger.isDebugEnabled()) {
logger.debug("Removing message channel from cache " + eldest.getKey());
}
bindingService.unbindProducers(eldest.getKey());
}
return remove;
}
};
The eviction path releases the producer binding, but it never releases the second piece of per-destination state that the same send created. Resolving a dynamic destination goes through BindingServiceProperties, which lazily materialises a BindingProperties entry for any binding name it has not seen before and stores it in its own bindings map:
public BindingProperties getBindingProperties(String bindingName) {
this.bindIfNecessary(bindingName);
BindingProperties bindingProperties = this.bindings.get(bindingName);
if (bindingProperties.getDestination() == null) {
bindingProperties.setDestination(bindingName);
}
return bindingProperties;
}
private void bindIfNecessary(String bindingName) {
if (!this.bindings.containsKey(bindingName)) {
this.bindToDefault(bindingName);
}
}
Because the eviction path removes only the cached channel, every distinct dynamic destination name that an application ever sends to leaves a permanent BindingProperties object behind in the bindings map. The bound channelCache therefore gives a false sense of containment: the map that the operator sized stays small while a second, unbounded map grows for the lifetime of the process. An actor who can influence the destination names an application publishes to, for example through a routing expression or a message header that feeds the binding name, can force steady memory growth and eventual heap exhaustion in a long-lived service. Exploitation is not trivial, which is why the advisory rates this vulnerability Low, with a CVSS v3.1 base score of 3.1 and vector AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N; the practical risk is a slow degradation of an application that legitimately uses many short-lived dynamic destinations.
On the older lines that NES supports but the advisory's ranges do not list, the gap is wider still. In the 3.1.x code the eviction callback only writes a debug log line, and the producer binding is never registered for release in the first place, so both the binding and its properties entry survive eviction.
Mitigation
Only recent versions of Spring Cloud Stream receive community support. Older lines are End-of-Life and will not receive public updates to address this issue.
Users of the affected components should apply one of the following mitigations:
- Upgrade to a currently supported version of Spring Cloud Stream.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
Credits
- No public finder credit is listed in the advisory sources checked for this entry.