Skip to content

Commit b3ed157

Browse files
authored
Merge pull request #96 from angaduom/improve-cve-fix-repo-scoping
feat: scope CVE fixes to affected container and improve CVE verification
2 parents fa92377 + fa47f64 commit b3ed157

3 files changed

Lines changed: 333 additions & 56 deletions

File tree

workflows/cve-fixer/.claude/commands/cve.find.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,26 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md
3131
## Process
3232

3333
1. **Parse Arguments and Flags**
34-
- Parse the command arguments for both the component name and optional flags
34+
- Parse the command arguments for the component name, optional subcomponent, and optional flags
3535
- **Supported flags:**
3636
- `--ignore-resolved` — Exclude issues with Jira status "Resolved" from results
37-
- The component name is any argument that is not a flag
37+
- The component name is the first argument that is not a flag
38+
- The subcomponent is the second positional argument that is not a flag (optional)
3839
- If component is not provided, ask the user to type the component name
3940
- **IMPORTANT**: Let the user type the component name freely as text input
4041
- **DO NOT** provide multiple-choice options or suggestions
4142
- **DO NOT** use AskUserQuestion tool with predefined options
4243
- Simply ask: "What is the component name?" and wait for user's text response
4344
- Store the `--ignore-resolved` flag as a boolean for use in step 3
4445

46+
**Examples:**
47+
```bash
48+
/cve.find llm-d # all llm-d CVEs
49+
/cve.find llm-d autoscaler # only autoscaler CVEs
50+
/cve.find llm-d autoscaler --ignore-resolved
51+
/cve.find "AI Evaluations" trustyai-ragas
52+
```
53+
4554
2. **Check JIRA API Token (REQUIRED - User Setup)**
4655
- **This is the ONLY thing the user must configure manually before proceeding**
4756

