Keep a __proto__ key in attachment JSON out of Hash's prototype chain - #1352
Open
rosa wants to merge 1 commit into
Open
Keep a __proto__ key in attachment JSON out of Hash's prototype chain#1352rosa wants to merge 1 commit into
rosa wants to merge 1 commit into
Conversation
parseTrixDataAttribute() hands raw JSON.parse() output to Hash, and JSON.parse materializes "__proto__" as an ordinary own key. Object.keys() therefore yields it, and copy()'s plain assignment invoked the inherited setter, replacing the new object's prototype with the parsed value rather than storing it as data. The consequence was an asymmetry rather than global pollution: Object.prototype was untouched, but Hash.get() and Hash.has() resolved the smuggled names through the prototype chain while Object.keys(), toObject() and toJSON() could not see them. An embedder inspecting attachment attributes with own-key operations and gating on the result would pass a payload that Trix then read back in full. Define the property instead of assigning it, so "__proto__" is stored as an ordinary own key. What Trix reads and what callers inspect now agree. Object.create(null) was the other candidate and fixes the same bug, but it changes the prototype of every object handed back through toObject(), so embedders calling attributes.hasOwnProperty(...) would break. Defining the property keeps that surface intact. Reported by @bekkaze via GitHub Security Advisory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Hash mutation paths still use plain assignment and can reproduce the same prototype substitution.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Hardens Hash against __proto__ keys parsed from attachment JSON.
Changes:
- Uses property definition instead of assignment during copying.
- Adds regression tests and registers the new test suite.
- Rebuilds the vendored Action Text bundle.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
File summaries
| File | Description |
|---|---|
src/trix/core/collections/hash.js |
Safely copies __proto__ as an own property. |
src/test/unit/hash_test.js |
Adds prototype-substitution regression tests. |
src/test/unit.js |
Registers the Hash tests. |
action_text-trix/app/assets/javascripts/trix.js |
Updates the vendored implementation. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // inherited setter, replacing result's prototype with the parsed value instead of | ||
| // storing it. Hash then reads that value through get()/has(), while callers | ||
| // inspecting the same data with Object.keys() or toObject() cannot see it. | ||
| Object.defineProperty(result, key, { |
| // inherited setter, replacing result's prototype with the parsed value instead of | ||
| // storing it. Hash then reads that value through get()/has(), while callers | ||
| // inspecting the same data with Object.keys() or toObject() cannot see it. | ||
| Object.defineProperty(result, key, { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
parseTrixDataAttribute()hands rawJSON.parse()output toHash, andJSON.parsematerializes"__proto__"as an ordinary own key.Object.keys()therefore yields it, andcopy()'s plain assignment invoked the inherited setter, replacing the new object's prototype with the parsed value rather than storing it as data.The consequence is an asymmetry rather than global pollution.
Object.prototypeis untouched, butHash.get()andHash.has()resolve the smuggled names through the prototype chain, whileObject.keys(),toObject()andtoJSON()cannot see them:So an embedder that inspects attachment attributes with own-key operations and gates on the result will pass a payload that Trix then reads back in full. That is not hypothetical: it was reported against a real application, where it skipped an attachment-content scrubber and an image proxy, causing rendering to fetch sender-chosen URLs directly.
The change
Define the property instead of assigning it, so
"__proto__"is stored as an ordinary own key. What Trix reads and what callers inspect now agree.Why not
Object.create(null)That was the other candidate and it fixes the same bug, but
copy()also produces the object returned bytoObject()andtoJSON(). A null prototype there changes the surface embedders receive, soattributes.hasOwnProperty(...)would start throwing. Defining the property leaves that intact:get/hassmuggledtoObject()own keyshasOwnPropertyusableObject.create(null)Tests
src/test/unit/hash_test.jsis new. Two of its cases fail without the source change and pass with it, verified by reverting the change and re-running:With the change, the full suite is green:
506 tests: 477 passed, 0 failed, 29 skipped.The vendored Action Text bundle is rebuilt, matching how #1293 and the
data-trix-serialized-attributesfix shipped.Credit
Reported by @bekkaze through a GitHub Security Advisory in February, where we said we would take exactly this hardening and then did not write it. The impact was later demonstrated by another researcher through our HackerOne program. Thanks to both.
Related: #1351 covers a separate prototype-chain issue in the sanitizer and parser option handling.