high · 8.6CVE-2026-54609Jul 28, 2026

CVE-2026-54609: QTINeon NeonRelay Unauthenticated Amplification via Unbounded RECONNECT_REQUEST Forwarding

Shubham Kandhare
Security Engagement Manager, SecureLayer7

An unauthenticated attacker can flood a QTINeon relay with RECONNECT_REQUEST packets from spoofed source addresses, causing the relay to forward each one to the hidden host with no cap or…

Packagecom.quietterminal:qti-neon
Ecosystemmaven
Affected= 1.0.0
CVE-2026-54609: QTINeon NeonRelay Unauthenticated Amplification via Unbounded RECONNECT_REQUEST Forwarding

The problem

NeonRelay's handleReconnectRequest method forwards every inbound RECONNECT_REQUEST to the host and writes each source address into pendingReconnects without checking the map's size. The connect flow has an equivalent guard (maxPendingConnections) but the reconnect path was never given one.

An attacker who knows any valid session ID can send the same RECONNECT_REQUEST from thousands of spoofed source IPs. The per-source rate limiter is useless here because each new spoofed IP gets a fresh token bucket. A secondary bug compounds this: once spoofed-IP count exceeds maxRateLimiters, performCleanup calls rateLimiters.clear(), wiping rate-limit state for legitimate sources too.

Proof of concept

A working proof-of-concept for CVE-2026-54609 in com.quietterminal:qti-neon, with the exact payload below.

python
# Send RECONNECT_REQUEST with a known session ID from N spoofed source IPs.
# Each packet passes the session lookup and is forwarded to the host.
# The pendingReconnects map grows without bound; no size check exists.
#
# Minimal packet structure (derived from advisory + PROTOCOL.md description):
#
#   [packet_type=RECONNECT_REQUEST][session_id=<valid-session-id>][client_id=<any>]
#
# Flood loop (conceptual; requires raw-socket / spoofing capability):
for spoofed_src in spoof_range("10.0.0.0", count=50000):
    pkt = build_packet(
        type="RECONNECT_REQUEST",
        session_id="<valid-session-id>",   # obtained by observing any prior session
        client_id=random_id(),
        src_ip=spoofed_src
    )
    relay.send(pkt)  # relay forwards each one to host; pendingReconnects grows unbounded

The root cause is a missing size guard on pendingReconnects in handleReconnectRequest across all three implementations (Java, Python, TypeScript). The connect flow already checks pendingConnects.size() >= maxPendingConnections before accepting a new entry, but the reconnect path has no equivalent check, violating the same invariant.

The patch the advisory describes adds three things: (1) reject the request when pendingReconnects.size() >= maxPendingConnections, mirroring the connect guard; (2) only forward to the host once per sessionId:clientId slot instead of on every packet; and (3) evict throttled entries in performCleanup before falling back to rateLimiters.clear(), so legitimate sources keep their rate-limit history.

CWE-770 (Allocation of Resources Without Limits or Throttling) is the primary root cause. CWE-406 (Network Amplification) describes the impact: the relay multiplies attacker traffic toward the host, whose real address is never exposed to clients by design, making this the primary viable DoS path against it.

The fix

No patched version has been released as of this writing (affected: com.quietterminal:qti-neon 1.0.0; status: unpatched). Operators should apply BCP38/uRPF at the network edge to drop spoofed-source packets, which eliminates the amplification path in most environments.

This does not fix the missing pendingReconnects size cap or the rateLimiters.clear() issue. Watch the upstream repository for a release that adds the three handleReconnectRequest guards described in the advisory.

Reporter not attributed.

References: [1][2]

Related research