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 "