@@ -120,6 +129,28 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md
120129
# Build JQL query
121130
JQL="component = \"${COMPONENT_NAME}\" AND summary ~ \"CVE*\" AND labels = SecurityTracking"
122131
132+
# Append subcomponent filter if provided
133+
if [ -n "$SUBCOMPONENT" ] && [ -n "$MAPPING_FILE" ] && [ -f "$MAPPING_FILE" ]; then
134+
# Reverse lookup: find ALL containers whose primary repo has matching subcomponent
135+
PSCOMPONENTS=$(jq -r --arg comp "$COMPONENT_NAME" --arg sub "$SUBCOMPONENT" '
136+
.components[$comp] as $c |
137+
$c.container_to_repo_mapping | to_entries[] |
138+
select($c.repositories[.value].subcomponent == $sub) |
139+
"pscomponent:" + .key
140+
' "$MAPPING_FILE")
141+
142+
if [ -n "$PSCOMPONENTS" ]; then
143+
# Build OR clause for all matching containers
144+
LABEL_FILTERS=$(echo "$PSCOMPONENTS" | \
145+
awk '{print "labels = \"" $0 "\""}' | \
146+
paste -sd ' OR ' -)
147+
JQL="${JQL} AND (${LABEL_FILTERS})"
148+
echo "Filtering by subcomponent '${SUBCOMPONENT}': ${PSCOMPONENTS}"
149+
else
150+
echo "⚠️ Subcomponent '${SUBCOMPONENT}' not found in mapping for '${COMPONENT_NAME}' — running without subcomponent filter"
151+
fi
152+
fi
153+
123154
# Append resolved filter if --ignore-resolved flag was provided
124155
if [ "$IGNORE_RESOLVED" = "true" ]; then
125156
JQL="${JQL} AND status not in (\"Resolved\")"

workflows/cve-fixer/.claude/commands/cve.fix.md

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Summary:
4040
- Fetch the issue details from Jira API
4141
- Extract CVE ID from the issue summary
4242
- Extract component name from the issue
43+
- Extract container name from the issue summary (see below)
4344
- Proceed with this single CVE
4445
- Skip the `/cve.find` output lookup
4546

@@ -49,10 +50,34 @@ Summary:
4950
- Extract CVE IDs with their status from the markdown file
5051
- Filter for CVEs where `Status: Open` (unfixed vulnerabilities)
5152
- Extract component name from the find output (e.g., "AI Core Dashboard")
53+
- Extract container name from each issue summary (see below)
5254
- Collect ALL open CVEs (no filtering)
5355
- Proceed with all open CVEs found
5456

55-
**Result**: A list of CVEs to fix with their associated Jira issues and components
57+
**Extracting container and package from Jira summary (both options)**
58+
59+
Jira summaries follow the pattern:
60+
```
61+
CVE-YYYY-XXXXX <container-name>: <package>: <description>
62+
```
63+
Example:
64+
```
65+
CVE-2025-66418 rhoai/odh-llm-d-routing-sidecar-rhel9: urllib3: Unbounded decompression
66+
```
67+
68+
Parse each summary to extract:
69+
- **Container name**: the `rhoai/odh-*-rhel9` token (or similar) between the CVE ID and the first colon
70+
- **Package name**: the token after the first colon (e.g., `urllib3`, `grpc-go`, `aiohttp`)
71+
72+
```bash
73+
SUMMARY="CVE-2025-66418 rhoai/odh-llm-d-routing-sidecar-rhel9: urllib3: Unbounded decompression"
74+
CONTAINER=$(echo "$SUMMARY" | grep -oP '(?<=CVE-[0-9]+-[0-9]+ )[\w/.-]+(?=:)')
75+
PACKAGE=$(echo "$SUMMARY" | grep -oP '(?<=: )[\w.@/_-]+(?=:)')
76+
```
77+
78+
Store `CONTAINER` and `PACKAGE` per CVE for use in Steps 3 and 5.
79+
80+
**Result**: A list of CVEs to fix with their associated Jira issues, components, containers, and package names
5681

5782
2. **Load Component-Repository Mapping**
5883
- Use `component-repository-mappings.json` from workspace root
@@ -92,23 +117,42 @@ Summary:
92117
- Proceed with mapped repository as documented below
93118

94119
3. **Identify Target Repositories**
95-
- Get list of ALL repositories from the mapping for the component
96-
- **IMPORTANT**: A single component may map to MULTIPLE repositories (e.g., an upstream repo and one or more downstream repos)
97-
- Each repository entry may have a `repo_type` field indicating `"upstream"` or `"downstream"`
98-
- For each repository, gather:
99-
- Repository name (e.g., "opendatahub-io/odh-dashboard")
100-
- Default branch (e.g., "main")
101-
- Active release branches (e.g., ["v2.29.0-fixes", "v2.28.0-fixes", "rhoai-3.0"])
102-
- Primary target branch for CVE fixes (from `cve_fix_workflow.primary_target`)
103-
- Backport targets from cve_fix_workflow
104-
- Repository type (monorepo vs single package)
105-
- Repo type: upstream or downstream (from `repo_type` field, defaults to upstream if absent)
106-
- Create initial list of ALL candidate repositories for the fix
107-
- **Multi-repo strategy**: When a component has both upstream and downstream repos:
108-
- Fix upstream first, then apply the same fix to downstream repos
109-
- Each repo gets its own clone, branch, PR, and verification cycle
110-
- The fix in downstream repos may be a cherry-pick or re-application of the upstream fix
111-
- Steps 4 through 11 are repeated for EACH repository in the list
120+
121+
**3.1: Use container to scope repos (preferred)**
122+
123+
If a `CONTAINER` was extracted in Step 1:
124+
- Look up `CONTAINER` in `container_to_repo_mapping` for the component
125+
- **If container not found in mapping**:
126+
- Log a warning: "⚠️ Container [CONTAINER] not in mapping — may be a new container not yet registered. Processing all component repos."
127+
- Fall back to processing all repos in the component (scan in Step 5 filters irrelevant ones)
128+
- **If container found**: gives the **primary repo** (e.g., `opendatahub-io/workload-variant-autoscaler`)
129+
- Check if the primary repo has a `subcomponent` field in the `repositories` section
130+
- **If `subcomponent` is defined**: collect all repos in the component with the same `subcomponent` value — this is the chain (upstream + midstream + downstream)
131+
- **If `subcomponent` is not defined**: process ALL repositories in the component (safe fallback — the CVE scan in Step 5 will filter out repos where the CVE doesn't exist)
132+
- **This ensures only the repos relevant to that specific container get PRs** — not repos belonging to other subcomponents
133+
134+
Example: `rhoai/odh-workload-variant-autoscaler-controller-rhel9` → primary repo `opendatahub-io/workload-variant-autoscaler``subcomponent: autoscaler` → only process `llm-d/llm-d-workload-variant-autoscaler`, `opendatahub-io/workload-variant-autoscaler`, `red-hat-data-services/workload-variant-autoscaler`.
135+
136+
**3.2: Fallback — use all repos**
137+
138+
If no `CONTAINER` was extracted (summary doesn't match expected pattern):
139+
- Process ALL repositories listed under the component
140+
- The CVE scan in Step 5 acts as the safety net — it will skip repos where the CVE doesn't exist
141+
- Log a warning: "⚠️ Could not extract container from summary — processing all component repos"
142+
143+
**3.3: For each target repo, gather:**
144+
- Repository name (e.g., "opendatahub-io/odh-dashboard")
145+
- Default branch (e.g., "main")
146+
- Active release branches (e.g., ["v2.29.0-fixes", "v2.28.0-fixes", "rhoai-3.0"])
147+
- Primary target branch for CVE fixes (from `cve_fix_workflow.primary_target`)
148+
- Backport targets from `cve_fix_workflow`
149+
- Repository type (monorepo vs single package)
150+
- Repo type: upstream or downstream (from `repo_type` field, defaults to upstream if absent)
151+
152+
**Multi-repo strategy**: When a container chain has upstream, midstream, and downstream repos:
153+
- Fix upstream first, then apply the same fix to midstream and downstream
154+
- Each repo gets its own clone, branch, PR, and verification cycle
155+
- Steps 4 through 11 are repeated for EACH repository in the list
112156

113157
4. **Clone or Use Existing Repository**
114158
- Always use `/tmp` for repository operations with unique dirs per repo
@@ -234,18 +278,47 @@ Summary:
234278
**5.2: Analyze Scan Results**
235279
236280
- Check if the target CVE appears in the scan results
237-
- **If CVE has already been fixed (not present in scan results)**:
238-
- **DO NOT create a PR** — the vulnerability is already resolved
281+
- **If CVE found in scan** → proceed with fix (confirmed vulnerable)
282+
- **If CVE NOT found in scan**do NOT skip immediately. Instead run Step 5.2.1 below.
283+
284+
**5.2.1: Package version check (when scan does not find CVE)**
285+
286+
Container-level CVEs may not be detected by source-level scanners because the vulnerable
287+
package may be installed via RPM, a transitive dependency, or a base image layer rather
288+
than declared directly in the manifest. If the scan returns no result, check the package
289+
version directly:
290+
291+
```bash
292+
# Use PACKAGE extracted from Jira summary in Step 1 (e.g., "urllib3", "grpc-go")
293+
294+
# Python — check requirements files
295+
grep -ri "${PACKAGE}" requirements*.txt setup.py pyproject.toml 2>/dev/null
296+
297+
# Go — check go.mod
298+
grep -i "${PACKAGE}" go.mod 2>/dev/null
299+
300+
# Node — check package.json
301+
grep -i "${PACKAGE}" package.json 2>/dev/null
302+
```
303+
304+
**Interpret results:**
305+
- **Package found at a version** → compare against CVE affected version range
306+
- If version is in affected range → proceed with fix
307+
- If version is already patched → mark as already fixed (see below)
308+
- **Package not found in any manifest** → it may be transitive or RPM-installed
309+
- **Do NOT blindly add a direct dependency** — this can cause version conflicts or unnecessary bloat
310+
- Instead, document the situation and create PR with guidance:
311+
- **Go**: transitive deps require a `replace` directive in go.mod — add it only if intentional
312+
- **Python**: adding to requirements.txt may conflict with what pip resolves transitively; prefer updating the parent package that pulls it in
313+
- **Node**: use npm `overrides` to force a safe version without adding a direct dep
314+
- Include note in PR: "⚠️ Package not found directly in manifests — may be a transitive or RPM-installed dependency. Manual review required to confirm the right fix approach."
315+
- **Both scan AND version check find nothing** → mark as already fixed:
316+
- **DO NOT create a PR**
239317
- **Print to stdout**: "✅ CVE-YYYY-XXXXX is already fixed in [repository] ([branch]). No action needed."
240-
- **Document in artifacts**: Create a brief note in `artifacts/cve-fixer/fixes/already-fixed-CVE-YYYY-XXXXX.md` with:
241-
- CVE ID
242-
- Repository and branch checked
243-
- Scan results showing CVE is not present
244-
- Timestamp of verification
245-
- Note that Jira ticket may need manual closure
246-
- **Move to next CVE**: Skip all remaining steps for this CVE and proceed to the next one
247-
- **Note**: The Jira ticket may still be open — this is an issue management task, not a code fix task
248-
- Only proceed with remaining steps for CVEs that are confirmed as current vulnerabilities in the scan
318+
- **Document in artifacts**: `artifacts/cve-fixer/fixes/already-fixed-CVE-YYYY-XXXXX.md`
319+
- **Note**: Jira ticket may need manual closure
320+
321+
- Only skip the CVE entirely when BOTH the scan AND the direct package check find no evidence of the vulnerability
249322
250323
**5.3: Check for Existing Open PRs**
251324

0 commit comments

Comments
 (0)