"""PLAN A12 guard: the browser must load nothing this process does not serve. This is the mechanical half of "no external CDNs" (LOCKED decision A12, AGENTS.md §5). It has two halves on purpose: 1. **A text scan** of every `app/static/**` asset (`.html`, `.css`, `.js`, `.svg`) for the shapes that reach off-origin: `http(s)://`, protocol-relative `//host/`, hosted-library mirrors (`jsdelivr`, `unpkg`, …), CSS `@import url(`, protocol-relative stylesheets, `srcset`, `fetch()` / `import` of an absolute URL, an absolute-URL `WebSocket`, and `EventSource` (SSE is not this project's transport). A single remote reference is both an availability and a privacy defect, so the scan is blunt and needs no allow-list — with one documented exception, the `xmlns` namespace URIs of SVG/XML, which are identifiers the browser never fetches (A11 ships in-repo SVG art). 2. **An element check** of `index.html`: every `'), ( "module_src", '', ), ("stylesheet_link", ''), ("protocol_relative_stylesheet", ''), ("img_src", ''), ("img_srcset", ''), ], ) def test_a_planted_html_reference_fails_the_scan( static_tree: Path, name: str, content: str ) -> None: page = static_tree / f"{name}.html" page.write_text(content, encoding="utf-8") violations = scan_tree(static_tree) assert violations, content assert all(violation.path == page for violation in violations), format_violations(violations) assert page.name in format_violations(violations) @pytest.mark.parametrize( ("suffix", "content", "rule"), [ ( ".js", 'import { x } from "https://cdn.example.com/lib.js";', "import of an off-origin URL", ), ( ".js", 'import "https://cdn.example.com/side-effect.js";', "bare import of an off-origin URL", ), (".js", 'export * from "//cdn.example.com/lib.js";', "import of an off-origin URL"), (".js", 'fetch("https://api.example.com/state");', "fetch() of an off-origin URL"), (".js", 'fetch("//api.example.com/state");', "fetch() of an off-origin URL"), ( ".js", 'new WebSocket("wss://remote.example.com/ws/ABCDE");', "WebSocket with an absolute URL", ), (".js", 'new EventSource("/events");', "EventSource (server-sent events)"), (".js", 'const u = "https://cdn.example.com/x.js";', "absolute URL"), (".css", "@import url(https://fonts.example.com/one.css);", "CSS @import url()"), (".css", "@import url(//fonts.example.com/one.css);", "CSS @import url()"), (".css", "body { background: url(//cdn.example.com/bg.png); }", "protocol-relative URL"), ( ".svg", '' '', "absolute URL", ), ( ".svg", '' '', "absolute URL", ), ( ".svg", '' '', "absolute URL", ), (".html", 'out', "absolute URL"), ], ) def test_a_planted_asset_reference_fails_the_scan( static_tree: Path, suffix: str, content: str, rule: str ) -> None: planted = static_tree / "js" / f"planted{suffix}" planted.write_text(content, encoding="utf-8") violations = scan_tree(static_tree) assert violations, content assert rule in {violation.rule for violation in violations}, format_violations(violations) assert any(violation.path == planted for violation in violations) assert planted.name in format_violations(violations) @pytest.mark.parametrize("token", HOSTED_LIBRARY_TOKENS) @pytest.mark.parametrize("suffix", [".js", ".html"]) def test_a_planted_hosted_library_token_fails_the_scan( static_tree: Path, token: str, suffix: str ) -> None: planted = static_tree / f"planted{suffix}" planted.write_text(f"/* vendored from {token} once */", encoding="utf-8") violations = [v for v in scan_tree(static_tree) if v.path == planted] assert violations, token assert token in format_violations(violations) # --------------------------------------------------------------------------- what must stay green @pytest.mark.parametrize( ("suffix", "content"), [ ( ".svg", '' '', ), ( ".svg", '' '', ), ( ".svg", '' '', ), (".js", "// a comment about docs/PROTOCOL.md and a/b/c paths\nexport const ok = 1;\n"), (".js", 'const ws = new WebSocket(`${location.origin}/ws/${code}`);\n'), (".js", 'fetch("/static/api/levels");\n'), (".css", ".a { background: url(/static/assets/tile.svg); }\n"), (".html", '\n'), ], ) def test_same_origin_and_namespace_references_are_allowed( static_tree: Path, suffix: str, content: str ) -> None: """The guard must not train anyone to delete real code: same-origin stays green.""" (static_tree / "js" / f"fine{suffix}").write_text(content, encoding="utf-8") assert scan_tree(static_tree) == [] def test_non_asset_files_are_out_of_scope(static_tree: Path) -> None: """Notes and images are not client instructions; the scan is about loadable code.""" (static_tree / "NOTES.md").write_text("see https://example.com for details", encoding="utf-8") (static_tree / "logo.png").write_bytes(b"\x89PNG\r\n") assert scan_tree(static_tree) == [] def test_an_empty_tree_is_not_a_passing_signal(tmp_path: Path) -> None: """Documented explicitly: an empty root scans clean, so the root tests above matter.""" assert scan_tree(tmp_path) == [] assert list(iter_asset_files(tmp_path)) == []