Versions of Signal K Server prior to 2.19.0 allow unauthenticated access to sensitive diagnostic endpoints. Attackers can map serial ports, view the full server data schema, and fingerprint installed analyzers due to a missing authorization check in the application's middleware routing logic.
Signal K Server, the open-source nervous system for modern connected boats, suffered from a classic 'fail-open' security architecture. By forgetting to manually whitelist three API endpoints in a centralized security file, developers inadvertently exposed system internals and hardware configurations to unauthenticated remote attackers.
Boats used to be simple things—wood, fiberglass, and maybe a diesel engine that you could fix with a hammer. Today, a modern vessel is a floating data center. At the heart of this revolution is Signal K Server, a Node.js-based hub that takes the chaotic, proprietary babel of marine electronics (NMEA 0183, NMEA 2000) and translates it into beautiful, standardized JSON.
It’s a brilliant piece of software that lets you visualize your engine temp on an iPad or track your wind speed via Grafana. But here's the kicker: because boat owners love remote monitoring, these servers are frequently exposed to the open internet, often sitting behind nothing more than a consumer-grade router.
When you expose the brain of your ship to the web, you'd better hope the authorization logic is watertight. Unfortunately, as CVE-2025-68273 demonstrates, the Signal K team left a few portholes open. It’s not a remote code execution (RCE) that sinks the ship immediately, but it’s a leak that lets anyone with a web browser peek into the engine room.
The root cause of this vulnerability is a textbook example of architectural fragility. Ideally, a web application should be 'secure by default'—meaning every route requires authentication unless explicitly exempted. Signal K Server, however, opted for a manual whitelist approach for its middleware application.
In the file src/tokensecurity.js, the developers maintain arrays of strings representing URL paths. The server iterates over these arrays and applies security middleware (like http_authorize or adminAuthenticationMiddleware) to the matching routes. If a developer adds a new route in src/serverroutes.ts but forgets to copy-paste that path into tokensecurity.js, the new route is born into the world naked and unprotected.
This is a fail-open design. In this specific case, three endpoints related to server diagnostics and hardware management were implemented but never added to the security middleware lists:
/skServer/availablePaths/skServer/serialports/skServer/hasAnalyzerBecause the middleware never 'saw' these paths, requests to them were passed directly to the route handlers without checking for a session token or API key.
Let's look at the patch to understand the magnitude of the oversight. The fix wasn't a complex logic rewrite; it was literally just adding the forgotten strings to the list.
Here is the diff from commit ead2a03d8994969cafcca0320abee16f0e66e7a9. Notice how the server iterates over a hardcoded array to apply protection:
// src/tokensecurity.js
const generatedAdminKeys = [
'/backup',
'/restore',
'/providers',
'/vessel',
+ '/serialports' // <--- The fix: Now requires Admin Auth
]
generatedAdminKeys.forEach(string => {
app.use(`${SERVERROUTESPREFIX}${string}`, adminAuthenticationMiddleware(false))
})
const readOnlyKeys = [
'/webapps',
+ '/availablePaths', // <--- The fix: Now requires Read Auth
+ '/hasAnalyzer', // <--- The fix: Now requires Read Auth
'/skServer/inputTest'
]
readOnlyKeys.forEach(string => {
app.use(`${SERVERROUTESPREFIX}${string}`, http_authorize(false))
})[!NOTE] The move of
/serialportsto theadminAuthenticationMiddlewareblock is telling. It implies that listing the hardware interfaces of the host machine was considered sensitive enough to require full administrative privileges, yet prior to this patch, it was available to the entire world.
Exploiting this is trivially easy. You don't need a buffer overflow or a complex race condition; you just need curl. An attacker scanning for Signal K instances (default port 3000) can simply ask the server for its secrets.
1. Hardware Enumeration By querying the serial ports, an attacker can map the physical connections of the host device. This leaks filesystem paths, which is a goldmine if you are looking for Local File Inclusion (LFI) targets later, or trying to identify the host OS (e.g., distinguishing between a Raspberry Pi Linux path and a Windows path).
$ curl http://target-boat.local:3000/skServer/serialports
{
"serialports": [
"/dev/ttyUSB0",
"/dev/serial/by-id/usb-Actisense_NGT-1_..."
]
}2. Schema Dumping
The /availablePaths endpoint is even more chatty. It dumps the entire Signal K schema tree known to the server. This tells the attacker exactly what sensors are installed (Do they have AIS? Depth sounders? Engine metrics?) and reveals internal UUIDs and pending request states.
$ curl http://target-boat.local:3000/skServer/availablePaths
{
"paths": [
"vessel.self.navigation.position",
"vessel.self.propulsion.port.revolutions",
"vessel.self.electrical.batteries.1.voltage"
]
}This is reconnaissance gold. If I know you have a specific expensive brand of NMEA gateway connected to /dev/ttyUSB0, and I know your exact sensor tree, I can craft highly specific phishing emails to the owner or look for vulnerabilities in those specific sensor drivers.
The remediation in version 2.19.0 plugs the immediate holes by adding the missing paths to src/tokensecurity.js. If you are running Signal K, update immediately.
However, from a security research perspective, the fix feels like a band-aid on a broken leg. The underlying architecture—manual whitelisting—remains. This means the vulnerability is likely to recur the next time a developer adds a route and forgets to update the security file.
Bypassing the Fix?
Researchers should look closely at how Express handles path normalization versus how the string matching in tokensecurity.js works.
If tokensecurity.js does a simple string match, but the Express router is smarter about decoding paths, you might be able to bypass the check. For example, does requesting //skServer/serialports (double slash) bypass the middleware string match while still hitting the router? Or perhaps .../skServer/%2e%2e/skServer/serialports? It's a classic area for WAF-style bypasses in Node.js applications.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Signal K Server Signal K | < 2.19.0 | 2.19.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200 |
| Attack Vector | Network |
| CVSS Score | 5.3 |
| Impact | Information Disclosure |
| Patch Commit | ead2a03d8994969cafcca0320abee16f0e66e7a9 |
| Exploit Status | PoC Available |
Exposure of Sensitive Information to an Unauthorized Actor
Get the latest CVE analysis reports delivered to your inbox.