fix: address PR review — XSS escape, asyncio→anyio, URL rewrite dedup

- Escape HTML in _render_error to prevent XSS from exception messages
- Replace asyncio.create_task/sleep with anyio task group and sleep,
  tying poll task lifetime to the app lifespan for proper cleanup
- Extract rewrite_url_origin() utility to fix duplicated URL rewriting
  logic and replace urlparse._replace with stable urlunparse API
- Add warning log for insecure HTTP redirect URIs
- Add unit tests for validation, XSS escaping, route handlers, and
  URL rewriting (16 new tests in test_provision_routes.py)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-03-30 08:51:41 +02:00
co-authored by Claude Opus 4.6
parent c21776948d
commit 777a09c806
5 changed files with 264 additions and 33 deletions
+34
View File
@@ -14,6 +14,7 @@ from nextcloud_mcp_server.auth.login_flow import (
LoginFlowInitResponse,
LoginFlowPollResult,
LoginFlowV2Client,
rewrite_url_origin,
)
pytestmark = pytest.mark.unit
@@ -208,3 +209,36 @@ async def test_login_flow_poll_result_model():
assert pending.status == "pending"
assert pending.server is None
assert pending.app_password is None
# ── rewrite_url_origin tests ─────────────────────────────────────────────
async def test_rewrite_url_origin_basic():
"""Test basic origin rewriting."""
result = rewrite_url_origin(
"http://localhost/login/v2/poll", "https://cloud.example.com"
)
assert result == "https://cloud.example.com/login/v2/poll"
async def test_rewrite_url_origin_preserves_port():
"""Test that port in target_host is preserved."""
result = rewrite_url_origin("http://localhost/path", "http://app:8080")
assert result == "http://app:8080/path"
async def test_rewrite_url_origin_preserves_query():
"""Test that query string and fragment are preserved."""
result = rewrite_url_origin(
"http://internal/path?token=abc&foo=bar#section",
"https://public.example.com",
)
assert result == "https://public.example.com/path?token=abc&foo=bar#section"
async def test_rewrite_url_origin_noop_when_same():
"""Test that rewriting to the same origin is a no-op."""
url = "https://cloud.example.com/login/v2/poll"
result = rewrite_url_origin(url, "https://cloud.example.com")
assert result == url