Fix stored XSS via data-trix-serialized-attributes bypass#1282
Conversation
…H1 #3581911) Strip `data-trix-serialized-attributes` in the DOMPurify `uponSanitizeAttribute` hook before the `data-trix-*` force-keep logic runs. This attribute is unique among `data-trix-*` attributes: it is the only one whose value is later treated as trusted instructions to create arbitrary DOM attributes via `el.setAttribute(name, value)` in the serialization code. No other `data-trix-*` attribute has this XSS-enabling property: - `data-trix-attachment` content is re-sanitized when rendered in AttachmentView - `data-trix-attributes` is reduced to caption/presentation for attachment pieces - The remaining internal markers (data-trix-id, data-trix-store-key, etc.) are stripped or regenerated during serialization and never reach an executable sink The fix is therefore narrowly scoped to `data-trix-serialized-attributes`. The legitimate producer (PreviewableAttachmentView) sets this attribute at runtime on live DOM elements and never goes through DOMPurify, so it is unaffected.
|
Claude and Codex review summary: Review SessionArtifact
Reviewers
Thread ID: 019cddfc-0462-7fa2-b05a-6dc8957f2419 Round 1Self-Review (Implementer)
External Review (Codex)No H/M/L findings. Codex confirmed:
Reconciliation
Round 1 SynthesisConsensus:
Disagreements:
Actions:
Decision points: none this round. Open Questions:
Gate Status:
Mediator Approval: pending Final Synthesis
Attestation: pending |
There was a problem hiding this comment.
Pull request overview
Closes a stored XSS vector in Trix by ensuring data-trix-serialized-attributes is stripped during DOMPurify sanitization, preventing later attribute expansion from reintroducing attacker-controlled DOM attributes.
Changes:
- Rejects
data-trix-serialized-attributesin the DOMPurifyuponSanitizeAttributehook before thedata-trix-*force-keep logic applies. - Adds unit tests verifying the attribute is stripped while other
data-trix-*attributes remain. - Adds a system test covering an end-to-end paste payload that previously could yield an
onerrorattribute.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/trix/models/html_sanitizer.js | Adds DOMPurify hook logic to drop data-trix-serialized-attributes during sanitization. |
| src/test/unit/html_sanitizer_test.js | Adds unit coverage for stripping the specific attribute and preserving other data-trix-*. |
| src/test/system/pasting_test.js | Adds end-to-end regression coverage ensuring serialized output does not contain onerror. |
| action_text-trix/app/assets/javascripts/trix.js | Mirrors the sanitizer hook change in the packaged/bundled asset. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
|
|
||
| DOMPurify.addHook("uponSanitizeAttribute", function (node, data) { | ||
| if (data.attrName === "data-trix-serialized-attributes") { | ||
| data.keepAttr = false |
There was a problem hiding this comment.
DOMPurify gives forceKeepAttr precedence over keepAttr (see DOMPurify’s _sanitizeAttributes: it continues when forceKeepAttr is truthy before checking keepAttr). If another uponSanitizeAttribute hook runs earlier and sets forceKeepAttr = true for data-trix-*, this attribute would still be kept even after setting keepAttr = false here. To make the strip robust against earlier hooks, explicitly clear data.forceKeepAttr (set it to false/undefined) in this branch in addition to data.keepAttr = false.
| data.keepAttr = false | |
| data.keepAttr = false | |
| data.forceKeepAttr = false |
|
|
||
| purify.addHook("uponSanitizeAttribute", function (node, data) { | ||
| if (data.attrName === "data-trix-serialized-attributes") { | ||
| data.keepAttr = false; |
There was a problem hiding this comment.
Same issue as the source version: because DOMPurify checks forceKeepAttr before keepAttr, stripping this attribute is not robust if any earlier uponSanitizeAttribute hook set forceKeepAttr = true for data-trix-*. Clear data.forceKeepAttr in this branch (set to false/undefined) along with data.keepAttr = false to ensure the attribute is removed even if a previous hook forced it to be kept.
| data.keepAttr = false; | |
| data.keepAttr = false; | |
| data.forceKeepAttr = false; |
Summary
data-trix-serialized-attributesin the DOMPurifyuponSanitizeAttributehook before thedata-trix-*force-keep logic runs, closing a stored XSS vector (H1 #3581911)data-trix-*attribute whose value is later expanded into arbitrary DOM attributes viasetAttribute()in the serialization code — no otherdata-trix-*attribute has this propertyPreviewableAttachmentView) sets this attribute at runtime on live DOM elements and never goes through DOMPurify, so it is unaffectedTest plan
data-trix-serialized-attributesfrom sanitized HTMLdata-trix-*attributes (e.g.data-trix-attachment)data-trix-attachmentJSON →contentwithdata-trix-serialized-attributes) asserts serialized output has noonerrorattribute