Skip to content
Merged
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
148 changes: 148 additions & 0 deletions .claude/skills/fix-security-failures/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
name: fix-security-failures
description: Fix pip-audit security vulnerability failures. Use when CI fails due to pip-audit findings (CVE/GHSA). Handles both fixable and unfixable (upstream-only) vulnerabilities with graceful exit.
---

# Fix Security Failures (pip-audit)

## Step 1: Parse the Vulnerability Report

Search the failure logs for pip-audit findings:

```bash
grep -i "CVE-\|GHSA-\|vulnerability\|Found.*vulnerability\|pip-audit" .failure-logs.txt | head -100
```

Extract for each finding:
- **Package name** (e.g., `requests`)
- **Installed version** (e.g., `2.28.0`)
- **Vulnerability ID** (e.g., `GHSA-xxxx-xxxx-xxxx` or `CVE-2024-xxxxx`)
- **Fix version** if listed (pip-audit often states `Fix versions: X.Y.Z`)

pip-audit output format to recognize:
```
requests 2.28.0 GHSA-xxxx Fix versions: 2.31.0
filelock 3.12.0 CVE-2024-x Fix versions: (none)
```

## Step 2: For Each Vulnerable Package — Check PyPI for a Patched Version

### 2a. If pip-audit already lists fix versions

Use those directly — skip to Step 3.

### 2b. If no fix version is listed, check PyPI

```bash
# Check all available versions on PyPI
pip index versions <package-name> 2>/dev/null | head -5

# Or query the PyPI JSON API directly
curl -s "https://pypi.org/pypi/<package-name>/json" | python3 -c "
import sys, json
data = json.load(sys.stdin)
versions = sorted(data['releases'].keys())
print('Available versions:', versions[-10:])
print('Latest:', data['info']['version'])
"
```

### 2c. Determine if a patch exists

A patch exists if there is **any published version higher than the installed version** that is NOT listed in the vulnerability's `fixed_in` exclusions.

