From 20b30ab1df91e4c700868ecab5f3b9080fbf0786 Mon Sep 17 00:00:00 2001 From: yiguo Date: Thu, 5 Nov 2026 21:04:51 +0800 Subject: [PATCH 1/2] fix(release): validate declared winget manifest schemas --- build_scripts/README.md | 4 +++- build_scripts/tests/test_winget_manifest.py | 20 ++++++++++++++++++-- build_scripts/winget_manifest.py | 14 ++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/build_scripts/README.md b/build_scripts/README.md index 2682f8179..edae7868a 100644 --- a/build_scripts/README.md +++ b/build_scripts/README.md @@ -219,7 +219,9 @@ build jobs together. The workflow pipes `komac update --dry-run` output through `awk` to save separate YAML files by manifest type and locale; dry-run returns before handling `--output`. [winget_manifest.py](winget_manifest.py) retains identity, release URL and user - installation policy checks. Komac supplies the schema-required `InstallerSha256`; + installation policy checks. Manifests must share a numeric schema version and + pass Microsoft's JSON schema for that declared version; the helper does not + rewrite the generator's `ManifestVersion`. Komac supplies the schema-required `InstallerSha256`; no extra comparison with GitHub asset digests is performed. Before submission, `komac sync-fork` updates the organization fork using the same PAT and `KOMAC_FORK_OWNER` as the final submit. Fork synchronization changes GitHub; diff --git a/build_scripts/tests/test_winget_manifest.py b/build_scripts/tests/test_winget_manifest.py index 5e758e89c..5c9ff0602 100644 --- a/build_scripts/tests/test_winget_manifest.py +++ b/build_scripts/tests/test_winget_manifest.py @@ -6,7 +6,6 @@ INSTALL_DIRECTORY, INSTALLER_NAMES, PACKAGE_ID, - SCHEMA_VERSION, fix_installer, validate_manifests, ) @@ -29,7 +28,7 @@ def setUp(self): base = { "PackageIdentifier": PACKAGE_ID, "PackageVersion": "26.9.3", - "ManifestVersion": SCHEMA_VERSION, + "ManifestVersion": "1.12.0", } self.generated = base | { "ManifestType": "installer", @@ -153,6 +152,23 @@ def test_rejects_non_release(self): with self.subTest(field=field), self.assertRaises(ValueError): validate_manifests(self.manifests, self.release | {field: True}) + def test_accepts_a_consistent_generator_schema_version(self): + for version in ("1.10.0", "1.12.0", "1.28.0"): + with self.subTest(version=version): + manifests = deepcopy(self.manifests) + for manifest in manifests: + manifest["ManifestVersion"] = version + validate_manifests(manifests, self.release) + + def test_rejects_unsafe_or_missing_schema_versions(self): + for version in (None, "", "v1.12.0", "../1.12.0", "1.12.0?raw=1", 1.12): + with self.subTest(version=version): + manifests = deepcopy(self.manifests) + for manifest in manifests: + manifest["ManifestVersion"] = version + with self.assertRaises(ValueError): + validate_manifests(manifests, self.release) + if __name__ == "__main__": unittest.main() diff --git a/build_scripts/winget_manifest.py b/build_scripts/winget_manifest.py index 0448b9421..c932781c1 100644 --- a/build_scripts/winget_manifest.py +++ b/build_scripts/winget_manifest.py @@ -6,6 +6,7 @@ import argparse import json +import re from collections import Counter from copy import deepcopy from pathlib import Path @@ -19,7 +20,6 @@ "x64": "OneXray-windows-amd64.exe", "arm64": "OneXray-windows-arm64.exe", } -SCHEMA_VERSION = "1.12.0" def fix_installer(manifest): @@ -56,8 +56,13 @@ def validate_manifests(manifests, release): for item in manifests: if item.get("PackageIdentifier") != PACKAGE_ID or item.get("PackageVersion") != version: raise ValueError("Manifest package/version does not match the release") - if item.get("ManifestVersion") != SCHEMA_VERSION: - raise ValueError(f"Expected manifest schema {SCHEMA_VERSION}") + schema_version = item.get("ManifestVersion") + if not isinstance(schema_version, str) or not re.fullmatch( + r"[0-9]+\.[0-9]+\.[0-9]+", schema_version + ): + raise ValueError("Expected a numeric manifest schema version") + if schema_version != manifests[0].get("ManifestVersion"): + raise ValueError("All manifests must use the same schema version") by_type = {item["ManifestType"]: item for item in manifests} if by_type["version"]["DefaultLocale"] != by_type["defaultLocale"]["PackageLocale"]: raise ValueError("DefaultLocale does not match the default locale manifest") @@ -129,6 +134,7 @@ def validate_directory(directory, release): validate_manifests(manifests, release) for path, manifest in zip(paths, manifests): kind = manifest["ManifestType"] + schema_version = manifest["ManifestVersion"] suffix = { "installer": ".installer", "version": "", @@ -139,7 +145,7 @@ def validate_directory(directory, release): raise ValueError(f"Unexpected manifest filename: {path.name}") url = ( "https://raw.githubusercontent.com/microsoft/winget-cli/master/schemas/JSON/manifests/" - f"v{SCHEMA_VERSION}/manifest.{kind}.{SCHEMA_VERSION}.json" + f"v{schema_version}/manifest.{kind}.{schema_version}.json" ) with urlopen(url, timeout=30) as response: schema = json.load(response) From 1dd58b68ada407dcda74be50767f72dda305e23a Mon Sep 17 00:00:00 2001 From: yiguo Date: Thu, 5 Nov 2026 21:09:25 +0800 Subject: [PATCH 2/2] fix(release): remove custom manifest schema version checks --- build_scripts/README.md | 5 ++--- build_scripts/tests/test_winget_manifest.py | 22 +++++---------------- build_scripts/winget_manifest.py | 11 ++--------- 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/build_scripts/README.md b/build_scripts/README.md index edae7868a..496e39c39 100644 --- a/build_scripts/README.md +++ b/build_scripts/README.md @@ -219,9 +219,8 @@ build jobs together. The workflow pipes `komac update --dry-run` output through `awk` to save separate YAML files by manifest type and locale; dry-run returns before handling `--output`. [winget_manifest.py](winget_manifest.py) retains identity, release URL and user - installation policy checks. Manifests must share a numeric schema version and - pass Microsoft's JSON schema for that declared version; the helper does not - rewrite the generator's `ManifestVersion`. Komac supplies the schema-required `InstallerSha256`; + installation policy checks and validates each file against Microsoft's JSON + schema for its declared `ManifestVersion`. Komac supplies the schema-required `InstallerSha256`; no extra comparison with GitHub asset digests is performed. Before submission, `komac sync-fork` updates the organization fork using the same PAT and `KOMAC_FORK_OWNER` as the final submit. Fork synchronization changes GitHub; diff --git a/build_scripts/tests/test_winget_manifest.py b/build_scripts/tests/test_winget_manifest.py index 5c9ff0602..c3d53738b 100644 --- a/build_scripts/tests/test_winget_manifest.py +++ b/build_scripts/tests/test_winget_manifest.py @@ -138,7 +138,6 @@ def test_rejects_mixed_package_version_and_locale(self): for index, field, value in ( (1, "PackageVersion", "26.9.2"), (0, "PackageIdentifier", "Other.Package"), - (0, "ManifestVersion", "1.10.0"), (2, "DefaultLocale", "zh-CN"), ): with self.subTest(field=field): @@ -152,22 +151,11 @@ def test_rejects_non_release(self): with self.subTest(field=field), self.assertRaises(ValueError): validate_manifests(self.manifests, self.release | {field: True}) - def test_accepts_a_consistent_generator_schema_version(self): - for version in ("1.10.0", "1.12.0", "1.28.0"): - with self.subTest(version=version): - manifests = deepcopy(self.manifests) - for manifest in manifests: - manifest["ManifestVersion"] = version - validate_manifests(manifests, self.release) - - def test_rejects_unsafe_or_missing_schema_versions(self): - for version in (None, "", "v1.12.0", "../1.12.0", "1.12.0?raw=1", 1.12): - with self.subTest(version=version): - manifests = deepcopy(self.manifests) - for manifest in manifests: - manifest["ManifestVersion"] = version - with self.assertRaises(ValueError): - validate_manifests(manifests, self.release) + def test_release_policy_does_not_pin_manifest_schema_versions(self): + manifests = deepcopy(self.manifests) + for manifest, version in zip(manifests, ("1.10.0", "1.12.0", "1.28.0")): + manifest["ManifestVersion"] = version + validate_manifests(manifests, self.release) if __name__ == "__main__": diff --git a/build_scripts/winget_manifest.py b/build_scripts/winget_manifest.py index c932781c1..2df4b0f1e 100644 --- a/build_scripts/winget_manifest.py +++ b/build_scripts/winget_manifest.py @@ -6,11 +6,11 @@ import argparse import json -import re from collections import Counter from copy import deepcopy from pathlib import Path from typing import ClassVar +from urllib.parse import quote from urllib.request import urlopen PACKAGE_ID = "YuanDevLLC.OneXray" @@ -56,13 +56,6 @@ def validate_manifests(manifests, release): for item in manifests: if item.get("PackageIdentifier") != PACKAGE_ID or item.get("PackageVersion") != version: raise ValueError("Manifest package/version does not match the release") - schema_version = item.get("ManifestVersion") - if not isinstance(schema_version, str) or not re.fullmatch( - r"[0-9]+\.[0-9]+\.[0-9]+", schema_version - ): - raise ValueError("Expected a numeric manifest schema version") - if schema_version != manifests[0].get("ManifestVersion"): - raise ValueError("All manifests must use the same schema version") by_type = {item["ManifestType"]: item for item in manifests} if by_type["version"]["DefaultLocale"] != by_type["defaultLocale"]["PackageLocale"]: raise ValueError("DefaultLocale does not match the default locale manifest") @@ -134,7 +127,7 @@ def validate_directory(directory, release): validate_manifests(manifests, release) for path, manifest in zip(paths, manifests): kind = manifest["ManifestType"] - schema_version = manifest["ManifestVersion"] + schema_version = quote(manifest["ManifestVersion"], safe="") suffix = { "installer": ".installer", "version": "",