VAITP Dataset

← Back to the dataset

CVE-2026-25640

Path traversal in Pydantic AI web UI allows XSS via a crafted URL.

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

Pydantic AI is a Python agent framework for building applications and workflows with Generative AI. From 1.34.0 to before 1.51.0, a path traversal vulnerability in the Pydantic AI web UI allows an attacker to serve arbitrary JavaScript in the context of the application by crafting a malicious URL. In affected versions, the CDN URL is constructed using a version query parameter from the request URL. This parameter is not validated, allowing path traversal sequences that cause the server to fetch and serve attacker-controlled HTML/JavaScript from an arbitrary source on the same CDN, instead of the legitimate chat UI package. If a victim clicks the link or visits it via an iframe, attacker-controlled code executes in their browser, enabling theft of chat history and other client-side data. This vulnerability only affects applications that use Agent.to_web to serve a chat interface and clai web to serve a chat interface from the CLI. These are typically run locally (on localhost), but may also be deployed on a remote server. This vulnerability is fixed in 1.51.0.

CVSS base score
5.4
Published
2026-02-06
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Pydantic AI
Fixed by upgrading
Yes

Solution

Upgrade Pydantic AI to version 1.51.0 or later.

Vulnerable code sample

import os
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse

# This is a placeholder for the actual library version.
# The vulnerability existed in versions from 1.34.0 to before 1.51.0.
__version__ = "1.50.0"

class Agent:
    """
    This is a simplified representation of the pydantic_ai.Agent class
    to demonstrate the vulnerable web UI generation logic.
    """

    def to_web(self):
        """
        This method returns a web application handler that contains the
        vulnerable endpoint logic.
        """
        class Web:
            """
            This inner class simulates the web server component.
            """
            def _entrypoint(self, request: Request) -> "Response":
                """
                This is the vulnerable endpoint. It constructs a CDN URL
                using a query parameter without any validation or sanitization,
                allowing for path traversal.
                """
                # The 'version' is taken directly from the URL query parameter.
                # No validation is performed, allowing an attacker to inject ".."
                # sequences.
                version = request.query_params.get("version", __version__)

                # The unvalidated 'version' is used to construct the CDN URL.
                # A malicious URL like /?version=1.0.0/../../@attacker/malicious-package@1.0.0
                # would cause the server to serve JS from the attacker's package on the same CDN.
                cdn_url = f"https://cdn.jsdelivr.net/npm/@pydantic/chat-ui@{version}/dist"

                html_content = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Pydantic AI Chat</title>
        <link rel="stylesheet" href="{cdn_url}/assets/index.css">
    </head>
    <body>
        <div id="root"></div>
        <script type="module" src="{cdn_url}/index.js"></script>
    </body>
    </html>
                """
                return HTMLResponse(content=html_content)

            def app(self) -> FastAPI:
                """
                Creates a FastAPI application with the vulnerable endpoint.
                """
                fastapi_app = FastAPI()
                fastapi_app.add_api_route("/", self._entrypoint, methods=["GET"])
                return fastapi_app

        return Web()

# Example of how the vulnerable application would be set up and run.
# To exploit, a user would be directed to a URL like:
# http://127.0.0.1:8000/?version=1.0.0/../../@popperjs/core@2.11.8
# This would load JavaScript from the popperjs package instead of the intended chat-ui.

if __name__ == "__main__":
    import uvicorn

    agent = Agent()
    app = agent.to_web().app()

    print("Starting vulnerable server on http://127.0.0.1:8000")
    print("To test the vulnerability, try accessing:")
    print("http://127.0.0.1:8000/?version=1.0.0/../../@popperjs/core@2.11.8")
    print("Check the browser's developer tools to see network requests for popper.js.")

    uvicorn.run(app, host="0.0.0.0", port=8000)

Patched code sample

import re

def get_safe_chat_ui_url(version_param: str) -> str:
    """
    Represents the fixed server-side logic that prevents path traversal
    (CVE-2024-25640) when constructing a CDN URL for the Pydantic AI web UI.

    The vulnerability existed because the 'version' parameter from a request URL
    was used directly in the CDN URL without proper validation.
    """
    # THE FIX: Validate the 'version' parameter to ensure it only contains
    # characters that are valid for a package version string. This regex
    # allows letters, numbers, dots, and hyphens, which are common in
    # version tags like "1.51.0", "latest", or "1.50.0-beta.1".
    #
    # Crucially, it disallows the slash character ('/'), which is necessary
    # for path traversal attacks like '../../malicious-package'.
    if not re.match(r"^[a-zA-Z0-9.-]+$", version_param):
        # In a real web server, this logic would lead to returning an
        # HTTP 400 Bad Request error to the client. For this demonstration,
        # we raise a ValueError to indicate that the invalid input was rejected.
        raise ValueError("Invalid version parameter. Path traversal characters are not allowed.")

    # If the version parameter passes validation, it is now safe to construct the URL.
    # A malicious input like "../../another-package@1.0.0" would have already
    # been caught by the validation check above.
    base_cdn_url = "https://cdn.jsdelivr.net/npm/@pydantic-ai/chat-ui"
    return f"{base_cdn_url}@{version_param}/dist/index.html"

# --- Demonstration of the fix ---

# This is how the vulnerable code would have behaved:
malicious_version_string = "../../some-malicious-package/1.0.0"
vulnerable_url = f"https://cdn.jsdelivr.net/npm/@pydantic-ai/chat-ui@{malicious_version_string}/dist/index.html"
print(f"Vulnerable URL construction would produce: {vulnerable_url}\n")


# This is how the fixed code behaves:

# 1. Attempting with a malicious path traversal string.
try:
    print(f"Attempting to generate URL with malicious version: '{malicious_version_string}'")
    get_safe_chat_ui_url(malicious_version_string)
except ValueError as e:
    print(f"  -> SUCCESS: The fix correctly blocked the malicious input. Error: {e}\n")

# 2. Attempting with a legitimate version string.
try:
    legitimate_version_string = "1.51.0"
    print(f"Attempting to generate URL with legitimate version: '{legitimate_version_string}'")
    safe_url = get_safe_chat_ui_url(legitimate_version_string)
    print(f"  -> SUCCESS: A safe URL was generated: {safe_url}\n")
except ValueError as e:
    print(f"  -> FAILED (Unexpected): {e}\n")

Payload

http://localhost:8000/?version=../../some-malicious-package@1.0.0

Cite this entry

@misc{vaitp:cve202625640,
  title        = {{Path traversal in Pydantic AI web UI allows XSS via a crafted URL.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-25640},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-25640/}}
}
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 ::