**No patch exists** if:
- pip-audit explicitly states `Fix versions: (none)` or `No fix available`
- The PyPI API shows no newer releases
- The vulnerability advisory (GHSA/CVE) states the fix requires changes in a **dependency of the package** (i.e., the vulnerable code is in a transitive dependency that the package author hasn't yet updated)
- The latest PyPI version is the same as or older than the installed version

## Step 3: Apply the Fix (only if a patch version exists)

Update the version constraint in `pyproject.toml`:

```bash
# Find current constraint
grep -n "<package-name>" pyproject.toml
```

Edit `pyproject.toml` to require the patched minimum version:
- Change `"package>=1.0"` → `"package>=<fix-version>"`
- Or add a lower bound: `"package"` → `"package>=<fix-version>"`

Then regenerate the lock file:

```bash
uv lock
uv sync
```

Verify pip-audit now passes locally:

```bash
uv run pip-audit
```

If clean, commit:

```bash
git add pyproject.toml uv.lock
git commit -m "chore: bump <package> to <fix-version> to fix <CVE/GHSA-ID>

Co-authored-by: aieng-bot <aieng-bot@vectorinstitute.ai>"
```

## Step 4: Graceful Exit When No Patch Is Available

**This is the critical path.** If ANY vulnerability has no patched version available, do NOT attempt to fix it. The fix must come from the upstream library maintainers.

### Identify unfixable vulnerabilities

A vulnerability is unfixable if Step 2 confirms no patch version exists on PyPI.

### Post a PR comment explaining the situation

```bash
PR_NUMBER=$(cat .pr-context.json | python3 -c "import sys,json; print(json.load(sys.stdin)['pr_number'])")
REPO=$(cat .pr-context.json | python3 -c "import sys,json; print(json.load(sys.stdin)['repo'])")

gh pr comment "$PR_NUMBER" --repo "$REPO" --body "## Security Vulnerability — No Patch Available Yet

aieng-bot found the following security vulnerabilities reported by pip-audit, but **cannot fix them automatically** because no patched version has been released to PyPI yet:

| Package | Version | Vulnerability | Status |
|---------|---------|---------------|--------|
| <package> | <version> | <CVE/GHSA> | No fix available on PyPI |

### Why this cannot be auto-fixed

The vulnerability exists in \`<package>\` itself (or one of its dependencies). A fix requires the upstream maintainers to release a new version. Once a patched release is published to PyPI, aieng-bot can re-run and apply the update automatically.

### Recommended next steps

1. Monitor the vulnerability advisory for a patch release
2. Check if a \`pip-audit\` ignore/exception can be added temporarily with justification (requires human review)
3. Consider whether this dependency can be replaced with an alternative

_This PR will not be auto-merged until the vulnerability is resolved._"
```

### Exit without making any changes

Do **not** modify `pyproject.toml`, `uv.lock`, or any other file. Do **not** commit anything. Stop here and let the human team handle it.

## Step 5: Mixed Case — Some Fixable, Some Not

If a PR has multiple vulnerabilities where some have patches and some don't:

1. Fix all the patchable ones (Step 3)
2. Post a comment listing the unfixable ones (Step 4 comment template)
3. Do NOT merge the PR — leave it for human review
4. Push the partial fixes so CI can re-run and confirm the remaining vulnerabilities

## Common Mistakes to Avoid

- **Do not pin to an exact version** (e.g., `==2.31.0`) — use a minimum bound (`>=2.31.0`) to allow future patch upgrades
- **Do not ignore or suppress pip-audit findings** with `--ignore-vuln` unless a human has explicitly approved it
- **Do not assume transitive dependency bumps are safe** — always run `uv sync` and check that tests still pass after bumping
- **Do not mark the PR as fixed** if an unfixable vulnerability remains — the CI will still fail
8 changes: 4 additions & 4 deletions dashboard/components/failure-analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function FailureAnalysis({ failure }: FailureAnalysisProps) {
case 'lint':
return 'Code style and linting issues detected. The bot automatically reformatted and fixed code quality issues.'
case 'security':
return 'Security vulnerabilities detected. The bot analyzed and resolved security issues in dependencies.'
return 'Security vulnerabilities detected. The bot analyzed dependencies for CVE/GHSA findings. If a patched version was available on PyPI, the bot bumped the version constraint and regenerated the lock file. If no patch exists yet, the bot left a PR comment explaining the issue and exited without changes.'
case 'build':
return 'Build compilation errors detected. The bot fixed configuration and code issues to restore builds.'
case 'merge_conflict':
Expand Down Expand Up @@ -200,15 +200,15 @@ export default function FailureAnalysis({ failure }: FailureAnalysisProps) {
<>
<li className="flex items-start space-x-2">
<span className="text-red-600 dark:text-red-400 mt-0.5">•</span>
<span>Identified vulnerable dependencies</span>
<span>Parsed pip-audit output for CVE/GHSA IDs and affected package versions</span>
</li>
<li className="flex items-start space-x-2">
<span className="text-red-600 dark:text-red-400 mt-0.5">•</span>
<span>Updated to patched versions or applied workarounds</span>
<span>Checked PyPI for a patched release; bumped version constraint in pyproject.toml and regenerated uv.lock if a fix was available</span>
</li>
<li className="flex items-start space-x-2">
<span className="text-red-600 dark:text-red-400 mt-0.5">•</span>
<span>Verified security scans pass</span>
<span>If no patch exists upstream, left a PR comment listing the unresolved CVEs and exited without modifying files — requires human review</span>
</li>
</>
)}
Expand Down
3 changes: 2 additions & 1 deletion src/aieng_bot/agent_fixer/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
Skills provide conventions and context - use them for reference when needed:
- `/python-conventions` - uv, ruff, mypy conventions
- `/merge-resolution` - How to resolve merge conflicts
- `/fix-security-failures` - pip-audit vulnerability triage: bump fixable packages, gracefully exit when no patch exists upstream

**You handle ALL workflow steps directly** - skills don't do git operations.

Expand Down Expand Up @@ -98,7 +99,7 @@
```bash
grep -i "error\|fail\|exception" .failure-logs.txt | head -50
```
3. Fix the issues (use `/python-conventions` for guidance)
3. Fix the issues (use `/python-conventions` for general guidance; use `/fix-security-failures` for pip-audit/CVE failures)
4. Commit and go to Step 2:
```bash
git add -A
Expand Down
1 change: 1 addition & 0 deletions tests/agent_fixer/test_fixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ def test_build_agentic_prompt(self, agentic_request):
assert "Skills (Context Only)" in prompt
assert "/python-conventions" in prompt
assert "/merge-resolution" in prompt
assert "/fix-security-failures" in prompt
assert ".pr-context.json" in prompt
assert ".failure-logs.txt" in prompt
assert "gh pr checks" in prompt
Expand Down
Loading