name: Vulnerability Scan on: pull_request: push: branches: [main] schedule: # Run weekly on Monday at 9:00 UTC - cron: '0 9 * * 1' permissions: contents: read jobs: pip-audit: name: pip-audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Restore global uv cache id: cache-restore uses: actions/cache/restore@v4 with: path: | ~/.cache/uv ~/.local/share/uv .venv key: uv-main-py3.11-${{ hashFiles('uv.lock') }} restore-keys: | uv-main-py3.11- - name: Install uv uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 with: version: "0.11.3" python-version: "3.11" enable-cache: false - name: Install dependencies run: uv sync --all-groups --all-extras --no-install-project - name: Install pip-audit run: uv pip install pip-audit - name: Run pip-audit run: | uv run pip-audit --desc --aliases --skip-editable --format json --output pip-audit-report.json \ --ignore-vuln CVE-2026-3219 \ --ignore-vuln GHSA-r374-rxx8-8654 \ --ignore-vuln PYSEC-2024-277 \ --ignore-vuln PYSEC-2026-89 \ --ignore-vuln PYSEC-2026-97 \ --ignore-vuln PYSEC-2025-148 \ --ignore-vuln PYSEC-2025-183 \ --ignore-vuln PYSEC-2025-189 \ --ignore-vuln PYSEC-2025-190 \ --ignore-vuln PYSEC-2025-191 \ --ignore-vuln PYSEC-2025-192 \ --ignore-vuln PYSEC-2025-193 \ --ignore-vuln PYSEC-2025-194 \ --ignore-vuln PYSEC-2025-195 \ --ignore-vuln PYSEC-2025-196 \ --ignore-vuln PYSEC-2025-197 \ --ignore-vuln PYSEC-2025-210 \ --ignore-vuln PYSEC-2026-139 \ --ignore-vuln PYSEC-2025-211 \ --ignore-vuln PYSEC-2025-212 \ --ignore-vuln PYSEC-2025-213 \ --ignore-vuln PYSEC-2025-214 \ --ignore-vuln PYSEC-2025-215 \ --ignore-vuln PYSEC-2025-216 \ --ignore-vuln PYSEC-2025-217 \ --ignore-vuln PYSEC-2025-218 # Ignored CVEs: # CVE-2026-3219 - pip 26.0.1 (GHSA-58qw-9mgm-455v): no fix available, archive handling issue # GHSA-r374-rxx8-8654 - paramiko 4.0.0 (SHA-1 in rsakey.py): no fix available; transitive via composio-core # PYSEC-2024-277 - joblib 1.5.3: disputed; NumpyArrayWrapper only used with trusted caches # PYSEC-2026-89 - markdown 3.10.2: DoS via malformed HTML; fix 3.8.1 — already past, advisory range is stale # PYSEC-2026-97 - nltk 3.9.4: arbitrary file read in filestring(); no fix available # PYSEC-2025-148 - onnx 1.21.0: path traversal in save_external_data; no fix available # PYSEC-2025-183 - pyjwt 2.12.1: disputed weak-encryption claim; key length is application-chosen # PYSEC-2025-189..197 - torch 2.11.0: memory-corruption/DoS in functions only reachable via untrusted models; no fix available # PYSEC-2025-210, PYSEC-2026-139 - torch 2.11.0: profiler/deserialization issues; no fix available # PYSEC-2025-211..218 - transformers 5.5.4: deserialization/code injection via malicious model checkpoints; no fix available continue-on-error: true - name: Display results if: always() run: | if [ -f pip-audit-report.json ]; then echo "## pip-audit Results" >> $GITHUB_STEP_SUMMARY echo '```json' >> $GITHUB_STEP_SUMMARY cat pip-audit-report.json | python3 -m json.tool >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY # Fail if vulnerabilities found python3 -c " import json, sys with open('pip-audit-report.json') as f: data = json.load(f) vulns = [d for d in data.get('dependencies', []) if d.get('vulns')] if vulns: print(f'::error::Found vulnerabilities in {len(vulns)} package(s)') for v in vulns: for vuln in v['vulns']: print(f' - {v[\"name\"]}=={v[\"version\"]}: {vuln[\"id\"]}') sys.exit(1) print('No known vulnerabilities found') " else echo "::error::pip-audit failed to produce a report. Check the pip-audit step logs." exit 1 fi - name: Upload pip-audit report if: always() uses: actions/upload-artifact@v4 with: name: pip-audit-report path: pip-audit-report.json - name: Save uv caches if: steps.cache-restore.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | ~/.cache/uv ~/.local/share/uv .venv key: uv-main-py3.11-${{ hashFiles('uv.lock') }}