Feb 12, 2026·6 min read·23 visits
The `webtransport-go` library prior to v0.10.0 fails to delete stream references from an internal map after they are closed. Attackers can open and immediately close millions of streams, bloating the server's memory until it crashes (OOM), even while staying within 'active stream' limits.
A comprehensive analysis of a memory exhaustion vulnerability in the `webtransport-go` library. By failing to remove closed streams from an internal tracking map, the library allows attackers to trigger a Denial of Service (DoS) via unlimited memory consumption, bypassing standard concurrency limits.
WebTransport is the new hotness in the protocol world. Built on top of HTTP/3 and QUIC, it promises the low-latency, bidirectional communication that WebSockets could only dream of, but without the Head-of-Line blocking nightmares. It's complex, stateful, and fast. But as any systems programmer knows, 'stateful' is just a polite word for 'memory management minefield'.
When you build a protocol implementation in Go, you often lull yourself into a false sense of security. 'I have a Garbage Collector,' you whisper to yourself as you sleep soundly at night. 'I don't need to free() anything.' And technically, you're right. You don't need to manually free memory.
But the Garbage Collector is not a psychic. It cannot clean up objects that you are still referencing. If you put a sticky note on a box in the attic, the cleaner isn't going to throw it out. CVE-2026-21438 is exactly that: a digital hoarder's paradise where webtransport-go kept a reference to every single stream that ever existed in a session, long after those streams were dead and buried.
The vulnerability (CWE-401/CWE-459) is a classic case of unbounded data structure growth. In the affected versions of webtransport-go, the library maintains an internal map to track active WebTransport streams. This is standard practice; you need to map IDs to stream contexts to handle incoming data frames or state changes.
Here is the logic flaw: When a stream was initialized, it was eagerly added to this map. However, the developers missed the critical inverse operation. When a stream was closed—either by the local application or by a remote RESET_STREAM frame—the entry in the map was not removed.
This creates a fascinating discrepancy between the logical state of the connection and the physical memory usage. You could have a session with zero active streams, yet the underlying map structure could contain millions of entries pointing to dead stream contexts. The Go runtime sees these map entries as 'live' data, so the Garbage Collector (GC) kindly keeps its hands off. The server slowly suffocates, holding onto the ghosts of streams past.
Let's look at the pattern that causes this. While I won't bore you with the entire 5,000-line diff, the core issue boils down to lifecycle symmetry. In Go, maps don't shrink automatically, and entries don't disappear unless you explicitly delete() them.
In the vulnerable versions, the session management looked something like this:
// Simplified logic of the vulnerable code
type Session struct {
streams map[quic.StreamID]*Stream
// ... other fields
}
func (s *Session) acceptStream() (*Stream, error) {
// 1. Accept the QUIC stream
qStr, err := s.conn.AcceptStream(context.Background())
if err != nil { return nil, err }
// 2. Wrap it and ADD to the map
str := newStream(qStr)
s.streams[str.StreamID()] = str
return str, nil
}
// AND THAT'S IT. There was no corresponding delete()
// in the Close() path of the stream.The fix is elegantly simple. The maintainers introduced a cleanup mechanism that triggers when a stream dies. They essentially taught the library how to let go.
func (s *Session) removeStream(id quic.StreamID) {
s.mx.Lock()
defer s.mx.Unlock()
delete(s.streams, id) // <--- The line that saves your RAM
}This function is now hooked into the stream's closure lifecycle. When a stream is finished, it calls home to the parent Session and removes itself from the registry. It's basic housekeeping, but in high-throughput network services, housekeeping is critical infrastructure.
Exploiting this is trivially easy and frustratingly effective. Most DoS protections rely on concurrency limits. For example, a server might say, "You can only have 100 streams open at once." If you try to open stream #101, the server tells you to get lost.
But this bug doesn't require concurrent streams. It effectively leaks historical streams. An attacker can respect the concurrency limit perfectly and still crash the server.
RST_STREAM).From the perspective of the rate limiter:
From the perspective of the server's RAM:
The beauty of this attack is its stealth. It doesn't trigger "Max Open Files" warnings. It doesn't trip standard firewall concurrency rules. It just slowly eats memory until the Linux OOM killer walks in and shoots the process in the head.
The impact here is purely Availability (the 'A' in CIA triad), but don't let the 'Low' severity score fool you. In a microservices environment, or a gateway handling WebTransport traffic for thousands of users, this is a distinct single-point-of-failure.
Because the attack vector is network-based and requires no authentication (if the endpoint is public) or low privileges, any script kiddie with a loop can take down a production service. The memory consumption is linear. If a Stream struct and its map overhead cost roughly 200 bytes, sending 5 million streams (which takes seconds on a fast connection) consumes 1GB of RAM. Do that from 10 different IPs, and you're eating server capacity faster than an auto-scaling group can spin up new nodes.
If you are using webtransport-go for gaming servers, real-time collaboration tools, or media ingestion, this is a "wake up at 3 AM" kind of bug.
The mitigation is straightforward: Update your dependencies. The fix landed in v0.10.0. This version introduces the necessary cleanup logic.
> [!WARNING]
> Dependency Hell Alert: Version v0.10.0 of webtransport-go requires quic-go version v0.59.0 or later. This is a breaking API change for quic-go. You cannot just bump the patch version; you will likely need to refactor some of your QUIC handling code to match the new API surface.
go get github.com/quic-go/webtransport-go@v0.10.0github.com/quic-go/quic-go is updated to at least v0.59.0.AcceptStream, ensure you aren't holding onto references there either. The library fix only clears the library's internal map. If you put the stream in a global slice, that's on you.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
webtransport-go quic-go | < 0.10.0 | 0.10.0 |
| Attribute | Detail |
|---|---|
| CWE | CWE-401 (Memory Leak) |
| CVSS | 5.3 (Medium) |
| Attack Vector | Network |
| Exploit Status | Trivial / High Likelihood |
| Impact | Denial of Service (OOM) |
| Privileges | None |
Missing Release of Memory after Effective Lifetime
CVE-2026-14669 is a critical heap-based buffer overflow vulnerability in PostgreSQL's date/time formatting function to_char(timestamptz). The flaw arises from unsafe copying of user-controlled timezone abbreviations into a fixed-size internal buffer. An authenticated database user can trigger this issue by setting a long POSIX timezone abbreviation containing custom formatting, allowing them to overwrite adjacent heap structures and hijack execution control to achieve remote code execution (RCE) with the privileges of the 'postgres' operating system user.
An unauthenticated remote denial of service vulnerability exists in the Unleash feature management platform. By submitting a crafted JSON payload containing deeply nested structures to an OpenAPI-validated endpoint, an attacker can trigger uncontrolled recursion within the error formatting module. This leads to a call-stack exhaustion (RangeError: Maximum call stack size exceeded) inside the Node.js runtime, causing the service to crash immediately without recovery.
CVE-2026-63004 is a server-side request forgery (SSRF) vulnerability in the Unleash feature management platform. Authenticated administrators with CREATE_ADDON or UPDATE_ADDON privileges can exploit this vulnerability to initiate requests to loopback addresses, private networks, and cloud metadata endpoints, potentially leading to information disclosure and credential extraction.
Prior to version 8.0.3, Unleash's Markdown event formatter directly mutated the global template-escaping function of the shared mustache Node.js module, resulting in a process-wide security degradation where HTML/Markdown escaping was permanently disabled for the application lifetime.
A critical SQL injection vulnerability exists in the GeoTools open-source Java library. This vulnerability is situated within the post-processing phase of OGC Filter conversion inside the PostGIS DataStore module. Specifically, the `jsonArrayContains` function does not validate or sanitize its arguments before constructing PostgreSQL SQL/JSON path evaluation queries. An unauthenticated remote attacker can exploit this weakness by submitting crafted filters via standard OGC services like WFS or WMS to execute arbitrary SQL commands on the underlying database system.
CVE-2026-61824 is a high-severity Cross-Site Scripting (XSS) vulnerability in kepano/defuddle before version 0.19.1. Custom site extractors for platforms such as X/Twitter, Substack, and YouTube constructed HTML representations via template string interpolation without output escaping. This allowed malicious pages to bypass standard parser sanitization routines and execute arbitrary JavaScript.