CVE-2026-41893 Overview
CVE-2026-41893 is an authentication weakness in Signal K Server, a server application that runs on a central hub in a boat for marine data exchange. Versions prior to 2.25.0 enforce rate limiting on HTTP login endpoints (POST /login and POST /signalk/v1/auth/login) using express-rate-limit, but the WebSocket login path invokes app.securityStrategy.login() directly without any throttling. Attackers can bypass HTTP rate limits by opening a WebSocket connection and submitting unlimited password guesses, limited only by bcrypt verification speed (approximately 20 attempts per second at 10 salt rounds). The flaw is categorized as Improper Restriction of Excessive Authentication Attempts [CWE-307].
Critical Impact
Unauthenticated remote attackers can brute-force Signal K Server user credentials over WebSocket, leading to account takeover on vessel onboard systems.
Affected Products
- Signal K Server versions prior to 2.25.0
- Deployments exposing HTTP/WebSocket login endpoints over the network
- Marine onboard hubs running signalk/signalk-server
Discovery Timeline
- 2026-05-09 - CVE-2026-41893 published to NVD
- 2026-05-15 - Last updated in NVD database
Technical Details for CVE-2026-41893
Vulnerability Analysis
Signal K Server exposes two authentication surfaces: the HTTP login endpoints and a WebSocket login channel. The HTTP endpoints are wrapped by express-rate-limit with a default limit of 100 attempts per 10-minute window, configurable via HTTP_RATE_LIMITS. The WebSocket handler accepts {login: {username, password}} messages on an established connection and forwards the credentials directly to app.securityStrategy.login(). No counter, lockout, or backoff is applied to the WebSocket path.
An attacker holding a single WebSocket session can issue password guesses continuously. The only practical ceiling is bcrypt's verification cost, which permits roughly 20 guesses per second at 10 salt rounds. That throughput is more than sufficient to enumerate weak passwords and dictionary-based credentials within hours.
Root Cause
The rate-limiting middleware was applied at the Express HTTP layer only. The WebSocket interface in src/interfaces/ws.ts did not share the limiter logic, leaving a parallel authentication channel without any throttling control.
Attack Vector
The attack is fully remote and unauthenticated. An attacker establishes a WebSocket connection to the Signal K Server, then transmits successive JSON login messages containing candidate credentials. Because the path bypasses express-rate-limit, no IP-based blocking activates, and no failed-attempt log on the HTTP layer is triggered.
// Patch excerpt from src/interfaces/ws.ts
InvalidTokenError,
WithSecurityStrategy
} from '../security'
+import {
+ LoginRateLimiter,
+ LOGIN_RATE_LIMIT_MESSAGE
+} from '../login-rate-limiter'
import { WithConfig } from '../app'
import {
findRequest,
// Source: https://github.com/SignalK/signalk-server/commit/215d81eb700d5419c3396a0fbf23f2e246dfac2d
The fix introduces a shared LoginRateLimiter module that tracks attempts per source IP across both transports:
// Patch excerpt from src/login-rate-limiter.ts
+export const LOGIN_RATE_LIMIT_MESSAGE =
+ 'Too many login attempts from this IP, please try again later'
+
+export interface LoginRateLimiter {
+ check(ip: string): { allowed: boolean; retryAfterMs: number }
+ dispose(): void
+}
+
+interface Entry {
+ count: number
+ resetTime: number
+}
+
+export function createLoginRateLimiter(
+ windowMs: number,
+ max: number
+): LoginRateLimiter {
+ const entries = new Map<string, Entry>()
+
+ const cleanup = setInterval(() => {
+ const now = Date.now()
+ for (const [ip, entry] of entries) {
+ if (now >= entry.resetTime) {
+ entries.delete(ip)
+ }
+ }
+ }, windowMs)
+ cleanup.unref()
+
+ return {
// Source: https://github.com/SignalK/signalk-server/commit/215d81eb700d5419c3396a0fbf23f2e246dfac2d
Detection Methods for CVE-2026-41893
Indicators of Compromise
- High volume of WebSocket connections from a single source IP to the Signal K Server port
- Repeated {login: {...}} WebSocket frames with varying password values for the same username
- Sustained CPU usage attributable to bcrypt hashing in the signalk-server process
- Successful login events preceded by hundreds of failed WebSocket authentication attempts
Detection Strategies
- Inspect WebSocket session logs for repeated login frames within short intervals from the same IP or session
- Correlate authentication success events with prior failure counts to identify brute-force success
- Alert on connection-rate anomalies against the Signal K Server WebSocket endpoint
Monitoring Recommendations
- Enable verbose authentication logging in signalk-server and forward logs to a central collector
- Monitor process metrics for spikes in bcrypt-bound CPU consumption on vessel hubs
- Track unique source IPs initiating WebSocket upgrades and flag deviations from baseline
How to Mitigate CVE-2026-41893
Immediate Actions Required
- Upgrade Signal K Server to version 2.25.0 or later without delay
- Restrict network exposure of the Signal K Server port to trusted vessel network segments only
- Rotate passwords for any accounts that may have been targeted, prioritizing privileged users
- Enforce strong password policies to raise the cost of guessing attacks
Patch Information
The issue is resolved in Signal K Server 2.25.0. The fix is delivered in pull request SignalK/signalk-server #2568 and commit 215d81eb700d5419c3396a0fbf23f2e246dfac2d. Refer to the GitHub Security Advisory GHSA-vmfm-ch9h-5c7g and the v2.25.0 release notes for details.
Workarounds
- Place Signal K Server behind a reverse proxy that enforces WebSocket connection rate limits per source IP
- Use firewall rules to allow access only from known marine network clients
- Disable WebSocket login if not required by downstream applications
- Require VPN access to the vessel network before allowing connections to the server
# Upgrade Signal K Server to the patched release
npm install -g signalk-server@2.25.0
# Verify installed version
signalk-server --version
# Optional: tighten HTTP rate limit window via environment variable
export HTTP_RATE_LIMITS='{"windowMs":600000,"max":50}'
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

