Safeguard
AppSec

react-quill and Quill: XSS History and Safe Rich-Text Editing

Using Quill in React means understanding CVE-2021-3163, the react-quill maintenance gap, and why editor output must always be sanitized server-side before display.

Priya Mehta
Security Analyst
6 min read

Running Quill in a React app is safe only if you accept one architectural rule up front: rich-text editor output is untrusted user input, and it must be sanitized server-side before it is ever stored or rendered — because the Quill react ecosystem has both a documented XSS advisory (CVE-2021-3163) and a wrapper-package maintenance gap that left many apps pinned to the affected editor version for years. This post walks through what actually happened, which package versions are involved, and the rendering architecture that makes the whole question of editor CVEs much less scary.

The stack: Quill, react-quill, and the version trap

Quill is the editor engine; the npm react-quill package is the most popular React wrapper around it. The version trap catches almost everyone:

  • react-quill 2.0.0 — the wrapper's own 2.0, published back in 2022 — bundles Quill 1.3.7, not Quill 2.
  • Quill itself shipped its major 2.0 release in April 2024, with performance work, security fixes, and replacement of a deprecated DOM API.
  • The original react-quill wrapper never updated to Quill 2. The community continuation is react-quill-new, a fork that upgrades the bundled editor to Quill 2.0.x and adds current TypeScript support.

So a team that installed npm react-quill last year and saw "2.0.0, latest" reasonably assumed they were on a current editor. They were on a 2017-era engine core with a known advisory against it. Version numbers of wrappers tell you nothing about the engine inside — your lockfile does. Check what actually resolved:

npm ls quill

CVE-2021-3163, plainly

CVE-2021-3163 describes a cross-site scripting issue in Quill versions up to and including 1.3.7: crafted HTML pasted or injected into the editor — the published example abuses an onloadstart attribute on an image element — could result in attacker-supplied JavaScript executing in the context of the page. NVD scores it moderate, and it's worth noting the maintainers disputed aspects of it, arguing the behavior belongs to the browser's handling of markup rather than a flaw in Quill's own code.

The dispute is actually the most instructive part. Whether you side with the reporter or the maintainer, the practical lesson is identical: a WYSIWYG editor is an HTML-construction surface, and no editor's internal filtering is a security boundary. Editors are designed to preserve formatting faithfully; attackers exploit exactly that faithfulness. If your threat model depends on the editor never letting an event-handler attribute through, you have no threat model — you have a hope.

The architecture that makes editor CVEs survivable

Three rules, applied together, mean the next editor advisory is a routine patch instead of an incident:

1. Prefer storing Deltas over raw HTML. Quill's native document format is the Delta — a JSON structure of insert operations and attributes. Deltas constrain content to a known vocabulary and are far easier to validate than arbitrary HTML:

// what the editor gives you
const delta = quillRef.current.getEditor().getContents();
// store this — a JSON structure you can schema-validate —
// and render it to HTML yourself, server-side, from the allowlisted ops

2. Sanitize server-side at write time, and treat render-time as defense in depth. Wherever HTML enters your system — whether generated from Deltas or accepted from the editor — run it through a maintained allowlist sanitizer (DOMPurify on a JSDOM instance, or the xss package) on the server. Client-side-only sanitization can be bypassed by anyone who talks to your API directly, which is to say: by attackers. We've written up allowlist configuration in detail in our xss npm package guide.

3. Never dangerouslySetInnerHTML unsanitized stored content. The React escape hatch is named honestly. Every render path for user-authored rich text should flow through the same sanitizer, so a document stored before your sanitization existed still can't execute today.

Add a content security policy that forbids inline script as the final backstop, and stored XSS via the editor requires defeating three independent layers.

Patching guidance for existing apps

If you're on react-quill today:

  1. Confirm your resolved Quill version with npm ls quill. If it's 1.3.7 (it is, under react-quill 2.0.0), you're carrying the CVE-2021-3163-affected engine.
  2. Migrate to react-quill-new to get Quill 2.0.x. For most codebases it's an import swap plus a pass over custom modules/formats, since Quill 2 changed some module registration and clipboard behaviors. Test paste-from-Word flows specifically; that's where rich-text regressions hide.
  3. If you can't migrate yet, your compensating controls are exactly the architecture above — server-side sanitization makes the editor-layer issue unreachable in practice. This is also the honest answer for scanner findings: a version-range match on quill 1.3.7 with strict output sanitization is real but mitigated, and reachability-aware tooling like Safeguard's SCA lets you document that triage instead of playing whack-a-mole with the same finding every sprint.
  4. Verify the fix from the outside. A DAST pass that submits event-handler-attribute payloads through your actual editor save/render flow proves the sanitization pipeline works end to end — the test that matters more than any version number.

Evaluating rich-text editors as dependencies

The react-quill story generalizes into a checklist for any editor dependency:

  • Engine vs wrapper: identify both, and track advisories against the engine, which is where content handling lives.
  • Maintenance liveness: a wrapper that stops tracking its engine's majors (as react-quill did across the Quill 2 release) silently freezes your security posture. Fork activity — like react-quill-new absorbing the community — is the signal to watch.
  • Output format: editors with a constrained document model (Delta, ProseMirror's schema'd JSON, Lexical's editor state) beat raw-HTML editors for validation.
  • Sanitization stance: read what the project itself says. Good projects tell you plainly that output must be sanitized downstream; treat any claim of "XSS-safe output" with suspicion.

FAQ

Is react-quill safe to use in production?

Yes, provided you treat its output as untrusted: sanitize server-side with an allowlist sanitizer, avoid rendering stored HTML unsanitized, and prefer the react-quill-new fork so your bundled Quill engine is 2.0.x rather than 1.3.7.

What exactly is CVE-2021-3163?

A moderate-severity XSS advisory against Quill 1.3.7 and earlier, where crafted markup (the public example uses an image element's onloadstart attribute) could execute script. Maintainers partially disputed it as browser behavior — which changes nothing about the correct mitigation: sanitize editor output.

Should I store Quill content as HTML or Delta?

Delta, where feasible. It's a constrained JSON format you can schema-validate, it survives editor version changes better, and you control the HTML rendering step — which is exactly where sanitization belongs.

Does sanitizing on the client with the editor's own filters suffice?

No. Anything enforced only in the browser is optional for an attacker with an HTTP client. Client-side filtering improves UX; the security control is the server-side sanitizer on write and render paths.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.