diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf4384a1c..84b9f9afb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,12 +87,12 @@ jobs: rm -rf codecov codecov.SHA256SUM codecov.SHA256SUM.sig - name: Run json schema extract - # This should be kept before the repository check to ensure that the schema is up-to-date + # This must be kept before the repository check to ensure that the schema is up-to-date run: | python build_helpers/extract_config_json_schema.py - name: Run command docs partials extract - # This should be kept before the repository check to ensure that the docs are up-to-date + # This must be kept before the repository check to ensure that the docs are up-to-date if: ${{ (matrix.python-version == '3.13') }} run: | python build_helpers/create_command_partials.py diff --git a/requirements-dev.txt b/requirements-dev.txt index 38a49f661..a59805e46 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -30,3 +30,4 @@ types-filelock==3.2.7 types-requests==2.32.4.20250913 types-tabulate==0.9.0.20241207 types-python-dateutil==2.9.0.20251115 +pip-audit==2.10.0 diff --git a/tests/test_pip_audit.py b/tests/test_pip_audit.py new file mode 100644 index 000000000..ab9017b89 --- /dev/null +++ b/tests/test_pip_audit.py @@ -0,0 +1,92 @@ +""" +Run pip audit to check for known security vulnerabilities in installed packages. +Original Idea and base for this implementation by Michael Kennedy's blog: +https://mkennedy.codes/posts/python-supply-chain-security-made-easy/ +""" + +import subprocess +import sys +from pathlib import Path + +import pytest + + +def test_pip_audit_no_vulnerabilities(): + """ + Run pip-audit to check for known security vulnerabilities. + + This test will fail if any vulnerabilities are detected in the installed packages. + + Note: CVE-2025-53000 (nbconvert Windows vulnerability) is ignored as it only affects + Windows platforms and is a known acceptable risk for this project. + """ + # Get the project root directory + project_root = Path(__file__).parent.parent + command = [ + sys.executable, + "-m", + "pip_audit", + # "--format=json", + "--progress-spinner=off", + "--ignore-vuln", + "CVE-2025-53000", + "--skip-editable", + ] + + # Run pip-audit with JSON output for easier parsing + try: + result = subprocess.run( + command, + cwd=project_root, + capture_output=True, + text=True, + timeout=120, # 2 minute timeout + ) + except subprocess.TimeoutExpired: + pytest.fail("pip-audit command timed out after 120 seconds") + except FileNotFoundError: + pytest.fail("pip-audit not installed or not accessible") + + # Check if pip-audit found any vulnerabilities + if result.returncode != 0: + # pip-audit returns non-zero when vulnerabilities are found + error_output = result.stdout + "\n" + result.stderr + + # Check if it's an actual vulnerability vs an error + if "vulnerabilities found" in error_output.lower() or '"dependencies"' in result.stdout: + pytest.fail( + f"pip-audit detected security vulnerabilities!\n\n" + f"Output:\n{result.stdout}\n\n" + f"Please review and update vulnerable packages.\n" + f"Run manually with: {' '.join(command)}" + ) + else: + # Some other error occurred + pytest.fail( + f"pip-audit failed to run properly:\n\nReturn code: {result.returncode}\n" + f"Output: {error_output}\n" + ) + + # Success - no vulnerabilities found + assert result.returncode == 0, "pip-audit should return 0 when no vulnerabilities are found" + + +def test_pip_audit_runs_successfully(): + """ + Verify that pip-audit can run successfully (even if vulnerabilities are found). + + This is a smoke test to ensure pip-audit is properly installed and functional. + """ + try: + result = subprocess.run( + [sys.executable, "-m", "pip_audit", "--version"], + capture_output=True, + text=True, + timeout=10, + ) + assert result.returncode == 0, f"pip-audit --version failed: {result.stderr}" + assert "pip-audit" in result.stdout.lower(), "pip-audit version output unexpected" + except FileNotFoundError: + pytest.fail("pip-audit not installed") + except subprocess.TimeoutExpired: + pytest.fail("pip-audit --version timed out")