Reflected XSS

Author: HK4zCziCreated Aug 11, 2026Updated Aug 11, 2026

CVE Request — GHSA-jx8r-j32j-q3mq

Description

Reflected Cross-Site Scripting (XSS) vulnerability in the embedded MCP HTTP server of next-ai-draw-io. The server interpolates the ?mcp= query parameter directly into a <script> block and into raw HTML without sanitization or encoding, allowing arbitrary JavaScript execution in the localhost origin when a victim visits a crafted URL.

Affected Product

Field Value
Ecosystem npm
Package @next-ai-drawio/mcp-server
Affected versions <= 0.2.1 (bundled in next-ai-draw-io <= 0.4.16)
Patched versions (pending — to be confirmed once fix is released)

Weakness

CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-Site Scripting')

Severity (CVSS v3.1)

  • Score: 6.1 (Moderate)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Summary

The MCP embedded HTTP server interpolates the ?mcp= query parameter directly into a <script> block and into raw HTML without any sanitization or encoding. An attacker who can trick a user into visiting a crafted http://localhost:<port>/?mcp=… URL causes arbitrary JavaScript to execute in the localhost origin, where it can read and exfiltrate every diagram session stored by the server.


Details

packages/mcp-server/src/http-server.ts extracts the query parameter at line 264:

typescript
// line 264
const sessionId = url.searchParams.get("mcp") || ""

sessionId is then interpolated unescaped in two places:

Sink 1 — Inside a <script> block (line 700, JS injection)

typescript
// line 700 — inside getHtmlPage(sessionId)
const sessionId = "${sessionId}";

A value such as ";alert(1);// closes the string literal, injects arbitrary statements, and comments out the remainder of the line.

Sink 2 — Inside HTML markup (line 638, HTML injection)

typescript
// line 638
${sessionId ? `<span class="session">${sessionId.slice(-8)}</span>` : ""}

A value containing <img src=x onerror=…> is rendered verbatim into the page.

Neither encodeURIComponent, encodeHTML, nor any escaping function is applied before either interpolation.


Proof of Concept

Prerequisites: the MCP HTTP server is running locally (default port range 6002–6020, or any custom port).

  1. Identify the port (displayed in the VS Code sidebar or MCP server logs).
  2. Open the following URL in any browser while the server is running:
http://localhost:<PORT>/?mcp=%22%3Balert(document.domain)%3B%2F%2F

URL-decoded payload: ";alert(document.domain);//

  1. The page loads and the browser shows an alert() dialog displaying the origin (http://localhost:<PORT>) without any user interaction beyond navigating to the URL.
Image

Delivery vector: embed the URL inside a malicious webpage hosted on an attacker-controlled domain:

xml
<iframe src="http://localhost:6002/?mcp=%22%3Balert(document.origin)%3B%2F%2F"></iframe>

Or via window.open(…). When the victim visits the page while the MCP server is running, the payload executes automatically.


Impact

Dimension Detail
Who is affected Any user running @next-ai-drawio/mcp-server who visits an attacker-controlled webpage while the MCP HTTP server is active
Attacker prerequisite Victim visits a malicious page — no credentials or local access required
Consequence Injected JavaScript executes in the localhost origin and can call any /api/* endpoint (read all diagram XML, session history, model configuration) and exfiltrate data to an external server without further user interaction

Recommended Fix

HTML-encode sessionId before any interpolation into HTML or JavaScript:

typescript
function escapeHtml(s: string): string {
    return s.replace(/&/g, "&amp;").replace(/</g, "&lt;")
            .replace(/>/g, "&gt;").replace(/"/g, "&quot;")
            .replace(/'/g, "&#x27;")
}

function escapeJs(s: string): string {
    return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
            .replace(/'/g, "\\'").replace(/</g, "\\x3C")
            .replace(/\r?\n/g, "\\n")
}

// line 700 — JS context
const sessionId = "${escapeJs(sessionId)}";

// line 638 — HTML context
${sessionId ? `<span class="session">${escapeHtml(sessionId.slice(-8))}</span>` : ""}

Reporter: @HK4zCzi (Hồ Việt Khánh)

Source: DayuanJiang/next-ai-draw-io