fix: Visual Selector renders scraped selectors as text, not markup - #4282
Merged
dgtlmoon merged 1 commit intoAug 4, 2026
Merged
Conversation
The element scraper builds '//*[@id="' + n.id + '"]' from the scraped page's raw id attribute, and visual-selector.js displayed the result with innerHTML, so an id containing a quote or an angle bracket turned into DOM nodes in the edit page. The selector display now uses textContent, and getxpath() only takes the id shortcut for ids it can actually express in a double-quoted xpath literal, using the positional path for the rest.
Owner
|
ah thanks for this! |
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.
What
The Visual Selector displays the selector of the element under the mouse, and
static/js/visual-selector.js:256assigned that string withinnerHTML. The string can contain markup from the scraped page, becausecontent_fetchers/res/xpath_element_scraper.js:16builds it as'//*[@id="' + n.id + '"]'from the page's rawidattribute. Anidsuch asdupe"><img src=x onerror=...>therefore becomes real DOM nodes in the edit page instead of being shown as the selector it is.Why
Two gates are missing on the way to that display.
findUpTag()runsCSS.escape()on the id, but it returnsnullwhen the id is not unique on the page (xpath_element_scraper.js:62,:65), and the caller then falls back togetxpath()(:151-160), which interpolates the id verbatim. That is also the only branch that can emit an invalid selector: a double quote cannot be expressed inside a double-quoted xpath literal, so the resulting//*[@id="a"b"]never matches anything either way. At the display end, sibling code in this repo routes untrusted strings through.text()rather thaninnerHTML, for examplestatic/js/watch-overview.js:454($('<span>').text(line).html()) andstatic/js/realtime.js:217(.html(" ").text(data.status)).How
xpath_element_scraper.jstakes the//*[@id="..."]shortcut only for ids without",<or>and builds the positional path (/html/body/div[1]) for the rest, so those elements stay selectable and are addressed by position instead.visual-selector.jssetstextContenton the selector display, which also covers element data already stored inelements.deflatefrom earlier versions.Left unchanged on purpose: the stored
elements.deflatepayload and thevisual_selector_dataendpoint (the display-end fix covers files written by earlier versions), theinclude_filtersbranch atxpath_element_scraper.js:203(operator input, not page content), andbrowser-steps.js, which puts the same selectors into input values with.val().Testing
Added
test_visual_selector_xpath_carries_no_page_markuptotests/visualselector/test_fetch_data.py, the file the playwright and pyppeteer CI jobs already run. It serves a page with the same id on two elements, fetches it withhtml_webdriver, and asserts the stored selectors carry no markup while both divs keep a selector and a unique id still uses the#idform. On the unpatched tree it fails with//*[@id="dupe"><img src=x onerror=console.log(1)>"]; with the change the file passes 5/5 against asockpuppetbrowsercontainer.Also checked by hand against a running instance watching such a page: the stored selectors become
/html/body/div[1]and/html/body/div[2], hovering shows the selector as literal text with no child elements, and#unique-benignand/html/body/h1display as before.tests/test_security.pypasses 17/17.ruff checkon the touched test file reports the same 6 pre-existing findings as onmaster, no new ones. The full suite was not run, only those two test files.References
GHSA-8m69-92q9-qcxx (reported privately, unpublished at time of writing)