Signal K path traversal: 2.34.0 fixes secret theft
Signal K resources-provider path traversal can expose the JWT signing secret and enable persistent command execution with server-process privileges. Versions before 2.34.0 are affected; update to 2.34.0 even if anonymous reads are disabled, since read-only users remain exposed. Review saved providers too: updating does not undo earlier configuration changes.
Summary written with AI from this article, and checked against it.

Affected systems and impact
| Flaw | Affected systems | Impact |
|---|---|---|
| Unvalidated resource identifiers allow path traversal outside the storage root | Signal K resources-provider versions before 2.34.0, including authenticated read-only access even with allow_readonly=false | Signing-secret and password-hash theft, file writes and deletion, and persistent command execution with server-process privileges |
A read-only Signal K account can use Signal K path traversal to steal the signing secret and reach command execution. That's the finding in GHSA-4xm8-rg9h-jhq2. The flaw lives in resources-provider versions before 2.34.0; 2.34.0 is the patched release. Check your installed version and the allow_readonly setting before you update — disabling anonymous reads alone does not protect authenticated read-only users.
For administrators, the consequence extends beyond an exposed resource file. The advisory describes a chain: read configuration, forge an administrator token, create a command-executing provider, keep it across restarts. Patch exposed instances before routine maintenance because the documented outcome is command execution with the server process's privileges.
Check Signal K access and resources-provider versions
Start with the installed component and its access settings, not the advisory's exploit. Anonymous access and read-only authenticated access are separate exposure paths. A setting that blocks the former does not remove the latter.
Identify the installed resources-provider version, including whether it's bundled with signalk-server or installed standalone. The advisory marks versions before 2.34.0 as affected and 2.34.0 as patched. Its affected-product wording also covers all signalk-server releases, so don't substitute a server release label for checking the resources-provider component. Use your installed-package or deployment records to establish what's actually present.
Check whether
allow_readonly=trueis enabled. The advisory describes this as a supported, UI-toggleable setting that permits anonymous reads. Withallow_readonly=false, the documented traversal request returns401to an anonymous caller but still succeeds with a read-only user token. Turning off anonymous reads is a partial access restriction, not a fix.If you need to inspect how the server classifies an existing token, query its login-status endpoint. Replace the placeholder with a token you already hold and adjust the server address for your authorised instance.
This Python request prints the existing token's reported
userLevelwithout reading configuration files or creating providers.import json import urllib.request server = "http://127.0.0.1:3000" existing_token = "REPLACE_WITH_AN_EXISTING_TOKEN" status_request = urllib.request.Request( server + "/skServer/loginStatus", headers={"Authorization": "JWT " + existing_token}, ) status = json.load(urllib.request.urlopen(status_request)) print(status["userLevel"])This is a privilege check, not a vulnerability detector or version check. An expected result does not establish that the traversal is fixed. Do not use a disclosed signing secret to generate a test token against an operational service.
Update the affected resources-provider component to 2.34.0 and verify which component your deployment actually loads. The advisory does not supply an installation command or deployment-specific upgrade procedure, so follow the update mechanism appropriate to your installation rather than guessing a package command.
Until the update is complete, I'd restrict who can reach the service and disable anonymous reads where they're unnecessary. Those are containment choices; they should not be recorded as remediation of Signal K path traversal while an affected component remains installed.

