A AegiFlow
HIGHCVSS 8.3

CVE-2026-59179

@openhop/server: Path Traversal in Flow ID File Operations

Published
2026-09-09
Modified
2026-09-09
Aliases
GHSA-g72f-jw3w-mgh7
Sources
github-advisory

Summary

## Path Traversal in Flow ID File Operations ### Summary `@openhop/server` passes unsanitized HTTP route parameters directly to `path.join()` when constructing filesystem paths for flow YAML files. An unauthenticated attacker who can reach the server can read arbitrary `.yaml` files accessible to the OpenHop process outside the configured flow directory, and can delete arbitrary `.yaml` files at any path reachable by the process. Because CORS is set to `origin: true` (allow all origins), a victim's browser can be used to exploit the vulnerability against a loopback-bound instance. Docker deployments bind `HOST=0.0.0.0` by default, enabling direct remote exploitation. CVSS Base Score: **8.3 (High)**. ### Details `FlowStore.filePath()` in `packages/server/src/store.ts:52–53` constructs a filesystem path by concatenating the caller-supplied `id` directly into `path.join`: ```ts // packages/server/src/store.ts:52-53 private filePath(id: string): string { return join(this.dir, `${id}.yaml`) } ``` This result is consumed by two sinks: - **Read** (`packages/server/src/store.ts:78`): `readFile(this.filePath(id), 'utf-8')` - **Delete** (`packages/server/src/store.ts:105`): `unlink(this.filePath(id))` The `id` value originates from unauthenticated Fastify HTTP route parameters: - `GET /api/flows/:id` (`packages/server/src/routes.ts:306`) → `store.get(id)` at line 333–335 - `DELETE /api/flows/:id` (`packages/server/src/routes.ts:509`) → `store.delete(id)` at line 539–541 The route parameter schema at `packages/server/src/routes.ts:315` and `519` declares only `type: 'string'` with no pattern constraint or allowlist. Fastify's underlying router (`find-my-way`) applies `decodeURIComponent` to route parameters, so the URL segment `..%2Fvictim` is decoded to `../victim` before it reaches application code. Node.js `path.join('/data/flows', '../victim.yaml')` then normalizes to `/data/victim.yaml`, escaping the configured data directory. Additionally, `packages/server/src/index.ts:37` registers CORS with `origin: true`, permitting any browser origin to make cross-origin requests to the server. This makes the vulnerability exploitable via a malicious webpage against users running OpenHop locally. **Full data-flow (read path):** 1. HTTP `GET /api/flows/..%2Fvictim` received (`routes.ts:306`) 2. `find-my-way` decodes `..%2Fvictim` → `req.params.id = '../victim'` (`routes.ts:333`) 3. `store.get('../victim')` → `filePath('../victim')` → `join('/data/flows', '../victim.yaml')` → `/data/victim.yaml` (`store.ts:52–53`) 4. `readFile('/data/victim.yaml', 'utf-8')` returns file contents (`store.ts:78`) 5. Server responds HTTP 200 with YAML-parsed JSON body **Full data-flow (delete path):** 1. HTTP `DELETE /api/flows/..%2Fdelete-me` received (`routes.ts:509`) 2. `find-my-way` decodes `..%2Fdelete-me` → `req.params.id = '../delete-me'` (`routes.ts:539`) 3. `store.delete('../delete-me')` → `filePath('../delete-me')` → `join('/data/flows', '../delete-me.yaml')` → `/data/delete-me.yaml` (`store.ts:52–53`) 4. `unlink('/data/delete-me.yaml')` removes the file (`store.ts:105`) 5. Server responds HTTP 204 ### PoC **Environment setup (Docker):** ```bash # Build from repository root docker build -f vuln-001/Dockerfile -t openhop-vuln-001 . # Run with HOST=0.0.0.0 (default in the Dockerfile ENV) docker run -d --name openhop-vuln-001 -p 8799:8799 openhop-vuln-001 ``` The container creates `/data/flows/` as the configured flow store (`OPENHOP_DATA_DIR=/data/flows`) and places `/data/victim.yaml` and `/data/delete-me.yaml` outside that directory as traversal targets. **Attack 1 — Read file outside flow store:** ```bash curl -i --path-as-is 'http://127.0.0.1:8799/api/flows/..%2Fvictim' ``` Expected response: ```http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 {"id":"victim","meta":{"title":"SECRET_OUTSIDE_FILE","description":"This file lives outside the configured flow store directory"},"flow":{"nodes":[{"id":"a","label":"Sen

Affected packages

EcosystemPackageAffected versionsFixed versions
npm@openhop/server0.3.6

Remediation: Upgrade to 0.3.6 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.

CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.