From 46986e664f16eba711341de7dd4d497a9e11ba59 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 19 Sep 2026 15:04:13 -0700 Subject: [PATCH 1/5] fix(deps): upgrade GitPython security floor --- CHANGELOG.md | 1 + tests/test_dependency_lock.py | 2 +- uv.lock | 6 +++--- 3 files changed, 5 insertions(+), 4 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..cfb76589e 100644 --- a/tests/test_dependency_lock.py +++ b/tests/test_dependency_lock.py @@ -16,7 +16,7 @@ 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) 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" diff --git a/uv.lock b/uv.lock index 7d5651c10..d845d636d 100644 --- a/uv.lock +++ b/uv.lock @@ -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 ac1483f7d231da101a83677bd66eb7932e4aafe8 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 20 Sep 2026 15:03:59 -0700 Subject: [PATCH 2/5] fix(deps): upgrade AnyIO security floor --- tests/test_dependency_lock.py | 2 ++ uv.lock | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_dependency_lock.py b/tests/test_dependency_lock.py index cfb76589e..6d4b67365 100644 --- a/tests/test_dependency_lock.py +++ b/tests/test_dependency_lock.py @@ -17,6 +17,7 @@ RENOVATE_CONFIG = ROOT_DIR / "renovate.json" PICKLESCAN_PYPROJECT = ROOT_DIR / "packages" / "modelaudit-picklescan" / "pyproject.toml" 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 d845d636d..6f4c6b6e6 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]] From b7bcc102c050c9f2598195cd5b67d96efb77cf5f Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 3 Oct 2026 18:53:29 +0000 Subject: [PATCH 3/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"]) From de9413c67867a5d138b0c9a745b45c899d98a5f2 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sat, 3 Oct 2026 19:16:59 +0000 Subject: [PATCH 4/5] docs: keep dependency floors in unreleased notes --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ab4342f..2d9a80628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Security + +- Require patched GitPython and AnyIO releases in published package dependencies as well as the lockfile. + ## [0.2.53](https://github.com/promptfoo/modelaudit/compare/v0.2.52...v0.2.53) (2026-10-03) ### Security - Upgrade gzip, PCRE2, SQLite, and Perl in Docker runtime images to pick up Debian security fixes. -- Require patched GitPython and AnyIO releases in published package dependencies as well as the lockfile. - Upgrade locked AnyIO to 4.15.1 and GitPython to 3.1.62 to address 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. From d397ee3fea1898f52a6255f6ddf9c97b02ec1bc2 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 4 Oct 2026 09:30:16 +0000 Subject: [PATCH 5/5] fix(deps): require GitPython 3.2.0 security fixes --- CHANGELOG.md | 3 ++- pyproject.toml | 6 +++--- tests/test_dependency_lock.py | 4 ++-- uv.lock | 12 ++++++------ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec3d8ef2..ebcfbe3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security -- Require patched GitPython and AnyIO releases in published package dependencies as well as the lockfile. +- Require GitPython 3.2.0 or newer in the `mlflow`, `all-ci`, and `all` extras and lockfile to fix `Remote.pull()` refspec option injection ([GHSA-f9j4-qggq-h239](https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-f9j4-qggq-h239)). +- Require AnyIO 4.14.2 or newer in published package dependencies as well as the lockfile. ### Changed diff --git a/pyproject.toml b/pyproject.toml index ef783c1f4..a8c950859 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ xgboost = [ ] mlflow = [ "mlflow-skinny>=3.13.0", - "gitpython>=3.1.60", + "gitpython>=3.2.0", "sqlalchemy>=2.0.49", "alembic>=1.18.4", "sqlparse>=0.6.0", @@ -130,7 +130,7 @@ all-ci = [ "py-ubjson>=0.16.0", "py7zr>=1.1.3", "mlflow-skinny>=3.13.0", - "gitpython>=3.1.60", + "gitpython>=3.2.0", "sqlalchemy>=2.0.49", "alembic>=1.18.4", "sqlparse>=0.6.0", @@ -162,7 +162,7 @@ all = [ "py-ubjson>=0.16.0", "py7zr>=1.1.3", "mlflow-skinny>=3.13.0", - "gitpython>=3.1.60", + "gitpython>=3.2.0", "sqlalchemy>=2.0.49", "alembic>=1.18.4", "sqlparse>=0.6.0", diff --git a/tests/test_dependency_lock.py b/tests/test_dependency_lock.py index 5ec6d6295..c719b2955 100644 --- a/tests/test_dependency_lock.py +++ b/tests/test_dependency_lock.py @@ -18,9 +18,9 @@ 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, 60) +PATCHED_GITPYTHON_FLOOR = (3, 2, 0) PATCHED_ANYIO_FLOOR = (4, 14, 2) -PATCHED_GITPYTHON_REQUIREMENT = "gitpython>=3.1.60" +PATCHED_GITPYTHON_REQUIREMENT = "gitpython>=3.2.0" PATCHED_ANYIO_REQUIREMENT = "anyio>=4.14.2" PINNED_MATURIN_BACKEND = "maturin===1.13.3" PATCHED_PY7ZR_REQUIREMENT = "py7zr>=1.1.3" diff --git a/uv.lock b/uv.lock index e8778c983..18fb0275b 100644 --- a/uv.lock +++ b/uv.lock @@ -1140,14 +1140,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.62" +version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/db/3ca813cbacb23ab6fe46ff38a9b5ef8e73e970c8051f2ce903aacafe0446/gitpython-3.1.62.tar.gz", hash = "sha256:1791de66309bc0c7cfca40bf8d2e3de7ca091cbf94e6051be1ad0722c61062af", size = 231728, upload-time = "2026-09-07T02:57:21.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/2d/6f6e649818da44d4499604802c89329b8d9799687a124e3a5e467a643336/gitpython-3.2.0.tar.gz", hash = "sha256:fb92310af6844d96adc95ca066ed2e617c00e1dbd146a326626c81e72e18cc2e", size = 238091, upload-time = "2026-09-30T09:10:32.65Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/0b/29d7965215f8ef830a7ca1f42997fe13e5693d85e9edb18f938d063ef5f2/gitpython-3.1.62-py3-none-any.whl", hash = "sha256:7002251225e10e29d2e1f49e6532613fe5d5d9f0b6f1f02997a52b38fe56899e", size = 222753, upload-time = "2026-09-07T02:57:19.762Z" }, + { url = "https://files.pythonhosted.org/packages/fa/48/f36d233ce749d1bb880cb5e6f8bb171dc2effabc7435c31da604d831b3ec/gitpython-3.2.0-py3-none-any.whl", hash = "sha256:bd70c5ec05cd2b797423e7eb312147d2458d3cca92085888fba2213f85905537", size = 228537, upload-time = "2026-09-30T09:10:31.159Z" }, ] [[package]] @@ -2167,9 +2167,9 @@ requires-dist = [ { name = "dill", marker = "extra == 'numpy1'", specifier = ">=0.3.0,<1.0" }, { name = "fsspec", specifier = ">=2025.5.1" }, { name = "gcsfs", specifier = ">=2025.5.1" }, - { name = "gitpython", marker = "extra == 'all'", specifier = ">=3.1.60" }, - { name = "gitpython", marker = "extra == 'all-ci'", specifier = ">=3.1.60" }, - { name = "gitpython", marker = "extra == 'mlflow'", specifier = ">=3.1.60" }, + { name = "gitpython", marker = "extra == 'all'", specifier = ">=3.2.0" }, + { name = "gitpython", marker = "extra == 'all-ci'", specifier = ">=3.2.0" }, + { name = "gitpython", marker = "extra == 'mlflow'", specifier = ">=3.2.0" }, { name = "h5py", marker = "extra == 'all'", specifier = ">=3.1,<4.0" }, { name = "h5py", marker = "extra == 'all-ci'", specifier = ">=3.1,<4.0" }, { name = "h5py", marker = "extra == 'h5'", specifier = ">=3.1,<4.0" },