From 6157de43a0081e5f3fb402c48ed4709a61dd311d Mon Sep 17 00:00:00 2001 From: autumn_atlas <3477314+Autumn-Atlas@users.noreply.github.com> Date: Sun, 13 Sep 2026 18:03:10 +0800 Subject: [PATCH] fix(client): restore date-only strings, harden URL sanitize, custom-domain opt-in (0.1.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - YYYY-MM-DD strings accepted again as UTC midnight (0.1.2 regression); naive timestamp strings still rejected - safe_markdown_url: urlsplit validation (scheme+netloc), percent-encode <>() and spaces — closes https://...>") is None + + def test_scheme_relative_rejected(self): + assert safe_markdown_url("//attacker.example/x") is None + + def test_missing_netloc_rejected(self): + assert safe_markdown_url("https://") is None + + def test_angle_brackets_percent_encoded(self): + out = safe_markdown_url("https://example.com>") + assert "<" not in out and ">" not in out + assert out == "https://example.com%3E%3Cscript%3Ex%3C/script%3E" + + def test_parens_percent_encoded(self): + assert safe_markdown_url("https://example.com/x(1)") == "https://example.com/x%281%29" + + def test_normal_url_unchanged(self): + assert safe_markdown_url("https://example.com/a?b=1&c=2") == "https://example.com/a?b=1&c=2" + + def test_non_string_rejected(self): + assert safe_markdown_url(None) is None + assert safe_markdown_url(123) is None + + +class TestBriefingOutputSanitization: + def test_hostile_title_cannot_form_link(self): + art = { + "title": "[click here](javascript:alert(1))", + "description": "", + "url": "", + "published": "2026-01-01", + } + md, _ = briefing.build_output( + {"status": "ok", "news": [art]}, generated_at="2026-01-01T00:00:00Z" + ) + assert "](javascript:" not in md + + def test_hostile_url_cannot_break_out(self): + art = { + "title": "t", + "description": "", + "url": "https://example.com>", + "published": "2026-01-01", + } + md, _ = briefing.build_output( + {"status": "ok", "news": [art]}, generated_at="2026-01-01T00:00:00Z" + ) + assert "