VYPR
Low severityOSV Advisory· Published May 8, 2025· Updated Apr 15, 2026

CVE-2025-46812

CVE-2025-46812

Description

Trix is a what-you-see-is-what-you-get rich text editor for everyday writing. Versions prior to 2.1.15 are vulnerable to XSS attacks when pasting malicious code. An attacker could trick a user to copy and paste malicious code that would execute arbitrary JavaScript code within the context of the user's session, potentially leading to unauthorized actions being performed or sensitive information being disclosed. This issue has been patched in version 2.1.15.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
trixnpm
< 2.1.152.1.15

Affected products

1

Patches

2
752260896468

Merge commit from fork

https://github.com/basecamp/trixDonal McBreenMay 8, 2025via ghsa
4 files changed · +25 7
  • src/test/system/pasting_test.js+15 0 modified
    @@ -119,6 +119,21 @@ testGroup("Pasting", { template: "editor_empty" }, () => {
         delete window.unsanitized
       })
     
    +  test("paste data-trix-attachment unsafe html div overload", async () => {
    +    window.unsanitized = []
    +    const pasteData = {
    +      "text/plain": "x",
    +      "text/html": `\
    +      <div data-trix-attachment="{&quot;contentType&quot;:&quot;text/html5&quot;,&quot;content&quot;:&quot;<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><a><svg><desc><svg><image><a><desc><svg><image></image></svg></desc></a></image><style><a data-trix-caca='</style><img src=x onerror=window.unsanitized.push(1)>'></a></style></svg></desc></svg></a>&quot;}"></div>
    +      `,
    +    }
    +
    +    await pasteContent(pasteData)
    +    await delay(20)
    +    assert.deepEqual(window.unsanitized, [])
    +    delete window.unsanitized
    +  })
    +
       test("paste data-trix-attachment encoded mathml", async () => {
         window.unsanitized = []
         const pasteData = {
    
  • src/trix/models/composition.js+1 1 modified
    @@ -127,7 +127,7 @@ export default class Composition extends BasicObject {
       }
     
       insertHTML(html) {
    -    const document = HTMLParser.parse(html).getDocument()
    +    const document = HTMLParser.parse(html, { purifyOptions: { SAFE_FOR_XML: true } }).getDocument()
         const selectedRange = this.getSelectedRange()
     
         this.setDocument(this.document.mergeDocumentAtRange(document, selectedRange))
    
  • src/trix/models/html_parser.js+3 2 modified
    @@ -66,10 +66,11 @@ export default class HTMLParser extends BasicObject {
         return parser
       }
     
    -  constructor(html, { referenceElement } = {}) {
    +  constructor(html, { referenceElement, purifyOptions } = {}) {
         super(...arguments)
         this.html = html
         this.referenceElement = referenceElement
    +    this.purifyOptions = purifyOptions
         this.blocks = []
         this.blockElements = []
         this.processedElements = []
    @@ -84,7 +85,7 @@ export default class HTMLParser extends BasicObject {
       parse() {
         try {
           this.createHiddenContainer()
    -      HTMLSanitizer.setHTML(this.containerElement, this.html)
    +      HTMLSanitizer.setHTML(this.containerElement, this.html, { purifyOptions: this.purifyOptions })
           const walker = walkTree(this.containerElement, { usingFilter: nodeFilter })
           while (walker.nextNode()) {
             this.processNode(walker.currentNode)
    
  • src/trix/models/html_sanitizer.js+6 4 modified
    @@ -16,8 +16,8 @@ const DEFAULT_FORBIDDEN_PROTOCOLS = "javascript:".split(" ")
     const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form noscript".split(" ")
     
     export default class HTMLSanitizer extends BasicObject {
    -  static setHTML(element, html) {
    -    const sanitizedElement = new this(html).sanitize()
    +  static setHTML(element, html, options) {
    +    const sanitizedElement = new this(html, options).sanitize()
         const sanitizedHtml = sanitizedElement.getHTML ? sanitizedElement.getHTML() : sanitizedElement.outerHTML
         element.innerHTML = sanitizedHtml
       }
    @@ -28,18 +28,20 @@ export default class HTMLSanitizer extends BasicObject {
         return sanitizer
       }
     
    -  constructor(html, { allowedAttributes, forbiddenProtocols, forbiddenElements } = {}) {
    +  constructor(html, { allowedAttributes, forbiddenProtocols, forbiddenElements, purifyOptions } = {}) {
         super(...arguments)
         this.allowedAttributes = allowedAttributes || DEFAULT_ALLOWED_ATTRIBUTES
         this.forbiddenProtocols = forbiddenProtocols || DEFAULT_FORBIDDEN_PROTOCOLS
         this.forbiddenElements = forbiddenElements || DEFAULT_FORBIDDEN_ELEMENTS
    +    this.purifyOptions = purifyOptions || {}
         this.body = createBodyElementForHTML(html)
       }
     
       sanitize() {
         this.sanitizeElements()
         this.normalizeListElementNesting()
    -    DOMPurify.setConfig(config.dompurify)
    +    const purifyConfig = Object.assign({}, config.dompurify, this.purifyOptions)
    +    DOMPurify.setConfig(purifyConfig)
         this.body = DOMPurify.sanitize(this.body)
     
         return this.body
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.