Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix stored XSS via data-trix-serialized-attributes bypass#1282

Merged
flavorjones merged 1 commit into
mainfrom
h1-3581911-serialized-attr
Mar 11, 2026
Merged

Fix stored XSS via data-trix-serialized-attributes bypass#1282
flavorjones merged 1 commit into
mainfrom
h1-3581911-serialized-attr

Conversation

@flavorjones
Copy link
Copy Markdown
Member

Summary

  • Strips data-trix-serialized-attributes in the DOMPurify uponSanitizeAttribute hook before the data-trix-* force-keep logic runs, closing a stored XSS vector (H1 #3581911)
  • This is the only data-trix-* attribute whose value is later expanded into arbitrary DOM attributes via setAttribute() in the serialization code — no other data-trix-* attribute has this property
  • The legitimate producer (PreviewableAttachmentView) sets this attribute at runtime on live DOM elements and never goes through DOMPurify, so it is unaffected

Test plan

  • Unit test: DOMPurify strips data-trix-serialized-attributes from sanitized HTML
  • Unit test: DOMPurify preserves other data-trix-* attributes (e.g. data-trix-attachment)
  • System test: end-to-end paste of three-level attack payload (pasted HTML → data-trix-attachment JSON → content with data-trix-serialized-attributes) asserts serialized output has no onerror attribute
  • All tests verified to fail without the fix and pass with it
  • Full test suite passes (497 tests, 0 failures)

…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.
Copilot AI review requested due to automatic review settings March 11, 2026 17:42
Comment thread src/test/system/pasting_test.js Dismissed
@flavorjones
Copy link
Copy Markdown
Member Author

Claude and Codex review summary:

Review Session

Artifact

  • Description: Fix for stored XSS via data-trix-serialized-attributes sanitizer bypass (H1 #3581911)
  • Scope: The DOMPurify hook change in html_sanitizer.js, unit tests in html_sanitizer_test.js, system test in pasting_test.js
  • Quality bar: No bypass potential, no regression to legitimate functionality, sufficient test coverage

Reviewers

  • Codex: Security-focused code reviewer

Thread ID: 019cddfc-0462-7fa2-b05a-6dc8957f2419


Round 1

Self-Review (Implementer)

  • L: System test payload is dense due to triple-escaped HTML entities — but matches existing test patterns in the file
  • L: Could add a test verifying data-trix-serialized-attributes still works when set programmatically (the legitimate PreviewableAttachmentView path), but existing fixture-based serialization tests cover this implicitly

External Review (Codex)

No H/M/L findings. Codex confirmed:

  • Fix is correct: the early return for data-trix-serialized-attributes removes the only data-trix-* attribute that is later treated as arbitrary attribute instructions via setAttribute()
  • No practical bypass: data.attrName is compared against the literal attribute name before the broad force-keep rule, and there is no second consumer of this attribute outside serialization
  • Low regression risk: legitimate producer is internal runtime code in PreviewableAttachmentView, not sanitized paste input
  • Test coverage is solid: unit test proves hook behavior directly, system test exercises full three-layer payload
  • Residual note (not a finding): serialization.js still treats data-trix-serialized-attributes as trusted internal state — acceptable for this patch but remains a sensitive sink

Reconciliation

Issue Self-Review Codex Agreement
Fix correctness No issues found No issues found Agree
Bypass potential Not examined deeply No practical bypass found Agree
Regression risk Low (legitimate path unaffected) Low (internal runtime code, not paste) Agree
Test coverage Sufficient (L: could add programmatic test) Solid as-is Agree
serialization.js sink Not flagged Noted as residual (not a finding) Agree — out of scope for this patch

Round 1 Synthesis

Consensus:

  • Fix correctly closes the XSS vector
  • No bypass potential identified
  • No regression risk to legitimate functionality
  • Test coverage is sufficient with both unit and system tests
  • serialization.js remains a sensitive sink but is acceptable for this scoped fix

Disagreements:

  • None

Actions:

  • None required

Decision points: none this round.

Open Questions:

  • None

Gate Status:

  • Open high-severity items? no
  • Open medium items accepted? N/A
  • All actions addressed? yes
  • Ready to close? yes

Mediator Approval: pending


Final Synthesis

  • Decision: Ship the fix as-is. The 4-line DOMPurify hook change correctly strips the only dangerous data-trix-serialized-attributes from untrusted input while leaving the legitimate runtime path unaffected.
  • Risks accepted: serialization.js still treats data-trix-serialized-attributes as a trusted sink — acceptable because the input boundary is now defended.
  • Next steps: Merge and release.

Attestation: pending

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-attributes in the DOMPurify uponSanitizeAttribute hook before the data-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 onerror attribute.

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
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
data.keepAttr = false
data.keepAttr = false
data.forceKeepAttr = false

Copilot uses AI. Check for mistakes.

purify.addHook("uponSanitizeAttribute", function (node, data) {
if (data.attrName === "data-trix-serialized-attributes") {
data.keepAttr = false;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
data.keepAttr = false;
data.keepAttr = false;
data.forceKeepAttr = false;

Copilot uses AI. Check for mistakes.
@flavorjones flavorjones merged commit 53197ab into main Mar 11, 2026
39 of 40 checks passed
@flavorjones flavorjones deleted the h1-3581911-serialized-attr branch March 11, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants