Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,12 @@ jobs:
# CVE-2026-4539 currently has no upstream Pygments fix release.
# pip-audit cannot audit unpublished editable path requirements, so
# the standalone Rust-backed pickle package is audited separately.
uv export --no-hashes --no-emit-project \
uv export --extra all-ci --no-hashes --no-emit-project \
| sed -e '/^-e \.\/packages\/modelaudit-picklescan$/d' -e '/^modelaudit-picklescan==/d' \
> /tmp/modelaudit-audit-requirements.txt
uvx pip-audit --strict --desc --ignore-vuln CVE-2026-4539 -r /tmp/modelaudit-audit-requirements.txt
# The export already contains every pinned transitive dependency.
uvx pip-audit --strict --no-deps --disable-pip --desc \
--ignore-vuln CVE-2026-4539 -r /tmp/modelaudit-audit-requirements.txt
uv export --project packages/modelaudit-picklescan --no-hashes --no-emit-project \
> /tmp/modelaudit-picklescan-audit-requirements.txt
if grep -Eq '^[A-Za-z0-9_.-]' /tmp/modelaudit-picklescan-audit-requirements.txt; then
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Bug Fixes

- Upgrade vulnerable locked dependencies, use the hardened MLflow tracking client, and audit all installed CI extras.
- Avoid network false positives for bounded README examples that download sample images over HTTPS from Hugging Face.
- Prevent Windows cache identity probes from creating locked temporary files inside scanned directories.
- Preserve locked Windows cache probes reached through directory aliases while clearing stale scan results.
Expand Down
2 changes: 1 addition & 1 deletion docs/agents/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ModelAudit uses optional dependencies to keep the base installation lightweight:
| `tflite` | `tflite` | TensorFlow Lite parsing |
| `tensorflow` | `tensorflow` | Optional TF runtime-dependent paths on Python 3.11-3.12 |
| `xgboost` | `xgboost`, `py-ubjson` | XGBoost UBJ/full validation paths |
| `mlflow` | `mlflow` | MLflow registry source support |
| `mlflow` | `mlflow-skinny` | MLflow registry source support |
| `sevenzip` | `py7zr` | 7z archive scanner |
| `all-ci` | most portable extras except TensorFlow | CI profile |
| `all` | broad portable extras | Broad local scanner coverage; excludes TensorFlow and platform-specific TensorRT |
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ flax = ["msgpack>=1.2.1,<2.0"]
tflite = ["tflite>=2.18.0"]
tensorrt = ["tensorrt>=8.6.0; sys_platform == 'linux' or sys_platform == 'win32'"]
xgboost = ["xgboost>=3.2,<3.3", "py-ubjson>=0.16.0"]
mlflow = ["mlflow>=2.12.0"]
mlflow = ["mlflow-skinny>=3.13.0"]
sevenzip = ["py7zr>=1.1.3"]
# Full ML framework bundle with version-appropriate NumPy
# Note: TensorFlow is no longer required - vendored protos handle SavedModel scanning
Expand Down Expand Up @@ -116,7 +116,7 @@ all-ci = [
"xgboost>=3.2,<3.3",
"py-ubjson>=0.16.0",
"py7zr>=1.1.3",
"mlflow>=2.12.0",
"mlflow-skinny>=3.13.0",
"huggingface-hub>=0.23.0",
]
# Lightweight Windows CI dependencies (avoids heavy ML frameworks)
Expand All @@ -143,7 +143,7 @@ all = [
"xgboost>=3.2,<3.3",
"py-ubjson>=0.16.0",
"py7zr>=1.1.3",
"mlflow>=2.12.0",
"mlflow-skinny>=3.13.0",
"huggingface-hub>=0.23.0",
]

Expand Down
30 changes: 29 additions & 1 deletion tests/test_dependency_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import re
from pathlib import Path

import pytest

try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python 3.10 compatibility
Expand All @@ -12,11 +14,13 @@
LOCKFILE = ROOT_DIR / "uv.lock"
ROOT_PYPROJECT = ROOT_DIR / "pyproject.toml"
PICKLESCAN_PYPROJECT = ROOT_DIR / "packages" / "modelaudit-picklescan" / "pyproject.toml"
PATCHED_GITPYTHON_FLOOR = (3, 1, 50)
PATCHED_GITPYTHON_FLOOR = (3, 1, 58)
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"
PY7ZR_EXTRAS = ("sevenzip", "all-ci", "all")
PATCHED_MLFLOW_CLIENT_REQUIREMENT = "mlflow-skinny>=3.13.0"
MLFLOW_EXTRAS = ("mlflow", "all-ci", "all")
NUMPY_REQUIREMENTS = {
"numpy>=1.19.0,<2.0; python_version == '3.10'",
"numpy>=2.4.3,<2.5; python_version == '3.11'",
Expand Down Expand Up @@ -64,12 +68,36 @@ def test_gitpython_lock_stays_on_patched_release_floor() -> None:
assert "gitpython-3.1.49" not in gitpython_block


@pytest.mark.parametrize(
("package_name", "patched_floor"),
[
("aiohttp", (3, 14, 3)),
("cryptography", (50, 0, 0)),
("keras", (3, 15, 0)),
],
)
def test_security_sensitive_dependencies_stay_on_patched_release_floors(
package_name: str,
patched_floor: tuple[int, ...],
) -> None:
assert _locked_version(_lock_package_block(package_name)) >= patched_floor


def test_mlflow_skinny_transitive_gitpython_dependency_is_guarded() -> None:
mlflow_skinny_block = _lock_package_block("mlflow-skinny")

assert _locked_version(mlflow_skinny_block) >= (3, 13, 0)
assert "gitpython" in _dependency_names(mlflow_skinny_block)


def test_mlflow_extras_use_the_hardened_tracking_client() -> None:
root_config = tomllib.loads(ROOT_PYPROJECT.read_text(encoding="utf-8"))
optional_dependencies = root_config["project"]["optional-dependencies"]

for extra in MLFLOW_EXTRAS:
assert PATCHED_MLFLOW_CLIENT_REQUIREMENT in optional_dependencies[extra]


def test_picklescan_build_backend_is_exactly_pinned() -> None:
package_config = tomllib.loads(PICKLESCAN_PYPROJECT.read_text(encoding="utf-8"))
build_system = package_config["build-system"]
Expand Down
11 changes: 11 additions & 0 deletions tests/test_perf_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,17 @@ def test_dependency_audit_runs_for_source_reachability_changes() -> None:
assert "needs.changes.outputs.picklescan == 'true'" in condition


def test_dependency_audit_covers_installed_ci_extras() -> None:
workflow = _load_workflow("test.yml")
steps = _jobs(workflow)["dependency-audit"]["steps"]
assert isinstance(steps, list)

audit_step = _step_by_name(steps, "Audit dependencies for vulnerabilities")

assert "uv export --extra all-ci --no-hashes --no-emit-project" in audit_step["run"]
assert "pip-audit --strict --no-deps --disable-pip" in audit_step["run"]


def test_python_ci_triggers_merge_group_and_cancels_superseded_main_runs() -> None:
workflow = _load_workflow("test.yml")
triggers = _workflow_triggers(workflow)
Expand Down
Loading
Loading