diff --git a/docs/development-setup.md b/docs/development-setup.md index 60a427c66..9363cda74 100644 --- a/docs/development-setup.md +++ b/docs/development-setup.md @@ -68,6 +68,7 @@ UV_PROJECT_ENVIRONMENT="${MOVIEPILOT_WORKSPACE}/.venv-test" \ [CVE-2026-111497 的官方修复](https://github.com/google/brotli/commit/0ed4268a4401abcb4472096340477cf4af7d1a2b)。 源码分支的包版本仍为 1.2.0,不能仅凭版本字符串判断补丁是否存在;1.2.1 发布分支 尚未声明无 GIL 支持,不能直接替换 free-threaded 的源码依赖。 +发布审计脚本会将这个已核实修复提交按 1.2.1 版本匹配漏洞公告,安装仍使用锁文件中的源码地址。 ### 2.1 本地启动脚本 diff --git a/scripts/normalize_audit_requirements.py b/scripts/normalize_audit_requirements.py index b6575d447..64fc2c19f 100644 --- a/scripts/normalize_audit_requirements.py +++ b/scripts/normalize_audit_requirements.py @@ -14,6 +14,15 @@ DIRECT_REFERENCE = re.compile( r"(?P\S+)(?P.*)$" ) +# free-threaded Brotli 保留 1.2.0 包元数据以使用无 GIL 源码,但源码提交已包含 +# 1.2.1 的安全修复;仅在漏洞数据库匹配时使用修复版本,不改变实际安装来源。 +AUDIT_VERSION_OVERRIDES = { + ( + "brotli", + "https://github.com/google/brotli/archive/0ed4268a4401abcb4472096340477cf4af7d1a2b.tar.gz", + ): "1.2.1", +} + def _canonicalize_name(name: str) -> str: """ @@ -30,6 +39,7 @@ def _locked_url_versions(lock_file: Path) -> dict[tuple[str, str], str]: document = tomllib.load(file) versions: dict[tuple[str, str], str] = {} + locked_versions: dict[tuple[str, str], str] = {} for package in document.get("package", []): source = package.get("source") or {} url = source.get("url") @@ -38,8 +48,12 @@ def _locked_url_versions(lock_file: Path) -> dict[tuple[str, str], str]: if not all(isinstance(value, str) and value for value in (url, name, version)): continue key = (_canonicalize_name(name), url) - previous = versions.setdefault(key, version) - if previous != version: + locked_previous = locked_versions.setdefault(key, version) + if locked_previous != version: + raise ValueError(f"锁文件包含冲突的 URL 依赖版本:{name} @ {url}") + audit_version = AUDIT_VERSION_OVERRIDES.get(key, version) + previous = versions.setdefault(key, audit_version) + if previous != audit_version: raise ValueError(f"锁文件包含冲突的 URL 依赖版本:{name} @ {url}") return versions diff --git a/tests/test_release_supply_chain.py b/tests/test_release_supply_chain.py index 26b440bff..22a6fc002 100644 --- a/tests/test_release_supply_chain.py +++ b/tests/test_release_supply_chain.py @@ -199,6 +199,26 @@ source = { url = "https://example.com/brotli.tar.gz" } assert "@ https://example.com/brotli.tar.gz" not in normalized +def test_patched_free_threaded_brotli_uses_security_version_for_audit(tmp_path: Path) -> None: + """ + 无 GIL Brotli 源码保留 1.2.0 元数据时,审计应按已包含修复的 1.2.1 匹配公告。 + """ + source = ( + "https://github.com/google/brotli/archive/" + "0ed4268a4401abcb4472096340477cf4af7d1a2b.tar.gz" + ) + lock_file = tmp_path / "uv.lock" + lock_file.write_text( + f'version = 1\n\n[[package]]\nname = "brotli"\nversion = "1.2.0"\n' + f'source = {{ url = "{source}" }}\n', + encoding="utf-8", + ) + + normalized = normalize_requirements(f"brotli @ {source}\n", lock_file) + + assert normalized == "brotli==1.2.1\n" + + def test_direct_url_audit_requirement_rejects_unlocked_source(tmp_path: Path) -> None: """ 不能把未匹配锁文件来源的 URL 依赖伪装为已审计版本。