From 6ad3ff5abaf6bb84a0f54310d558318219517512 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 05:35:24 +0000 Subject: [PATCH] fix(web): CSS.escape() the owned key before it becomes a selector container.dataset.ownedKeys round-trips schema property keys through a DOM dataset attribute, and the takeover handoff spliced each one straight into '[data-child-key="' + k + '"]' with no escaping -- inconsistent with this codebase's own convention elsewhere (plugin-file-manager.js, app-shell.js's escapeCssSelector) for building a selector from a dynamic value. A key containing a quote or backslash would break the selector or be steerable; Codacy's static analysis flagged this pattern (1 high ErrorProne finding on PR #569, current head at the time) as a new issue, though its dashboard is unreachable from this sandbox (egress to app.codacy.com is blocked) and the check-run API returned no detail text -- verified and fixed by reading the diff directly rather than the tool's own description. Added a source-assertion regression test alongside this file's existing ones (this behavior lives in an inline script no Python test executes). Full suite: 4888 passed, 62 skipped, 2 failed -- both the pre-existing Europe/Kiev/Asia/Calcutta tzdata-alias gap in this sandbox, identical on origin/main, unrelated to this change. Co-Authored-By: Claude Opus 5 --- test/web_interface/test_style_editor_takeover.py | 10 ++++++++++ web_interface/templates/v3/partials/plugin_config.html | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/web_interface/test_style_editor_takeover.py b/test/web_interface/test_style_editor_takeover.py index 4831fb97..00ea9791 100644 --- a/test/web_interface/test_style_editor_takeover.py +++ b/test/web_interface/test_style_editor_takeover.py @@ -88,6 +88,16 @@ class TestItTakesOverOnlyItsOwnBlocks: entirely styling looks exactly as it did before.""" assert "!fallback.querySelector('[data-child-key]')" in source + def test_the_owned_key_is_escaped_before_it_becomes_a_selector(self, source): + """ownedKeys comes back off a dataset attribute, not a literal -- + splicing it into '[data-child-key="' + k + '"]' unescaped breaks (or + is steerable) on a key containing a quote or backslash. The rest of + this codebase (plugin-file-manager.js, app-shell.js) always routes a + dynamic value through CSS.escape() before it lands in a selector.""" + assert "CSS.escape(k)" in source, ( + "an owned key must be CSS.escape()'d before being interpolated " + "into the data-child-key attribute selector") + class TestTheSchemaSaysWhichBlocksAreStyling: def test_adopted_blocks_are_marked_and_others_are_not(self): diff --git a/web_interface/templates/v3/partials/plugin_config.html b/web_interface/templates/v3/partials/plugin_config.html index 8d4752a1..ae3263f4 100644 --- a/web_interface/templates/v3/partials/plugin_config.html +++ b/web_interface/templates/v3/partials/plugin_config.html @@ -147,8 +147,13 @@ var owned = (container.dataset.ownedKeys || '') .split(',').filter(function (k) { return k; }); owned.forEach(function (k) { + // CSS.escape: k is a schema property key read + // back off dataset.ownedKeys, not a literal -- + // building the attribute selector by raw + // concatenation would break (or be steerable) + // on a key containing a quote or backslash. var node = fallback.querySelector( - '[data-child-key="' + k + '"]'); + '[data-child-key="' + CSS.escape(k) + '"]'); if (node) { node.remove(); } }); // Nothing left but the section heading, or a widget