# tests/test_security_hardening.py — Regression tests for ROADMAP #84 # (BUG-021 → BUG-034): sanitizer, path isolation, password policy, regex # safety, webhook SSRF, rate limiting, audit IP and share-page escaping. import time from pathlib import Path import pytest from backend.services.errors import ServiceError # ═══════════════════════════════════════════════════════════════════ # BUG-021/022 — HTML sanitizer # ═══════════════════════════════════════════════════════════════════ class TestSanitizer: def test_strips_event_handlers(self): from backend.services.sanitizer import sanitize_html out = sanitize_html('') assert "onerror" not in out assert "alert" not in out def test_drops_script_content(self): from backend.services.sanitizer import sanitize_html out = sanitize_html("

ok

tail") assert "x') assert "javascript:" not in out assert ">x" in out def test_blocks_obfuscated_javascript_url(self): from backend.services.sanitizer import sanitize_html out = sanitize_html('x') assert "javascript" not in out.lower() def test_keeps_safe_markup(self): from backend.services.sanitizer import sanitize_html out = sanitize_html('

hi

y') assert '

hi

' in out assert 'data-vault="v"' in out assert 'href="/x"' in out def test_allows_image_data_uri_but_not_html(self): from backend.services.sanitizer import sanitize_html good = sanitize_html('') assert "data:image/png" in good bad = sanitize_html('') assert "data:text/html" not in bad def test_heading_ids_preserved(self): from backend.services.sanitizer import sanitize_html out = sanitize_html('

Intro

') assert 'id="intro"' in out def test_drops_style_attribute(self): from backend.services.sanitizer import sanitize_html out = sanitize_html('

x

') assert "style" not in out def test_is_safe_url(self): from backend.services.sanitizer import is_safe_url assert is_safe_url("/relative") assert is_safe_url("https://example.com") assert is_safe_url("#anchor") assert not is_safe_url("javascript:alert(1)") assert not is_safe_url("vbscript:msgbox") assert not is_safe_url("data:text/html,\n\n" "Normal **text**.\n" ) (Path(test_vault_dir) / "xss.md").write_text(payload, encoding="utf-8") resp = client.get("/api/file/TestVault", params={"path": "xss.md"}) assert resp.status_code == 200 html = resp.json()["html"] assert "onerror" not in html assert "text" in html def test_share_page_escapes_title_and_frontmatter(self, client, test_vault_dir): payload = ( '---\ntitle: ""\n' 'author: ""\n---\n' "# Body\ncontent\n" ) (Path(test_vault_dir) / "share_xss.md").write_text(payload, encoding="utf-8") create = client.post("/api/share/TestVault", json={"path": "share_xss.md"}) assert create.status_code == 200 token = create.json()["token"] page = client.get(f"/s/{token}") assert page.status_code == 200 body = page.text assert "" not in body assert "<script>alert(1)</script>" in body # The img tag must be escaped, so no live attribute is emitted. assert "