|
12 | 12 | from bedrock.security.models import Product |
13 | 13 |
|
14 | 14 |
|
| 15 | +class TestSanitizeAdvisoryHtml: |
| 16 | + """Verify advisory HTML sanitization strips dangerous content.""" |
| 17 | + |
| 18 | + def test_strips_script_tags(self): |
| 19 | + html = '<p>Safe</p><script>alert("xss")</script>' |
| 20 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 21 | + assert "<script" not in result |
| 22 | + assert "Safe" in result |
| 23 | + |
| 24 | + def test_strips_event_handlers(self): |
| 25 | + html = '<img src=x onerror=alert("XSS")>' |
| 26 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 27 | + assert "onerror" not in result |
| 28 | + |
| 29 | + def test_strips_javascript_urls(self): |
| 30 | + html = "<a href=\"javascript:alert('xss')\">click</a>" |
| 31 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 32 | + assert "javascript:" not in result.lower() |
| 33 | + |
| 34 | + def test_preserves_safe_advisory_html(self): |
| 35 | + """Tags used by the CVE partial template and markdown must survive.""" |
| 36 | + html = ( |
| 37 | + '<section class="cve">' |
| 38 | + '<h4 id="CVE-2024-0001"><a href="#CVE-2024-0001">' |
| 39 | + '<span class="anchor">#</span>CVE-2024-0001: Title</a></h4>' |
| 40 | + '<dl class="summary"><dt>Reporter</dt><dd>Alice</dd>' |
| 41 | + '<dt>Impact</dt><dd><span class="level critical">critical</span></dd></dl>' |
| 42 | + "<h5>Description</h5><p>A <strong>bad</strong> thing.</p>" |
| 43 | + "<h5>References</h5><ul><li>" |
| 44 | + '<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=12345">Bug 12345</a>' |
| 45 | + "</li></ul></section>" |
| 46 | + ) |
| 47 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 48 | + # All structural tags preserved |
| 49 | + for tag in ["<section", "<h4", "<h5", "<dl", "<dt", "<dd", "<span", "<a ", "<ul", "<li", "<p>", "<strong>"]: |
| 50 | + assert tag in result |
| 51 | + assert 'href="https://bugzilla.mozilla.org' in result |
| 52 | + assert 'id="CVE-2024-0001"' in result |
| 53 | + assert 'class="cve"' in result |
| 54 | + |
| 55 | + def test_preserves_markdown_output_tags(self): |
| 56 | + """Standard markdown output tags must survive.""" |
| 57 | + html = ( |
| 58 | + "<h1>Title</h1><h2>Sub</h2><h3>Sub2</h3>" |
| 59 | + "<p><em>emphasis</em> and <code>code</code></p>" |
| 60 | + "<pre><code>block</code></pre>" |
| 61 | + "<blockquote><p>quote</p></blockquote>" |
| 62 | + "<ol><li>one</li></ol><ul><li>two</li></ul>" |
| 63 | + "<hr><br>" |
| 64 | + ) |
| 65 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 66 | + for tag in ["<h1>", "<h2>", "<h3>", "<em>", "<code>", "<pre>", "<blockquote>", "<ol>", "<hr", "<br"]: |
| 67 | + assert tag in result |
| 68 | + |
| 69 | + def test_strips_svg_tags(self): |
| 70 | + html = '<p>ok</p><svg onload="alert(1)"></svg>' |
| 71 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 72 | + assert "<svg" not in result |
| 73 | + |
| 74 | + def test_strips_iframe_tags(self): |
| 75 | + html = '<p>ok</p><iframe src="https://evil.com"></iframe>' |
| 76 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 77 | + assert "<iframe" not in result |
| 78 | + |
| 79 | + def test_strips_disallowed_attrs_on_allowed_tags(self): |
| 80 | + html = '<span class="ok" onclick="bad()">text</span>' |
| 81 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 82 | + assert 'class="ok"' in result |
| 83 | + assert "onclick" not in result |
| 84 | + |
| 85 | + def test_preserves_additional_allowlisted_tags(self): |
| 86 | + html = ( |
| 87 | + "<p><del>removed</del> <b>bold</b> <i>italic</i> " |
| 88 | + "<small>fine print</small> <abbr>abbr</abbr></p>" |
| 89 | + '<img src="https://example.com/pic.png" alt="pic">' |
| 90 | + ) |
| 91 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 92 | + for tag in ["<del>", "<b>", "<i>", "<small>", "<abbr>", "<img "]: |
| 93 | + assert tag in result |
| 94 | + assert 'src="https://example.com/pic.png"' in result |
| 95 | + assert 'alt="pic"' in result |
| 96 | + |
| 97 | + def test_strips_javascript_img_src(self): |
| 98 | + html = '<img src="javascript:alert(1)" alt="x">' |
| 99 | + result = update_security_advisories.sanitize_advisory_html(html) |
| 100 | + assert "javascript:" not in result.lower() |
| 101 | + |
| 102 | + @patch.object(update_security_advisories, "add_or_update_advisory") |
| 103 | + @patch.object(update_security_advisories, "parse_md_file") |
| 104 | + def test_update_db_from_file_sanitizes_html(self, mock_parser, mock_add): |
| 105 | + """Integration: update_db_from_file must sanitize before storing.""" |
| 106 | + mock_parser.return_value = ( |
| 107 | + { |
| 108 | + "mfsa_id": "2024-01", |
| 109 | + "title": "T", |
| 110 | + "impact": "high", |
| 111 | + "fixed_in": ["Firefox 99"], |
| 112 | + "announced": "January 1, 2024", |
| 113 | + }, |
| 114 | + '<p>ok</p><script>alert("xss")</script>', |
| 115 | + ) |
| 116 | + update_security_advisories.update_db_from_file("/fake/path/mfsa2024-01.md") |
| 117 | + _data, html = mock_add.call_args[0] |
| 118 | + assert "<script" not in html |
| 119 | + assert "<p>ok</p>" in html |
| 120 | + |
| 121 | + |
15 | 122 | def test_fix_product_name(): |
16 | 123 | """Should fix SeaMonkey and strip '.0' from names.""" |
17 | 124 | assert update_security_advisories.fix_product_name("Seamonkey 2.2") == "SeaMonkey 2.2" |
|
0 commit comments