Apr 22, 2026·6 min read·3 visits
Unauthenticated attackers can send crafted WebSocket messages containing nested regex quantifiers to Signal K Server, triggering a ReDoS condition that blocks the Node.js event loop and causes 100% CPU exhaustion.
Signal K Server prior to version 2.25.0 contains a Regular Expression Denial of Service (ReDoS) vulnerability in its WebSocket subscription handling module. The application dynamically compiles unvalidated user input into regular expressions, allowing unauthenticated remote attackers to trigger catastrophic backtracking in the Node.js V8 engine. This results in complete resource exhaustion and immediate denial of service.
Signal K Server operates as a central data hub for marine vessels, heavily utilizing WebSockets for real-time telemetry streaming. The application processes client subscription requests to specific data paths using the SubscriptionManager component. Clients define their areas of interest using a context parameter, which the server translates into an internal filtering mechanism.
CVE-2026-39320 represents a Regular Expression Denial of Service (ReDoS) vulnerability in this subscription filtering logic. The application dynamically constructs regular expressions from unauthenticated, user-supplied context and path parameters. A failure to safely escape regex metacharacters allows attackers to inject malicious execution patterns.
Because Signal K Server runs on the Node.js runtime, it relies on a single-threaded event loop for JavaScript execution. When the V8 JavaScript engine evaluates an injected, highly complex regular expression against standard system identifiers, it enters a state of catastrophic backtracking. This synchronous computational operation halts all concurrent processing, resulting in an immediate and sustained denial of service.
The root cause lies in src/subscriptionmanager.ts, specifically within the contextMatcher and pathMatcher functions. These functions are responsible for converting wildcard-based string queries into native RegExp objects. The intended design allows simple wildcard filtering, translating paths like vessels.* into a matching regex pattern.
To achieve this behavior, the legacy implementation applied a naive string replacement strategy. It converted period characters to literal periods and asterisks to .* catch-all matchers. The implementation completely omitted sanitization for all other regular expression control characters, including grouping parentheses, alternation pipes, and repetition quantifiers.
This logic omission permits an attacker to supply a context string containing nested quantifiers. When the server concatenates this unsanitized input into the RegExp constructor, it creates a compiled pattern capable of exponential backtracking. The engine attempts to match the payload against long internal strings, typically the server's own 36-character UUID.
As the V8 regex engine evaluates the string, the nested groups cause it to compute every possible combination of character matches before ultimately failing. The computational time required scales exponentially with the length of the input string. This synchronous evaluation process blocks the Node.js event loop entirely.
The vulnerable implementation relied on manual, incomplete string replacement logic. By examining the source code prior to version 2.25.0, the failure to escape standard metacharacters is evident. The replacement sequence only accounted for two specific characters, leaving the rest of the regex syntax fully accessible to the end user.
// Vulnerable implementation in src/subscriptionmanager.ts
function contextMatcher(context: string) {
const pattern = context
.replace(/\./g, '\\.')
.replace(/\*/g, '.*');
return new RegExp(`^${pattern}$`);
}The patch introduced in commit 215d81eb700d5419c3396a0fbf23f2e246dfac2d addresses the flaw by integrating an external, robust regex escaping library. Rather than attempting to maintain a blocklist of dangerous characters, the fix employs an allowlist-like approach. It neutralizes the entire input string before selectively restoring the intended wildcard functionality.
// Patched implementation
import escapeStringRegexp from 'escape-string-regexp';
function contextMatcher(context: string) {
// Properly escape all metacharacters first
let pattern = escapeStringRegexp(context);
// Safely restore the intended wildcard behavior
pattern = pattern.replace(/\\\*/g, '.*');
return new RegExp(`^${pattern}$`);
}This architectural change ensures that any user-supplied parentheses, brackets, or plus signs are treated strictly as string literals by the regex engine. Consequently, the attacker loses the ability to define capturing groups or dynamic quantifiers, completely mitigating the catastrophic backtracking vector.
Exploitation requires no prior authentication and minimal network interaction. An attacker only needs TCP access to the target Signal K Server's WebSocket endpoint, typically exposed on port 3000. The attack initiates via a standard WebSocket upgrade request, followed by the transmission of a single crafted JSON payload.
The payload specifically targets the context field of a subscription request. The attacker structures the context to include a nested repetition pattern, followed by a character guaranteed not to exist at the end of the server's internal evaluation string. This suffix forces the worst-case time complexity path within the regex engine.
{
"context": "vessels.([a-z0-9:-]+)+!",
"subscribe": [
{
"path": "navigation.position",
"period": 1000
}
]
}Upon receiving this frame, the server maps the string ([a-z0-9:-]+)+! into a RegExp object. It then attempts to validate an incoming vessel identifier against it. When matching a standard vessel URN (e.g., urn:mrn:signalk:uuid:c0d79334-4e25-4245-8892-54e8ccc8021d), the engine matches the alphanumeric characters but fails at the literal exclamation mark boundary. The engine continuously backtracks through the overlapping plus quantifiers, locking the thread.
The successful execution of this attack results in a total denial of service for the target application. The underlying Node.js runtime operates concurrently using an asynchronous event loop, but native regex evaluations execute synchronously on the main thread. The catastrophic backtracking consumes 100% of the CPU core allocated to the Signal K process.
While the thread remains locked in the regex evaluation sequence, the server cannot process any pending or incoming network I/O. All subsequent REST API requests, WebSocket messages, and internal timers queue indefinitely. Existing client connections eventually drop as application-layer keep-alive mechanisms fail, rendering the system entirely unresponsive.
The CVSS v3.1 base score of 7.5 reflects the severity of this availability impact. The exploit execution requires low attack complexity, demands no privileges, and functions without any user interaction. System confidentiality and data integrity remain unaffected, as the vulnerability exclusively exhausts computational resources.
The recovery process requires manual administrative intervention or automated orchestration restarts. The affected process will not recover gracefully, as the regex evaluation time for a 36-character string with nested quantifiers can exceed multiple years of compute time. Administrators must forcefully terminate the process (SIGKILL) and allow a supervisor daemon to restart it.
The primary remediation strategy requires upgrading the Signal K Server software to version 2.25.0 or later. This release contains the complete cryptographic neutralization of user input prior to regex compilation, alongside new rate-limiting structures. Administrators must verify their package versions using npm or their container orchestrator management interfaces.
In environments where an immediate software upgrade is impossible, administrators can deploy Web Application Firewall (WAF) rules or reverse proxy filters to inspect WebSocket traffic. WAF signatures should be configured to aggressively drop any WebSocket frames containing repetitive regex metacharacters, specifically targeting patterns containing ( followed by + in the context JSON field.
Network-level restrictions provide an additional, effective layer of defense. By isolating the WebSocket endpoint behind a strict reverse proxy configuration or VPN gateway, administrators can restrict access to known, trusted client IP addresses. Implementing pre-connection authentication at the load balancer level completely removes the unauthenticated remote attack vector.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Signal K Server Signal K | < 2.25.0 | 2.25.0 |
| Attribute | Detail |
|---|---|
| CVSS v3.1 | 7.5 (High) |
| CWE IDs | CWE-1333, CWE-400 |
| Attack Vector | Network (Unauthenticated) |
| Impact | Denial of Service (100% CPU exhaustion) |
| EPSS Score | 0.00041 (12.57th percentile) |
| Exploit Status | Proof of Concept (PoC) available |
| CISA KEV | Not Listed |
Inefficient Regular Expression Complexity