CVE-2026-59179: @openhop/server Path Traversal in Flow ID File Operations
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…

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.
# 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 goneThe 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.
Related research
- high · 7.1@typespec/openapi3 Path Traversal via Versioned Enum Value in Output Filename
- critical · 9CVE-2026-75604CVE-2026-75604: Next.js Unauthenticated RCE via Windows Cache Path Traversal
- high · 7.5CVE-2026-82393CVE-2026-82393: pnpm Scoped-Name Path Traversal Arbitrary File Write
- high · 7.1CVE-2026-82392CVE-2026-82392: pnpm Virtual Store Linker Path Traversal via Crafted Lockfile