VAITP Dataset

← Back to the dataset

CVE-2026-7810

Path traversal in python-notebook-mcp allows remote file manipulation.

  • CVSS 5.5
  • CWE-22
  • Input Validation and Sanitization
  • Remote

A flaw has been found in UsamaK98 python-notebook-mcp up to a05a232815809a7e425b5fa7be26e0d4369894c2. Impacted is the function create_notebook/read_notebook/edit_cell/add_cell of the file server.py. This manipulation causes path traversal. It is possible to initiate the attack remotely. The exploit has been published and may be used. This product is using a rolling release to provide continious delivery. Therefore, no version details for affected nor updated releases are available. The project was informed of the problem early through an issue report but has not responded yet.

CVSS base score
5.5
Published
2026-05-05
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
UsamaK98 pyt
Fixed by upgrading
Yes

Solution

No patch is currently available for this vulnerability as the project has not yet responded.

Vulnerable code sample

import os
from flask import Flask, request, jsonify

app = Flask(__name__)

# Base directory for storing notebooks
NOTEBOOK_DIR = "notebooks"

@app.route('/read_notebook', methods=['GET'])
def read_notebook():
    """
    Reads the content of a specified notebook.
    Vulnerable to path traversal.
    """
    notebook_name = request.args.get('name')
    if not notebook_name:
        return jsonify({"error": "Notebook name is required"}), 400

    # VULNERABILITY: User input is directly combined with a base path.
    # An input like '../../../../etc/passwd' can be used to read system files.
    file_path = os.path.join(NOTEBOOK_DIR, notebook_name)

    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        return jsonify({"name": notebook_name, "content": content})
    except FileNotFoundError:
        return jsonify({"error": "Notebook not found"}), 404
    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/create_notebook', methods=['POST'])
def create_notebook():
    """
    Creates a new notebook with given content.
    Vulnerable to path traversal.
    """
    data = request.get_json()
    notebook_name = data.get('name')
    content = data.get('content', '')

    if not notebook_name:
        return jsonify({"error": "Notebook name is required"}), 400

    # VULNERABILITY: User input is directly combined with a base path.
    # An attacker can create or overwrite files anywhere the server has write access.
    file_path = os.path.join(NOTEBOOK_DIR, notebook_name)
    
    try:
        # The 'w' mode will create the file if it doesn't exist, or overwrite it if it does.
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)
        return jsonify({"status": "success", "message": f"Notebook '{notebook_name}' created."})
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    if not os.path.exists(NOTEBOOK_DIR):
        os.makedirs(NOTEBOOK_DIR)
    app.run(host='0.0.0.0', port=8080)

Patched code sample

import os

# A secure base directory where notebooks are stored.
# Using os.path.abspath ensures we have a canonical path to compare against.
NOTEBOOK_DIR = os.path.abspath("./notebooks")

# Create the directory if it doesn't exist for the example to be runnable.
if not os.path.exists(NOTEBOOK_DIR):
    os.makedirs(NOTEBOOK_DIR)

def read_notebook_fixed(notebook_name: str) -> str:
    """
    Securely reads a notebook file, preventing path traversal.

    This function represents a potential fix for the vulnerability described in
    CVE-2026-7810, where user input for a filename could be manipulated
    to access arbitrary files on the server.
    """
    if not notebook_name or ".." in notebook_name or "/" in notebook_name:
        # Basic sanitization: disallow path traversal characters and separators.
        raise ValueError("Invalid notebook name provided.")

    # Construct the full path to the requested notebook.
    # os.path.join is used, but it does not by itself prevent traversal.
    requested_path = os.path.join(NOTEBOOK_DIR, notebook_name)

    # **THE FIX:**
    # 1. Resolve the absolute, canonical path of the user's request.
    #    This collapses any '..' or other relative path components.
    #    e.g., /app/notebooks/../secret/file -> /app/secret/file
    real_path = os.path.abspath(requested_path)

    # 2. Check if the resolved path is still within the intended NOTEBOOK_DIR.
    #    os.path.commonpath is a reliable way to verify this containment.
    #    If the common path of the secure base and the user's path is not
    #    the secure base itself, it's a traversal attempt.
    if os.path.commonpath([real_path, NOTEBOOK_DIR]) != NOTEBOOK_DIR:
        raise PermissionError("Path traversal attempt detected.")

    # 3. After validation, proceed with the file operation.
    if not os.path.isfile(real_path):
        raise FileNotFoundError("Notebook not found.")

    with open(real_path, 'r', encoding='utf-8') as f:
        return f.read()

# Example usage (would typically be called from a web server handler):
if __name__ == '__main__':
    # Create a safe file to demonstrate a successful read
    with open(os.path.join(NOTEBOOK_DIR, "my_notebook.txt"), "w") as f:
        f.write("This is a safe notebook.")

    # --- Test Cases ---

    # 1. Legitimate access (should succeed)
    try:
        content = read_notebook_fixed("my_notebook.txt")
        print(f"Successfully read safe file:\n---\n{content}\n---\n")
    except (ValueError, PermissionError, FileNotFoundError) as e:
        print(f"Failed to read safe file: {e}\n")

    # 2. Path Traversal attempt (should fail)
    # This tries to access a file outside the NOTEBOOK_DIR.
    malicious_input = "../../../../../etc/passwd"
    try:
        read_notebook_fixed(malicious_input)
    except (ValueError, PermissionError, FileNotFoundError) as e:
        print(f"Correctly blocked malicious input '{malicious_input}': {e}")

Payload

../../../../../../../../etc/passwd

Cite this entry

@misc{vaitp:cve20267810,
  title        = {{Path traversal in python-notebook-mcp allows remote file manipulation.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-7810},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-7810/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::