From 9ca08a8aca264d67bbc4484e3138e69aa00677f8 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Fri, 19 Jun 2026 12:39:10 +0800 Subject: [PATCH] fix: validate hosted core package archive --- astrbot/core/updator.py | 5 ++++ tests/test_updator_socks.py | 55 +++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/astrbot/core/updator.py b/astrbot/core/updator.py index ce251e6c6..304b35769 100644 --- a/astrbot/core/updator.py +++ b/astrbot/core/updator.py @@ -1,6 +1,7 @@ import os import sys import time +import zipfile from pathlib import Path import psutil @@ -258,6 +259,10 @@ class AstrBotUpdator(RepoZipUpdator): str(zip_path), progress_callback=progress_callback, ) + if not zipfile.is_zipfile(zip_path): + raise RuntimeError( + "Downloaded hosted package is not a valid ZIP file" + ) return zip_path except Exception as exc: logger.warning( diff --git a/tests/test_updator_socks.py b/tests/test_updator_socks.py index 318a931e6..f5471f64a 100644 --- a/tests/test_updator_socks.py +++ b/tests/test_updator_socks.py @@ -1,5 +1,6 @@ import ntpath import posixpath +import zipfile from dataclasses import dataclass, field from pathlib import Path from types import SimpleNamespace @@ -313,7 +314,8 @@ async def test_astrbot_updator_prefers_hosted_core_package( async def fake_download_file(url: str, path: str, progress_callback=None): # noqa: ARG001 calls.append(url) - Path(path).write_bytes(b"hosted-core") + with zipfile.ZipFile(path, "w") as archive: + archive.writestr("AstrBot-v99.0.0/README.md", "hosted-core") monkeypatch.setattr(updator, "fetch_release_info", fake_fetch_release_info) monkeypatch.setattr(updator, "_download_file", fake_download_file) @@ -325,7 +327,7 @@ async def test_astrbot_updator_prefers_hosted_core_package( ) assert zip_path == tmp_path / "core.zip" - assert zip_path.read_bytes() == b"hosted-core" + assert zipfile.is_zipfile(zip_path) assert calls == ["https://cdn.example/core/v99.0.0/source.zip"] @@ -376,6 +378,55 @@ async def test_astrbot_updator_falls_back_when_hosted_core_package_fails( ] +@pytest.mark.asyncio +async def test_astrbot_updator_falls_back_when_hosted_core_package_is_not_zip( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + monkeypatch.delenv("ASTRBOT_CLI", raising=False) + monkeypatch.delenv("ASTRBOT_LAUNCHER", raising=False) + monkeypatch.setenv("ASTRBOT_CORE_PACKAGE_BASE_URL", "https://cdn.example/core") + + updator = AstrBotUpdator() + calls: list[str] = [] + + async def fake_fetch_release_info(url: str, latest: bool = True): # noqa: ARG001 + return [ + { + "version": "AstrBot v99.0.0", + "published_at": "2026-06-19T00:00:00Z", + "body": "hosted core package", + "tag_name": "v99.0.0", + "zipball_url": "https://github.example/archive.zip", + } + ] + + async def fake_download_file(url: str, path: str, progress_callback=None): # noqa: ARG001 + calls.append(url) + parsed = urlparse(url) + if parsed.scheme == "https" and parsed.hostname == "cdn.example": + Path(path).write_bytes(b"not a zip") + return + with zipfile.ZipFile(path, "w") as archive: + archive.writestr("AstrBot-v99.0.0/README.md", "github-core") + + monkeypatch.setattr(updator, "fetch_release_info", fake_fetch_release_info) + monkeypatch.setattr(updator, "_download_file", fake_download_file) + + zip_path = await updator.download_update_package( + latest=False, + version="v99.0.0", + path=tmp_path / "core.zip", + ) + + assert zip_path == tmp_path / "core.zip" + assert zipfile.is_zipfile(zip_path) + assert calls == [ + "https://cdn.example/core/v99.0.0/source.zip", + "https://github.example/archive.zip", + ] + + @pytest.mark.asyncio async def test_fetch_release_info_uses_httpx_client_with_env_proxy_support( monkeypatch: pytest.MonkeyPatch,