Where the resources path check fails
The documented entry point is GET /signalk/v2/api/resources/:resourceType/:resourceId. The handler passes resourceId to FileStore.getResource() without validation. That storage method constructs a path with path.join(this.resources[type].path, itemUuid), then reads the file and parses its contents as JSON.
The advisory shows that an encoded, multi-level traversal can take that read outside the intended resource directory. The important boundary isn't whether a request names a resource type — it's whether the final filesystem location stays inside that resource type's storage root. In the documented implementation, the supplied identifier defeats that boundary.
The read has a particularly consequential destination: <config>/security.json. Signal K stores its JWT signing secret there in plaintext. The advisory connects the unvalidated resource identifier to disclosure of that secret, rather than treating arbitrary JSON-file access as an isolated information leak.
Its summary also covers file writes and deletion outside the storage root through the resources and applicationData APIs. The demonstrated takeover chain, however, starts with a resource read. That distinction matters when reviewing the recommended fix: protecting only the demonstrated GET handler would leave the other storage operations out of the review.
How Signal K path traversal becomes admin access
The signing secret turns a file-read problem into an authentication problem. The advisory demonstrates an HS256-signed token identifying the user as admin, created using the disclosed secret. Signal K accepts that token at /skServer/loginStatus with userLevel reported as admin, and the advisory says admin-protected endpoints accept it too.
This is why a read-only account is not a reassuring boundary on an affected installation. Its initial permission is sufficient to retrieve material used to create a token with a different privilege level. The documented escalation does not require the attacker to begin with an administrator's existing token.
The next link is POST /skServer/providers, an administrator-protected endpoint that accepts a provider with type Execute. The advisory reports that creating such a provider invokes its command through spawn('sh', ['-c', command]). Command execution happens at provider creation time; the chain does not depend on waiting for a later restart.
A forged token grants access to the existing command-execution functionality. Reviewing only the original read request would miss the privilege change and the resulting provider configuration.
Why a restart does not remove an Execute provider
The advisory reports that the created provider is written to settings.json and survives restarts. It also reports independent local reproduction that included restart persistence. A restart alone is not a cleanup step for the documented provider. The saved configuration is part of the takeover chain.
For an instance that may have been exposed, I would review the configured providers and their saved commands as well as installing the update. An unfamiliar Execute provider deserves investigation because it's the persistence mechanism described in the advisory. The mere presence of that provider type, however, is not proof that someone used this vulnerability.
Keep the review specific. Establish which command-capable providers are expected, which commands they contain, and whether the saved settings match the intended configuration. These are suggested response checks, not evidence that a particular installation has been attacked. Do not remove every command-capable provider simply because the advisory names that feature.
The reported impact is execution with server-process privileges. That's serious enough to require more than a package inventory, but it's also the boundary the advisory establishes. A saved provider should be assessed alongside the affected software, rather than assuming that updating the package explains or reverses every earlier configuration change.
The boundary checks recommended for Signal K
| Boundary | Advisory recommendation |
|---|---|
| Resource identifiers | Validate resourceId against a strict whitelist, such as UUID format, before storage calls, including PUT and DELETE. |
| Filesystem access | Use path.resolve() in FileStore.getResource/setResource/deleteResource and reject results outside this.resources[type].path. |
| Signing-secret storage | Move the JWT signing secret outside the API-readable configuration directory into a dedicated key file with mode 0600. |
| Command execution | Consider requiring explicit opt-in for command-executing provider types such as Execute. |
Storage-layer containment catches path traversal at the filesystem boundary, while request validation catches it at the API handler. The advisory explicitly names both the API handler and the storage methods. For a code review, that means checking where an identifier enters the system and where it finally becomes a filesystem path.
The secret-location and provider recommendations address later stages of the same chain. They are not reasons to leave traversal unfixed. They describe additional boundaries between reading resource data, obtaining signing material, and reaching functionality that executes commands.
What the 2.34.0 advisory does not establish
The advisory identifies a patched version, but it does not provide a release-by-release mapping between signalk-server and its bundled resources-provider. It also gives no version-check command. That limits how specific an installation recipe can be: confirm the component actually deployed instead of inferring its status from an unrelated runtime version or an administrator page.
The report contains a local proof of concept and says reproduction was independently verified. It does not report attacks against operational deployments. That supports taking the demonstrated chain seriously without describing every exposed server as compromised or presenting laboratory reproduction as evidence of exploitation in the wild.
Its demonstrated read returns JSON files through a JSON-parsing storage method. The summary also reports write and delete capabilities, but does not give those paths the same detailed reproduction. Keep those distinctions when communicating scope: neither reduce the issue to a harmless file read nor invent additional techniques beyond the advisory.
I'd treat Signal K path traversal as an urgent patch-and-review task, with two separate completion questions: is the affected component updated, and does the current provider configuration match what the administrator intended? The advisory's persistence finding is the reason not to let a successful upgrade answer both questions automatically.
Frequently asked questions
Which environment was used to verify the Signal K issue?
The advisory reports independent reproduction on master commit 340d5bb6 using Node 22 on macOS, including verification that the created provider persisted across a restart.
Can the security.json disclosure expose password information too?
Yes. The advisory says reading the configuration file exposes user password hashes as well as the JWT signing secret. It does not describe those hashes as plaintext passwords.
Source
- AdvisoryGHSA-4xm8-rg9h-jhq2 · github.com
How this article was made: drafted with AI from the source above and fact-checked against it, then reviewed by Vishal on . How we write and check articles.

