high · 8.3CVE-2026-59179Sep 10, 2026

CVE-2026-59179: @openhop/server Path Traversal in Flow ID File Operations

Rohit Hatagale
AI Security Researcher, SecureLayer7

The @openhop/server package passes unsanitized route parameters directly into filesystem path construction, letting any unauthenticated caller read or permanently delete arbitrary YAML files outside…

Package@openhop/server
Ecosystemnpm
Affected<= 0.3.5
Fixed in0.3.6
CVE-2026-59179: @openhop/server Path Traversal in Flow ID File Operations

The problem

FlowStore.filePath() in store.ts concatenates a caller-supplied id directly into path.join() with no validation. Two unauthenticated routes consume this result: GET /api/flows/:id reads the resolved file, and DELETE /api/flows/:id unlinks it.

Fastify's underlying router (find-my-way) runs decodeURIComponent on each route segment before the application sees it, so the percent-encoded slash in ..%2Fvictim is decoded to ../victim in req.params.id. Node.js path.join then normalizes the result outside the configured data directory.

CORS is set to origin: true (allow all origins), and Docker deployments bind HOST=0.0.0.0 by default, making the attack reachable both from a malicious browser page against a local instance and directly over the network.

Proof of concept

A working proof-of-concept for CVE-2026-59179 in @openhop/server, with the exact payload below.

bash
# Attack 1: read any .yaml file outside the flow store
curl -i --path-as-is 'http://127.0.0.1:8799/api/flows/..%2Fvictim'
# -> HTTP 200 with contents of /data/victim.yaml

# Attack 2: permanently delete any .yaml file outside the flow store
curl -i -X DELETE --path-as-is 'http://127.0.0.1:8799/api/flows/..%2Fdelete-me'
# -> HTTP 204, /data/delete-me.yaml is gone

The root cause is the absence of any allowlist or pattern check on the id parameter before it reaches path.join(). Because find-my-way percent-decodes route segments, the encoded slash %2F survives URL parsing as a literal character inside a single segment, bypassing any path-separator split the router might otherwise apply. path.join('/data/flows', '../victim.yaml') then collapses to /data/victim.yaml, cleanly escaping the intended directory.

The patch (commit c8190fbe) adds a strict allowlist regex const FLOW_ID_PATTERN = /^[A-Za-z0-9_-]+$/ checked at the top of filePath(). Any id that contains dots, slashes, or percent signs now throws before the join executes. This is CWE-22: the code was constructing a restricted path from untrusted input with no boundary enforcement.

The fix

Upgrade @openhop/server (and the openhop CLI) to 0.3.6. The fix adds a strict id allowlist regex in packages/server/src/store.ts that rejects any flow id containing characters outside [A-Za-z0-9_-], blocking traversal sequences before path.join is called.

Reporter not attributed.

References: [1][2][3]

Related research