diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index 650c6d9e..9dcd584e 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -141,7 +141,10 @@ def _build_nettest_rules() -> list[dict[str, Any]]: "icon": "telegram", "url": "https://api.telegram.org", "proxy": True, - "allowed_redirect_prefixes": ["https://api.telegram.org/"], + "allowed_redirect_prefixes": [ + "https://api.telegram.org/", + "https://core.telegram.org/", + ], }, { "id": "wechat_api", @@ -157,7 +160,10 @@ def _build_nettest_rules() -> list[dict[str, Any]]: "icon": "douban", "url": "https://frodo.douban.com", "proxy": False, - "allowed_redirect_prefixes": ["https://frodo.douban.com/"], + "allowed_redirect_prefixes": [ + "https://frodo.douban.com/", + "https://www.douban.com/doubanapp/frodo", + ], }, { "id": "slack_api", @@ -214,7 +220,10 @@ def _build_nettest_rules() -> list[dict[str, Any]]: "icon": "github", "url": "https://codeload.github.com", "proxy": True, - "allowed_redirect_prefixes": ["https://codeload.github.com/"], + "allowed_redirect_prefixes": [ + "https://codeload.github.com/", + "https://github.com/", + ], "headers": settings.GITHUB_HEADERS, }, { diff --git a/tests/test_system_nettest.py b/tests/test_system_nettest.py index 6798ba89..598f4cf9 100644 --- a/tests/test_system_nettest.py +++ b/tests/test_system_nettest.py @@ -123,6 +123,50 @@ class NettestSecurityTest(unittest.TestCase): self.assertIn("跳转", resp.message) self.assertEqual(captured["calls"], 1) + def test_nettest_allows_known_external_redirects(self): + cases = { + "telegram_api": "https://core.telegram.org/bots", + "douban_api": "https://www.douban.com/doubanapp/frodo?wechat=0&os=Other", + "github_codeload": "https://github.com/", + } + + for target_id, redirect_url in cases.items(): + call_urls = [] + + class FakeResponse: + def __init__(self, status_code, headers=None, text=""): + self.status_code = status_code + self.headers = headers or {} + self.text = text + + async def aclose(self): + return None + + class FakeAsyncRequestUtils: + def __init__(self, **kwargs): + pass + + async def get_res(self, url, allow_redirects=True): + call_urls.append(url) + if len(call_urls) == 1: + return FakeResponse(302, headers={"location": redirect_url}) + return FakeResponse(200, text="ok") + + with self.subTest(target_id=target_id), patch.object( + system_endpoint, + "AsyncRequestUtils", + FakeAsyncRequestUtils, + ): + resp = asyncio.run( + system_endpoint.nettest( + target_id=target_id, + _="token", + ) + ) + + self.assertTrue(resp.success) + self.assertEqual(len(call_urls), 2) + def test_nettest_uses_safe_http_options_and_server_side_content_check(self): captured = {}