From 0b9e129e8d0831d0964b333cf964d77b262c936c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20=C3=87i=C3=A7ek?= <75256866+mustafacicek-eee@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:21:15 +0300 Subject: [PATCH] fix(bundles): reject non-ASCII digits in SemVer identifiers Restrict the SemVer regex to ASCII so Unicode decimal digits are rejected in bundle and component versions. Cover valid ASCII and invalid Unicode versions in both the version helper and the validate command. Assisted-by: OpenAI Codex (model: GPT-6, autonomous) --- src/specify_cli/bundles/versioning.py | 3 ++- .../bundles/test_command_validate.py | 25 +++++++++++++++++-- tests/specify_cli/bundles/test_versioning.py | 9 +++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/specify_cli/bundles/versioning.py b/src/specify_cli/bundles/versioning.py index df73b5bab6..0837b38bee 100644 --- a/src/specify_cli/bundles/versioning.py +++ b/src/specify_cli/bundles/versioning.py @@ -95,7 +95,8 @@ def same_version(actual: str, pinned: str) -> bool: r"^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)" r"(?:-(?:(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)" r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" - r"(?:\+(?:[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" + r"(?:\+(?:[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + flags=re.ASCII, ) diff --git a/tests/specify_cli/bundles/test_command_validate.py b/tests/specify_cli/bundles/test_command_validate.py index f0b22f7963..28da55bdd8 100644 --- a/tests/specify_cli/bundles/test_command_validate.py +++ b/tests/specify_cli/bundles/test_command_validate.py @@ -6,6 +6,7 @@ from unittest.mock import patch # noqa: F401 import yaml # noqa: F401 +import pytest from typer.testing import CliRunner from specify_cli import app @@ -27,9 +28,12 @@ def test_validate_reports_invalid_manifest(project: Path): assert "license" in result.output -def test_validate_accepts_valid_manifest(project: Path): +@pytest.mark.parametrize("version", ["1.2.0", "1.20.30-12alpha.1+build.01", "V10.20.30"]) +def test_validate_accepts_valid_manifest(project: Path, version: str): + data = valid_manifest_dict() + data["bundle"]["version"] = version (project / "bundle.yml").write_text( - yaml.safe_dump(valid_manifest_dict()), encoding="utf-8" + yaml.safe_dump(data), encoding="utf-8" ) # Offline mode does not fail on references it cannot verify (synthetic ids # here); they surface as warnings while structure is confirmed valid. @@ -38,6 +42,23 @@ def test_validate_accepts_valid_manifest(project: Path): assert "valid" in result.output +@pytest.mark.parametrize("version", ["1٢.2.3", "1.2.3-1٢", "1.2.3-٢alpha"]) +@pytest.mark.parametrize("field", ["bundle", "extension"]) +def test_validate_rejects_non_ascii_version(project: Path, version: str, field: str): + data = valid_manifest_dict() + if field == "bundle": + data["bundle"]["version"] = version + else: + data["provides"]["extensions"][0]["version"] = version + (project / "bundle.yml").write_text(yaml.safe_dump(data), encoding="utf-8") + + result = runner.invoke(app, ["bundle", "validate", "--offline"]) + + assert result.exit_code == 1, result.output + assert "invalid" in result.output + assert version in result.output + + def test_validate_escapes_manifest_markup_in_errors(project: Path): data = valid_manifest_dict() # An invalid constraint is echoed back inside the validation error. diff --git a/tests/specify_cli/bundles/test_versioning.py b/tests/specify_cli/bundles/test_versioning.py index aa6dfc1494..36d1117b8e 100644 --- a/tests/specify_cli/bundles/test_versioning.py +++ b/tests/specify_cli/bundles/test_versioning.py @@ -14,6 +14,15 @@ ("1.2.3-alpha1", True), ("1.2.3-beta2", True), ("v1.2.3", True), + ("1.20.30-12alpha.1+build.01", True), + ("V10.20.30", True), + # SemVer identifiers use ASCII digits, not Python's broader Unicode \d. + ("1٢.2.3", False), + ("1.2٣.3", False), + ("1.2.3٤", False), + ("1.2.3-1٢", False), + ("1.2.3-٢alpha", False), + ("1.2.3-1beta", False), ("not-a-version", False), ("", False), # packaging.version.Version accepts these partial versions; SemVer must not.