From d324555009ba09bcf29e04fe3669a549972fbeb5 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 27 Sep 2026 15:05:02 +0100 Subject: [PATCH 1/5] fix(ci): bound compatible lint and type checker versions --- pyproject.toml | 4 ++-- uv.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index db8e559d4..e705bd5fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,8 +178,8 @@ Changelog = "https://github.com/promptfoo/modelaudit/blob/main/CHANGELOG.md" dev = [ "pytest>=8.4.0", "coverage>=7.9.0", - "mypy>=1.16.0", - "ruff>=0.12.0", + "mypy>=1.16.0,<2.0.0", + "ruff>=0.12.0,<0.16.0", "types-PyYAML>=6.0.12.20250516", "types-tensorflow>=2.18.0.20250516", "types-requests>=2.31.0", diff --git a/uv.lock b/uv.lock index 7d5651c10..1c6e2e644 100644 --- a/uv.lock +++ b/uv.lock @@ -2164,13 +2164,13 @@ provides-extras = ["tensorflow", "h5", "pytorch", "safetensors", "onnx", "dill", dev = [ { name = "coverage", specifier = ">=7.9.0" }, { name = "dill", specifier = ">=0.4.0" }, - { name = "mypy", specifier = ">=1.16.0" }, + { name = "mypy", specifier = ">=1.16.0,<2.0.0" }, { name = "pytest", specifier = ">=8.4.0" }, { name = "pytest-asyncio", specifier = ">=1.1.0" }, { name = "pytest-cov", specifier = ">=6.2.1" }, { name = "pytest-xdist", specifier = ">=3.7.0" }, { name = "pyupgrade", specifier = ">=3.20.0" }, - { name = "ruff", specifier = ">=0.12.0" }, + { name = "ruff", specifier = ">=0.12.0,<0.16.0" }, { name = "ty", specifier = ">=0.0.1a20" }, { name = "types-click", specifier = ">=7.1.8" }, { name = "types-pyyaml", specifier = ">=6.0.12.20250516" }, From 966a5fad61efd7c99a044446dfed763332eb2656 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Mon, 28 Sep 2026 15:32:06 +0000 Subject: [PATCH 2/5] fix(ci): align standalone mypy validation dependencies --- .github/workflows/release-please.yml | 2 +- .github/workflows/test.yml | 2 +- docs/agents/picklescan-package-split.md | 2 +- packages/modelaudit-picklescan/AGENTS.md | 2 +- tests/test_release_workflow.py | 25 ++++++++++++++++++++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index bbb4854d0..479721542 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -679,7 +679,7 @@ jobs: run: uv run --with 'ruff==0.15.10' ruff format --check src tests - name: Type check standalone package with mypy - run: uv run --with mypy mypy src tests + run: uv run --with 'mypy>=1.16.0,<2.0.0' --with pytest mypy src tests - name: Run standalone package tests run: uv run --with pytest --with pytest-xdist pytest -n auto tests --tb=short diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb539363d..67ca372cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1020,7 +1020,7 @@ jobs: - name: Type check standalone package with mypy run: | - uv run --with mypy mypy src tests + uv run --with 'mypy>=1.16.0,<2.0.0' --with pytest mypy src tests - name: Run standalone package tests run: | diff --git a/docs/agents/picklescan-package-split.md b/docs/agents/picklescan-package-split.md index 2adae870b..f4e304ea0 100644 --- a/docs/agents/picklescan-package-split.md +++ b/docs/agents/picklescan-package-split.md @@ -130,7 +130,7 @@ Standalone package checks run from `packages/modelaudit-picklescan`: uv lock --check uv run --with 'ruff==0.15.10' ruff check src tests uv run --with 'ruff==0.15.10' ruff format --check src tests -uv run --with mypy mypy src tests +uv run --with 'mypy>=1.16.0,<2.0.0' --with pytest mypy src tests uv run --with pytest --with pytest-xdist pytest -n auto tests --tb=short uv run --with pytest pytest tests -q cargo fmt --manifest-path Cargo.toml -- --check diff --git a/packages/modelaudit-picklescan/AGENTS.md b/packages/modelaudit-picklescan/AGENTS.md index df7509f3d..3c56eda5c 100644 --- a/packages/modelaudit-picklescan/AGENTS.md +++ b/packages/modelaudit-picklescan/AGENTS.md @@ -42,7 +42,7 @@ Run from `packages/modelaudit-picklescan/`: uv lock --check uv run --with 'ruff==0.15.10' ruff check src tests uv run --with 'ruff==0.15.10' ruff format --check src tests -uv run --with mypy mypy src tests +uv run --with 'mypy>=1.16.0,<2.0.0' --with pytest mypy src tests uv run --with pytest --with pytest-xdist pytest -n auto tests --tb=short cargo fmt --manifest-path Cargo.toml -- --check diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 6146bacda..7a241bfb1 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -5,6 +5,7 @@ import io import json import os +import shlex import subprocess import sys import tarfile @@ -168,6 +169,30 @@ def test_standalone_package_lint_uses_locked_root_ruff_version() -> None: assert expected_command in guide_lines +@pytest.mark.parametrize( + ("workflow_name", "job_name"), + [("test.yml", "picklescan-package"), ("release-please.yml", "build-picklescan-package")], +) +def test_standalone_type_check_uses_supported_mypy(workflow_name: str, job_name: str) -> None: + root_dir = Path(__file__).resolve().parents[1] + root_project = tomllib.loads((root_dir / "pyproject.toml").read_text(encoding="utf-8")) + root_requirement = next( + requirement + for entry in root_project["dependency-groups"]["dev"] + if (requirement := Requirement(entry)).name == "mypy" + ) + workflow = yaml.safe_load((root_dir / ".github" / "workflows" / workflow_name).read_text(encoding="utf-8")) + step = _step_by_name(_job_steps(workflow, job_name), "Type check standalone package with mypy") + command = shlex.split(step["run"]) + requirements = [Requirement(command[index + 1]) for index, arg in enumerate(command) if arg == "--with"] + standalone_requirement = next(requirement for requirement in requirements if requirement.name == "mypy") + + assert standalone_requirement.specifier == root_requirement.specifier + assert standalone_requirement.specifier.contains("1.20.0") + assert not standalone_requirement.specifier.contains("2.0.0") + assert any(requirement.name == "pytest" for requirement in requirements) + + def test_release_workflow_manual_dispatch_inputs_and_guardrails() -> None: workflow = _load_release_workflow() From cb270b28125a13a39584c2cdc0ea60bf9b83faec Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Tue, 29 Sep 2026 15:14:26 +0000 Subject: [PATCH 3/5] fix(deps): carry independent audit remediation Reuse the AnyIO 4.14.2 and GitPython 3.1.60 lock updates and regression guards from PR #1857 so this branch passes the dependency audit independently. --- CHANGELOG.md | 1 + tests/test_dependency_lock.py | 4 +++- uv.lock | 12 ++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac57e303b..33027145b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade gzip, PCRE2, SQLite, and Perl in Docker runtime images to pick up Debian security fixes. - Upgrade locked GitPython to 3.1.59 to address four dependency audit advisories. +- Upgrade locked GitPython to 3.1.60 to address newly disclosed dependency audit advisories. - Inspect hidden ZIP archives and malicious pickle payloads in legacy GGML model variants. - Stop reporting a ZIP polyglot for GGUF/GGML files whose tensor data merely contains an end-of-central-directory signature. - Upgrade Debian util-linux packages in all Docker runtime images to remediate CVE-2026-53615. diff --git a/tests/test_dependency_lock.py b/tests/test_dependency_lock.py index a06feb7b7..6d4b67365 100644 --- a/tests/test_dependency_lock.py +++ b/tests/test_dependency_lock.py @@ -16,7 +16,8 @@ ROOT_PYPROJECT = ROOT_DIR / "pyproject.toml" RENOVATE_CONFIG = ROOT_DIR / "renovate.json" PICKLESCAN_PYPROJECT = ROOT_DIR / "packages" / "modelaudit-picklescan" / "pyproject.toml" -PATCHED_GITPYTHON_FLOOR = (3, 1, 59) +PATCHED_GITPYTHON_FLOOR = (3, 1, 60) +PATCHED_ANYIO_FLOOR = (4, 14, 2) PINNED_MATURIN_BACKEND = "maturin===1.13.3" REQUIRED_PICKLESCAN_RELEASE = "modelaudit-picklescan>=0.1.10,<0.2.0" PATCHED_PY7ZR_REQUIREMENT = "py7zr>=1.1.3" @@ -81,6 +82,7 @@ def test_gitpython_lock_stays_on_patched_release_floor() -> None: ("package_name", "patched_floor"), [ ("aiohttp", (3, 14, 3)), + ("anyio", PATCHED_ANYIO_FLOOR), ("cryptography", (50, 0, 0)), ("keras", (3, 15, 0)), ("sqlparse", (0, 6, 0)), diff --git a/uv.lock b/uv.lock index 1c6e2e644..e7eb60785 100644 --- a/uv.lock +++ b/uv.lock @@ -202,16 +202,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -1107,14 +1107,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.59" +version = "3.1.60" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/dc/126b28e76b24a9268ba931ad3e012f71ebdadf62fd9f17758f7074bb0b20/gitpython-3.1.59.tar.gz", hash = "sha256:0a1475cfdc38a5bfba1a3e9a4a9da52a39749ecec322b772915c019f94e5b7e4", size = 230445, upload-time = "2026-08-10T12:03:20.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/14/e6b1a48d831755a53c2029351fcef82e70db4a08f338daefe29d8d0cf31c/gitpython-3.1.60.tar.gz", hash = "sha256:e936431879fa85581b4311fa63492ea52251909e2d655b6529c704c904ddcc24", size = 230793, upload-time = "2026-08-25T18:33:46.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/ed/ae57eb7d344f43f87b74b3a281ead6ec7d6394eef72a7b1dcb28dd089550/gitpython-3.1.59-py3-none-any.whl", hash = "sha256:67a82f537384578643624c8b2c531938a9b82be431663e575dcf638526631d4c", size = 220996, upload-time = "2026-08-10T12:03:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/71/63/ba28697918b7c190af9f3f21940d03e8814e25dd4ddd39d6929f3a553995/gitpython-3.1.60-py3-none-any.whl", hash = "sha256:39548bffb8fa0f3a548133348868bb4838e79d73283052207dc97781a569b6b4", size = 221893, upload-time = "2026-08-25T18:33:44.75Z" }, ] [[package]] From 274694fd7067fe8037e17ffd631bc912b177f7d2 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Tue, 29 Sep 2026 15:29:47 +0000 Subject: [PATCH 4/5] docs(changelog): include AnyIO audit remediation --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33027145b..6ebd379a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade gzip, PCRE2, SQLite, and Perl in Docker runtime images to pick up Debian security fixes. - Upgrade locked GitPython to 3.1.59 to address four dependency audit advisories. -- Upgrade locked GitPython to 3.1.60 to address newly disclosed dependency audit advisories. +- Upgrade locked GitPython to 3.1.60 and AnyIO to 4.14.2 to address newly disclosed dependency audit advisories. - Inspect hidden ZIP archives and malicious pickle payloads in legacy GGML model variants. - Stop reporting a ZIP polyglot for GGUF/GGML files whose tensor data merely contains an end-of-central-directory signature. - Upgrade Debian util-linux packages in all Docker runtime images to remediate CVE-2026-53615. From 186945429553f828c66060bae09180bacd8569ff Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 3 Oct 2026 18:53:38 +0000 Subject: [PATCH 5/5] test(cache): make interrupt cleanup fixture portable --- tests/cache/test_cache_correctness.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/cache/test_cache_correctness.py b/tests/cache/test_cache_correctness.py index 94f2c7109..01b161d3e 100644 --- a/tests/cache/test_cache_correctness.py +++ b/tests/cache/test_cache_correctness.py @@ -1939,11 +1939,19 @@ def close(self) -> None: assert monitor.closed is True +@pytest.mark.parametrize("retry_capture", [False, True], ids=["initial-capture", "retried-capture"]) def test_identity_capture_closes_darwin_monitor_on_retained_keyboard_interrupt( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, + retry_capture: bool, ) -> None: created_monitors: list[Any] = [] + stat_comparisons = 0 + + def stat_matches(_left: os.stat_result, _right: os.stat_result) -> bool: + nonlocal stat_comparisons + stat_comparisons += 1 + return not retry_capture or stat_comparisons > 1 class StubDarwinPathMonitor: def __init__(self, _file_path: str, _ancestor_identity: tuple[Any, ...]) -> None: @@ -1961,16 +1969,25 @@ def interrupt_hash(_path: str, _file_stat: os.stat_result) -> str: file_path = _make_cacheable_file(tmp_path) cache = ScanResultsCache(str(tmp_path / "cache")) + ancestor_identity = cache._capture_ancestor_identity(str(file_path)) + barrier_token = max(1, *(entry[-1] for entry in ancestor_identity)) + 1 + monkeypatch.setattr(cache, "_get_file_change_token", lambda _path, _stat: 1) + monkeypatch.setattr(cache, "_capture_ancestor_identity", lambda _path: ancestor_identity) + monkeypatch.setattr(cache, "_advance_change_clock", lambda *_args: barrier_token) + monkeypatch.setattr(cache, "_stat_matches", stat_matches) monkeypatch.setattr(scan_results_cache_module.sys, "platform", "darwin") monkeypatch.setattr(scan_results_cache_module, "_DarwinPathMonitor", StubDarwinPathMonitor) monkeypatch.setattr(cache.hasher, "hash_file_with_stat", interrupt_hash) - with pytest.raises(KeyboardInterrupt, match="identity hashing interrupted") as interruption: - cache.capture_file_identity(str(file_path)) + with tempfile.TemporaryFile(mode="w+b", dir=tmp_path) as probe: + monkeypatch.setattr(cache, "_get_change_clock_probe", lambda _path, _device: probe) + with pytest.raises(KeyboardInterrupt, match="identity hashing interrupted") as interruption: + cache.capture_file_identity(str(file_path)) assert interruption.traceback is not None - assert len(created_monitors) == 1 - assert created_monitors[0].closed is True + assert stat_comparisons == (2 if retry_capture else 1) + assert len(created_monitors) == stat_comparisons + assert all(monitor.closed for monitor in created_monitors) @pytest.mark.parametrize("retry_capture", [False, True], ids=["initial-capture", "retried-capture"])