From b214aeb9876d6c26f52d6784d86f62952ecfbc2f Mon Sep 17 00:00:00 2001 From: Jakub Krasuski Date: Thu, 1 Oct 2026 10:32:16 +0200 Subject: [PATCH 1/2] fix: Escape the URL in the page-ready check Build the JS string with json.dumps instead of a template literal, so a backtick or a ${...} sequence in the URL is compared as plain text. --- CHANGELOG.md | 1 + .../protocol/devtools_async_helpers.py | 7 ++++--- tests/test_devtools_async_helpers.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0257ac88..b37de7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ where X.Y.Z is the semver of the most recent choreographer release. ### Fixed - Build the `ChromeNotFoundError` message as one string, so it no longer prints as a tuple [[#314](https://github.com/plotly/choreographer/pull/314)], with thanks to @Blizzeq for the contribution! +- Escape the URL in the page-ready check, so a backtick no longer counts as a loaded page and a `${...}` sequence no longer runs as JavaScript [[#XXX](https://github.com/plotly/choreographer/pull/XXX)] ## [1.4.0] -- 2026-09-16 diff --git a/src/choreographer/protocol/devtools_async_helpers.py b/src/choreographer/protocol/devtools_async_helpers.py index c1d2d250..edb42d52 100644 --- a/src/choreographer/protocol/devtools_async_helpers.py +++ b/src/choreographer/protocol/devtools_async_helpers.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +import json from typing import TYPE_CHECKING import logistro @@ -34,9 +35,9 @@ async def _check_document_ready(session: Session, url: str) -> BrowserResponse: new Promise((resolve) => { if ( (document.readyState === 'complete') && - (window.location==`""" # CONCATENATE! - f"{url!s}" - """`) + (window.location==""" # CONCATENATE! + f"{json.dumps(url)}" + """) ){ resolve("Was complete"); } else { diff --git a/tests/test_devtools_async_helpers.py b/tests/test_devtools_async_helpers.py index 86a83132..ecee1e7b 100644 --- a/tests/test_devtools_async_helpers.py +++ b/tests/test_devtools_async_helpers.py @@ -52,6 +52,23 @@ async def test_create_and_wait(browser): await create_and_wait(browser, url="http://192.0.2.1:9999", timeout=0.5) +@pytest.mark.asyncio +async def test_create_and_wait_escapes_url(browser): + """Test that create_and_wait treats JS template characters in the URL as text.""" + _logger.info("testing create_and_wait with template characters...") + + # Test 1: A backtick used to end the template literal in the ready check. + # The resulting SyntaxError was counted as a load, so this returned a tab + with pytest.raises(asyncio.TimeoutError): + await create_and_wait(browser, url="http://192.0.2.1:9999/`", timeout=0.5) + + # Test 2: A ${...} sequence used to run as JavaScript in the page + url = "about:blank?${window.injected=1}" + tab = await create_and_wait(browser, url=url, timeout=5.0) + result = await execute_js_and_wait(tab, "typeof window.injected", timeout=5.0) + assert result["result"]["result"]["value"] == "undefined" + + @pytest.mark.asyncio async def test_navigate_and_wait(browser): """Test navigate_and_wait with both valid data URL and bad URL.""" From 6ff60f443865e06557305d0889ab49c8b4df8f0e Mon Sep 17 00:00:00 2001 From: Jakub Krasuski Date: Thu, 1 Oct 2026 10:34:09 +0200 Subject: [PATCH 2/2] Add the PR number to the changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37de7b8..e05dc0eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ where X.Y.Z is the semver of the most recent choreographer release. ### Fixed - Build the `ChromeNotFoundError` message as one string, so it no longer prints as a tuple [[#314](https://github.com/plotly/choreographer/pull/314)], with thanks to @Blizzeq for the contribution! -- Escape the URL in the page-ready check, so a backtick no longer counts as a loaded page and a `${...}` sequence no longer runs as JavaScript [[#XXX](https://github.com/plotly/choreographer/pull/XXX)] +- Escape the URL in the page-ready check, so a backtick no longer counts as a loaded page and a `${...}` sequence no longer runs as JavaScript [[#317](https://github.com/plotly/choreographer/pull/317)] ## [1.4.0] -- 2026-09-16