"""The manager page must not build markup out of tenant text (review F-01). The page is rendered by ``tests/page_dom_probe.js`` (Node) against a recording DOM, so these tests drive the real inline script instead of matching its source. The property under test is the rendered result: no markup string may carry the entry text, and parsing every markup string the page writes must not yield an event-handler attribute. Pre-fix (``esc(e.question)`` concatenated into ``value="..."`` and, for the list view, straight into element content): a question of ``" onfocus="alert(1)" data-x="`` closed the ``value`` attribute and produced an ``onfocus`` handler, and an answer containing ```` produced an element with an ``onerror`` handler. """ from __future__ import annotations import json import shutil import subprocess import tempfile from html.parser import HTMLParser from pathlib import Path import pytest PLUGIN_ROOT = Path(__file__).resolve().parents[1] PAGE = PLUGIN_ROOT / "components/pages/manager/index.html" PROBE = Path(__file__).resolve().parent / "page_dom_probe.js" QUESTION = '" onfocus="alert(1)" data-x="' ANSWER = 'line1' ENTRY = {"id": "e1", "question": QUESTION, "answer": ANSWER} class _AttributeCollector(HTMLParser): """Collect every ``(tag, attribute, value)`` a markup string parses into.""" def __init__(self) -> None: super().__init__(convert_charrefs=True) self.attributes: list[tuple[str, str, str]] = [] def handle_starttag(self, tag, attrs) -> None: for name, value in attrs: self.attributes.append((tag, name, value if value is not None else "")) def handle_startendtag(self, tag, attrs) -> None: self.handle_starttag(tag, attrs) def _render(drive: str) -> dict: node = shutil.which("node") if node is None: pytest.skip("the page probe needs node to render the page against a recording DOM") config = {"responses": {"GET /entries": {"entries": [ENTRY]}}, "drive": drive} with tempfile.TemporaryDirectory() as tmp: config_path = Path(tmp) / "probe_config.json" config_path.write_text(json.dumps(config), encoding="utf-8") completed = subprocess.run( [node, str(PROBE), str(PAGE), str(config_path)], capture_output=True, text=True, timeout=120, ) assert completed.returncode == 0, completed.stderr return json.loads(completed.stdout) def _rendered_attributes(report: dict) -> list[tuple[str, str, str]]: parser = _AttributeCollector() for write in report["markup_writes"]: parser.feed(write["markup"]) parser.close() return parser.attributes def test_entry_text_never_reaches_markup_and_never_becomes_a_handler(): report = _render("await loadEntries();") assert report["errors"] == [] # 1. Entry text must not appear in any markup string at all: a value that # never reaches markup cannot open an attribute or a script context. for write in report["markup_writes"]: assert QUESTION not in write["markup"] assert "