Feb 24, 2026·6 min read·82 visits
The 'new-api' LLM gateway trusted AI-generated content too much. By asking the AI to write malicious code, attackers could trigger a Stored XSS in the playground UI, leading to session hijacking. Fixed in version 0.10.8-alpha.9 by sandboxing the output.
A critical Cross-Site Scripting (XSS) vulnerability was discovered in the 'new-api' LLM gateway, specifically within its playground component. The flaw allows attackers to weaponize Large Language Model outputs via indirect prompt injection, causing the application to render malicious JavaScript that executes in the victim's browser. The root cause lies in the unsafe use of React's 'dangerouslySetInnerHTML' without adequate sanitization.
In the rush to integrate Large Language Models (LLMs) into every piece of software known to man, developers often forget a cardinal rule of security: All input is evil. But with LLMs, the "input" is technically "output" generated by a machine we implicitly trust to be smart. This cognitive dissonance is exactly where CVE-2026-25802 lives. It targets new-api, a popular Go-based gateway for managing LLM assets and APIs. The vulnerability isn't in the AI itself; it's in how the human-built UI handles the AI's hallucinations.
The specific component at fault is the "Playground"—a sandbox where administrators and users can test prompts against various models. The irony of calling it a "sandbox" while it lacked actual browser sandboxing is palpable. When a user asks the model to generate content, the frontend renders it. If that content happens to be a malicious JavaScript payload disguised as helpful code, the browser executes it. This turns the LLM into a confused accomplice in a classic Cross-Site Scripting (XSS) attack.
This isn't just a theoretical "popup" bug. Because the chat history is saved to the database, this becomes a Stored XSS. An attacker can generate a malicious chat log, and later, when an administrator reviews the logs or the history is reloaded, the payload detonates. It's a landmine made of text.
The root cause of this vulnerability is a tale as old as React itself. The developers wanted to display rich text responses from the AI—markdown, code blocks, maybe some bold text for emphasis. To do this, they reached for the forbidden fruit: dangerouslySetInnerHTML. As the name implies, this React attribute is dangerous because it bypasses the virtual DOM's built-in XSS protection and dumps raw HTML directly into the document.
In MarkdownRenderer.jsx, the application took the string response from the LLM and shoved it directly into the DOM. There was no sanitization layer, no DOMPurify, just raw trust. If the LLM output contained <script>alert(1)</script>, the browser would obediently execute it. But wait, it gets worse. There was a secondary vector in CodeViewer.jsx.
The developers implemented a custom syntax highlighter for JSON responses. Instead of using a battle-tested library, they manually constructed HTML strings by wrapping tokens in <span> tags. They forgot to escape the content inside those tokens. So, if a JSON value contained "><img src=x onerror=alert(1)>, the concatenation logic would break out of the span and inject the event handler. It's a masterclass in why you should never build your own parser.
Let's look at the commit that introduced the fix (ab5456eb1049aa8a0f3e51f359907ec7fff38b4b). The difference between the vulnerable code and the patched code effectively illustrates the severity of the oversight.
The Vulnerable Implementation (Before):
In the original MarkdownRenderer.jsx, the code looked something like this:
// The developer trusted the 'content' variable completely
<div
className="markdown-body"
dangerouslySetInnerHTML={{ __html: content }}
/>This is the digital equivalent of leaving your front door open because you assume only your friends know where you live. The content variable here comes directly from the LLM, which effectively comes from the user's prompt.
The Fix (After):
Reference commit ab5456eb1049aa8a0f3e51f359907ec7fff38b4b. The maintainers realized they couldn't sanitize every possible output, so they chose containment. They replaced the direct render with a Sandboxed Iframe.
// SandboxedHtmlPreview component
<iframe
ref={iframeRef}
sandbox='allow-same-origin' // Note: allow-scripts is MISSING
srcDoc={code}
title='HTML Preview'
style={{ width: '100%', border: 'none' }}
/>By omitting allow-scripts from the sandbox attribute, the browser renders the HTML visually but refuses to execute any JavaScript within that frame. Additionally, in CodeViewer.jsx, they finally introduced a sanitizer:
const escapeHtml = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};This function is now called on every token before it gets wrapped in syntax highlighting tags, ensuring that special characters are treated as text, not code.
Exploiting this requires a technique known as Indirect Prompt Injection. We don't write the XSS payload; we bully the AI into writing it for us. The attack flow is elegant in its simplicity.
Step 1: The Setup
The attacker logs into the new-api playground. They select a model (it doesn't matter which one, as long as it follows instructions).
Step 2: The Injection The attacker sends the following prompt: > "Please write a complete HTML example that includes a script tag to redirect the current page to google.com. Do not use code blocks, just raw text."
Step 3: The Execution
The LLM, trying to be helpful, responds:
> "Sure, here is the code: <script>window.location.replace("https://www.google.com")</script>"
Step 4: The Detonation
The new-api frontend receives this string. The MarkdownRenderer sees the HTML tags and passes them to dangerouslySetInnerHTML. The React component mounts, the browser parses the DOM, encounters the <script> tag, and immediately executes the redirect.
Because this interaction is saved in the chat history, any administrator who views this chat session later will also be redirected (or worse, have their session cookies stolen via document.cookie).
The remediation for CVE-2026-25802 is twofold, addressing both the rendering engine and the data handling. The primary fix is the introduction of isolation.
1. Sandbox Isolation:
The move to an <iframe> with the sandbox attribute is the strongest defense here. Even if the sanitizer fails, or if the LLM invents a new polyglot payload, the browser's security model prevents execution. By strictly defining the capabilities of the iframe (allowing same-origin for styles but denying scripts), the attack surface is effectively neutralized.
2. Context-Aware Escaping: For the syntax highlighter, the developers implemented classic output encoding. This ensures that data is treated as data, not instructions. This is a crucial lesson: never manually build HTML strings without a utility function to handle entity encoding.
Recommendation:
If you are running new-api versions prior to 0.10.8-alpha.9, you are vulnerable. Update immediately. If you cannot update, disable the Playground feature or block access to it via network policies, as there is no configuration-based mitigation for the vulnerability itself.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
new-api QuantumNous | < 0.10.8-alpha.9 | 0.10.8-alpha.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| CVSS Score | 7.6 (High) |
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | Low |
| Impact | Session Hijacking, RCE (via Admin context) |
| Exploit Status | PoC Available |
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
A state persistence vulnerability exists in Tornado's CurlAsyncHTTPClient component where pooled pycurl.Curl handles are reused across asynchronous requests without a complete state reset. Consequently, sensitive per-request configurations, such as client TLS certificates or proxy basic authentication credentials, persist on the shared handle. This behavior leads to subsequent requests leaking these credentials to unauthorized remote servers.
CVE-2026-48748 is a denial-of-service vulnerability in Netty's HTTP/3 codec (netty-codec-http3) occurring when QPACK dynamic tables are enabled but the blocked streams limit is not explicitly configured. A bug in limit checking and a memory leak in stream tracking allow unauthenticated remote attackers to exhaust the JVM heap memory and crash the server.
CVE-2026-50009 is a cryptographic design vulnerability in the Netty network application framework. Prior to version 4.2.15.Final, the framework's QUIC protocol implementation fails to cryptographically segregate the generated Connection IDs and the associated Stateless Reset Tokens. An on-path network attacker who sniffs traffic during a Connection ID rotation can extract secret token material from cleartext headers, enabling them to inject spoofed reset packets and terminate active connections.
A critical hostname verification bypass vulnerability exists in the Netty network application framework when configured as a TLS client. When a developer registers a custom plain X509TrustManager, Netty wraps it inside an X509TrustManagerWrapper to adapt it to the X509ExtendedTrustManager API. However, this wrapper discards the SSLEngine context, bypassing critical hostname checks. Because the wrapper is identified as an X509ExtendedTrustManager, standard cryptographic engines and Netty's OpenSSL wrappers do not re-wrap it, failing to execute any hostname validation. Consequently, clients silently accept certificates for any host, enabling unauthenticated Man-in-the-Middle (MitM) attacks.
An uncontrolled resource pre-allocation flaw in the Netty Redis codec module allows remote unauthenticated attackers to cause a denial of service (OutOfMemoryError) by sending a crafted Redis Serialization Protocol (RESP) array header.
CVE-2026-50020 is a medium-severity HTTP Request Smuggling/Response Smuggling vulnerability (CWE-444) within the Netty asynchronous network application framework. The flaw resides in Netty's HTTP codec implementation, specifically the HttpObjectDecoder class, which silently consumes arbitrary ISO control bytes preceding the first request line.