diff --git a/scanner/dashboard/index.html b/scanner/dashboard/index.html index 132bc31b..8df0a0ee 100644 --- a/scanner/dashboard/index.html +++ b/scanner/dashboard/index.html @@ -281,7 +281,7 @@

Dashboard

function openDetail(f){ lastFocus = document.activeElement; const s = String(f.severity||'INFO').toUpperCase(); - const refs = (f.references||[]).map(r=>`${esc(r)}`).join('
'); + const refs = (f.references||[]).map(r=>`${esc(r)}`).join('
'); const owasp = (f.owasp||[]).join(', '); const cwe = (f.cwe||[]).join(', '); const d = document.getElementById('detail'); diff --git a/tests/test_dashboard_status_contracts.py b/tests/test_dashboard_status_contracts.py index a390688c..435ba369 100644 --- a/tests/test_dashboard_status_contracts.py +++ b/tests/test_dashboard_status_contracts.py @@ -2,6 +2,7 @@ from __future__ import annotations +import re from html.parser import HTMLParser from scanner.cli.appguardrail import dashboard_index_path @@ -70,8 +71,7 @@ def test_dashboard_escapes_double_quotes_with_complete_html_entity() -> None: html = _dashboard_html() expected_mapping = ( - "{'&':'&','<':'<','>':'>','\"':'"'," - "\"'\":''','`':'`'}" + "{'&':'&','<':'<','>':'>','\"':'"'," "\"'\":''','`':'`'}" ) assert expected_mapping in html @@ -90,3 +90,35 @@ def test_dashboard_distinguishes_unloaded_and_clean_scan_states() -> None: assert "Clean scan · 0 findings · deploy gate clear" in html assert "Load a different findings.json file" in html assert "🎉 Clean Scan" not in html + + +class _AnchorAttributeParser(HTMLParser): + def __init__(self) -> None: + super().__init__() + self.anchor_attrs: dict[str, str | None] = {} + + def handle_starttag( + self, + tag: str, + attrs: list[tuple[str, str | None]], + ) -> None: + if tag == "a": + self.anchor_attrs = dict(attrs) + + +def test_dashboard_external_links_warn_screen_readers() -> None: + """External links opening in new tabs must have accessible labels warning of context switch.""" + html = _dashboard_html() + + # Extract the literal anchor tag from the JS code + match = re.search( + r"const refs = [^`]+`(]+>)\$\{esc\(r\)\}`", html, re.DOTALL + ) + assert match is not None + anchor_html = match.group(1) + "" + + parser = _AnchorAttributeParser() + parser.feed(anchor_html) + + assert parser.anchor_attrs.get("target") == "_blank" + assert parser.anchor_attrs.get("aria-label") == "${esc(r)} (opens in a new tab)"