feat: add shadow DOM support for text/input/image replacements#344
feat: add shadow DOM support for text/input/image replacements#344svrnm wants to merge 4 commits into
Conversation
Resolves #146. DemoMonkey now traverses open shadow roots and applies the same replacement logic (text, input, textarea, image) inside them. Closed shadow roots remain inaccessible by browser design. Signed-off-by: svrnm <severin.neumann@altmuehlnet.de> Made-with: Cursor
There was a problem hiding this comment.
Security review completed for PR #344 (feat: add shadow DOM support for text/input/image replacements).
Result: No medium/high/critical vulnerabilities found in the modified code paths.
I traced the new shadow-DOM traversal flow in Monkey.apply() through _collectOpenShadowRoots() and _applyOnXpathGroup() into Configuration.apply() sinks and did not find a new exploitable injection/authz/secret-leak/SSRF/XSS path introduced by this change.
Slack summary: PR #344 security review -> no actionable medium+ findings.
Sent by Cursor Automation: Find vulnerabilities
There was a problem hiding this comment.
Pull request overview
Adds Shadow DOM traversal to DemoMonkey’s replacement engine so text/input/textarea/image replacements are also applied inside open shadow roots, while avoiding self-interference with the Live Editor host.
Changes:
- Invoke shadow-root processing during
Monkey.apply()via_applyOnShadowRoots. - Extend
_applyOnXpathGroupto accept an optionalcontextNodefor shadow-root-scoped XPath evaluation. - Add unit tests covering text replacement in shadow roots, nested shadow roots, skipping the live editor host, undo behavior, and shadow-root inputs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/models/Monkey.js | Adds discovery of open shadow roots and applies existing XPath-based replacements within each shadow root. |
| test/models/Monkey.js | Adds tests validating shadow-root traversal, live editor exclusion, nested roots, undo, and input handling. |
💡 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.
| sum.input += this._applyOnXpathGroup(configuration, './/input', 'input', 'value', shadowRoot) | ||
| sum.input += this._applyOnXpathGroup( | ||
| configuration, | ||
| './/textarea', | ||
| 'input', | ||
| 'value', | ||
| shadowRoot | ||
| ) | ||
| sum.image += this._applyOnXpathGroup(configuration, './/img', 'image', 'src', shadowRoot) | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds support for applying DemoMonkey replacements inside open Shadow DOM trees by discovering open shadow roots and running the existing XPath-based replacement logic within each shadow root context (while skipping DemoMonkey’s own live editor shadow root).
Changes:
- Traverse the document (and nested shadow roots) to collect open
shadowRoots and apply text/input/textarea/image replacements within them. - Extend
_applyOnXpathGroupto optionally run XPath evaluation relative to a provided context node. - Add unit tests covering text replacement, nested shadow roots, undo behavior, input handling, and skipping
dm-live-editor-host.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/models/Monkey.js | Adds shadow root collection + shadow-root-scoped XPath application; updates XPath helper to accept a context node. |
| test/models/Monkey.js | Adds unit tests validating replacement behavior inside open shadow roots and nested shadow roots. |
💡 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.
| _collectOpenShadowRoots(root) { | ||
| const shadowRoots = [] | ||
|
|
||
| // Prefer a TreeWalker-based traversal to avoid materializing a full NodeList | ||
| const ownerDocument = root.ownerDocument || root | ||
|
|
||
| if (ownerDocument && typeof ownerDocument.createTreeWalker === 'function') { | ||
| const walker = ownerDocument.createTreeWalker( | ||
| root, | ||
| NodeFilter.SHOW_ELEMENT, | ||
| null, | ||
| false | ||
| ) | ||
|
|
||
| let current = walker.currentNode | ||
| while (current) { | ||
| const el = current | ||
| if (el.shadowRoot && el.id !== 'dm-live-editor-host') { | ||
| shadowRoots.push(el.shadowRoot) | ||
| shadowRoots.push(...this._collectOpenShadowRoots(el.shadowRoot)) | ||
| } | ||
| current = walker.nextNode() | ||
| } | ||
|
|
||
| return shadowRoots | ||
| } | ||
|
|
||
| // Fallback for environments without TreeWalker support |
| shadowRoots.push(el.shadowRoot) | ||
| shadowRoots.push(...this._collectOpenShadowRoots(el.shadowRoot)) | ||
| } | ||
| } | ||
| return shadowRoots | ||
| } | ||
|
|
||
| _applyOnShadowRoots(configuration, sum) { | ||
| const shadowRoots = this._collectOpenShadowRoots(this.scope.document) | ||
| for (const shadowRoot of shadowRoots) { |
| const ownerDocument = root.ownerDocument || root | ||
|
|
||
| if (ownerDocument && typeof ownerDocument.createTreeWalker === 'function') { | ||
| const walker = ownerDocument.createTreeWalker( | ||
| root, | ||
| NodeFilter.SHOW_ELEMENT, | ||
| null, | ||
| false | ||
| ) | ||
|
|
||
| let current = walker.currentNode | ||
| while (current) { | ||
| const el = current | ||
| if (el.shadowRoot && el.id !== 'dm-live-editor-host') { |
…dImage selectors (#347) Co-authored-by: svrnm <1519757+svrnm@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>


Summary
Resolves #146.
Monkey.jsso that text, input, textarea, and image replacements now apply inside open shadow rootsquerySelectorAll('*')and checkselement.shadowRootdm-live-editor-host) to avoid self-interference_applyOnXpathGroupmethod with an optionalcontextNodeparameter, using shadow-root-scoped XPath (.//text(),.//input,.//img, etc.)Limitations
mode: 'open') are accessible — closed shadow roots are a browser security boundary and cannot be reachedTest plan
Made with Cursor