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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - name: Restore global uv cache id: cache-restore uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 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 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 \ --ignore-vuln GHSA-f4j7-r4q5-qw2c # Ignored CVEs: # 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 # GHSA-f4j7-r4q5-qw2c - chromadb 1.1.1 (CVE-2026-45829): pre-auth RCE via /api/v2/tenants/{tenant}/databases/{db}/collections when trust_remote_code=true. # Advisory: vulnerable >=1.0.0,<=1.5.9, firstPatchedVersion=none. We only use chromadb.PersistentClient (lib/crewai/src/crewai/rag/chromadb/factory.py) # and chromadb.utils.embedding_functions; the chromadb HTTP server is never started, so the vulnerable route is not exposed. 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cache/uv ~/.local/share/uv .venv key: uv-main-py3.11-${{ hashFiles('uv.lock') }}