From d238e1a989df8f468c7b9d9f1825140e2cdcdd1b Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 11:31:21 -0400 Subject: [PATCH 01/15] fix: MCP-first Jira auth + diagnose Ambient env var accessibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues fixed: 1. MCP not checked proactively: workflow jumped straight to curl/env var auth and failed even when a Jira MCP server was available. Now Step 2 explicitly checks for mcp__jira* / mcp__atlassian* tools first and uses MCP if found — no credentials needed from the user. 2. Ambient custom env vars not accessible to bash: env vars configured in Ambient session settings may not be automatically exported to bash subprocesses. Added a diagnostic step that checks accessibility before attempting the curl auth call, and gives a clear message with the specific export commands if the vars aren't reaching bash. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.find.md | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.find.md b/workflows/cve-fixer/.claude/commands/cve.find.md index 688284b..6e5a459 100644 --- a/workflows/cve-fixer/.claude/commands/cve.find.md +++ b/workflows/cve-fixer/.claude/commands/cve.find.md @@ -54,13 +54,45 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md 2. **Verify Jira Access** - Secrets may be injected by the Ambient session, a secrets manager, or an MCP server — do NOT rely solely on bash env var checks. Instead, attempt a lightweight test API call and let the response determine whether credentials are available. + **ALWAYS check for a Jira MCP server first** before attempting any curl/env var approach. + + **2.1: Check for Jira MCP server (do this first, every time)** + + Look at the available tools in the current session. If any tool matching `mcp__jira*`, + `mcp__atlassian*`, or any Jira-related MCP tool is present: + - Use the MCP tool directly for all Jira queries in Step 3 + - Skip the curl/auth setup entirely + - Print: "✅ Using Jira MCP server — no credentials required" + + **Do NOT assume MCP is unavailable just because the user has not mentioned it.** + Always proactively check the available tool list before falling back to curl. + + **2.2: Fallback — curl with credentials (only if no MCP found)** + + If no Jira MCP server is available, first check whether the credentials are accessible + to the bash shell — Ambient custom env vars are sometimes available to Claude but not + automatically exported to bash subprocesses: + + ```bash + # Diagnose accessibility before attempting auth + TOKEN_SET=$([ -n "${JIRA_API_TOKEN}" ] && echo "yes" || echo "no") + EMAIL_SET=$([ -n "${JIRA_EMAIL}" ] && echo "yes" || echo "no") + echo "JIRA_API_TOKEN accessible to bash: $TOKEN_SET" + echo "JIRA_EMAIL accessible to bash: $EMAIL_SET" + ``` + + - If either is **"no"** → the Ambient custom env vars are not being passed to bash + subprocesses. Ask the user to export them explicitly in the session: + ```bash + export JIRA_API_TOKEN="your-token-here" + export JIRA_EMAIL="your-email@redhat.com" + ``` + - If both are **"yes"** → proceed with the auth test call: ```bash JIRA_BASE_URL="https://redhat.atlassian.net" AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) - # Retry once on network failure (curl exit code 000 = timeout/no response) for ATTEMPT in 1 2; do TEST_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X GET \ --connect-timeout 10 --max-time 15 \ @@ -74,29 +106,22 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md ``` - **HTTP 200** → credentials valid, proceed - - **HTTP 401** → credentials missing or invalid. Note: `/rest/api/3/myself` returns 401 for all authentication failures — there is no separate 403 for this endpoint. Only now inform the user: - - Check if `JIRA_API_TOKEN` and `JIRA_EMAIL` are configured as Ambient session secrets - - If not, generate a token at https://id.atlassian.com/manage-profile/security/api-tokens and export: - - ```bash - export JIRA_API_TOKEN="your-token-here" - export JIRA_EMAIL="your-email@redhat.com" - ``` - - **HTTP 000 after retry** → persistent network issue — inform user and stop - - **Do NOT pre-check env vars with `[ -z "$JIRA_API_TOKEN" ]` and stop.** The variables may be available to the API call even if not visible to the shell check (e.g. Ambient secrets injection). + - **HTTP 401** → token is invalid or expired. Generate a new token at + https://id.atlassian.com/manage-profile/security/api-tokens and export it + - **HTTP 000 after retry** → network issue — inform user and stop 3. **Query Jira for CVE Issues** - a. Set up variables (AUTH already set from Step 2): + a. Set up variables: ```bash COMPONENT_NAME="[from step 1]" JIRA_BASE_URL="https://redhat.atlassian.net" - # AUTH already constructed in Step 2 — reuse it + # If using MCP (Step 2.1): pass JQL directly to MCP tool — no AUTH needed + # If using curl (Step 2.2): AUTH already constructed in Step 2 — reuse it ``` - b. Construct JQL query and execute API call: + b. Construct JQL query and execute via MCP or curl: ```bash # Normalize component name with case-insensitive lookup against mapping file From 16eaf6cad7b59159f4c44a6dd04ae3952afb0fdb Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 11:36:31 -0400 Subject: [PATCH 02/15] feat: add --automerge flag to cve.fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When passed, enables GitHub automerge on each PR after creation so it merges automatically once all required checks pass. Off by default — user must explicitly opt in. Usage: /cve.fix --automerge /cve.fix RHOAIENG-4973 --automerge Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index 2226be9..eb4864b 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -33,6 +33,11 @@ Summary: 1. **Load CVEs from Find Output or User-Specified Jira Issue** + **Supported flags:** + - `--automerge` — After creating each PR, enable GitHub's automerge so the PR merges automatically once all required checks pass. Off by default — the user must explicitly opt in. + + Parse this flag before processing Jira issues. Store as `AUTOMERGE=true/false`. + **Option A: User specifies one or more Jira issues** - If user provides one or more Jira issue IDs (e.g., `/cve.fix RHOAIENG-4973` or `/cve.fix RHOAIENG-4973 RHOAIENG-5821`): - Process each issue independently -- extract CVE ID and component from each @@ -1074,10 +1079,16 @@ This PR fixes **CVE-YYYY-XXXXX** by upgrading from X.X.X to Y.Y.Y. EOF ) - gh pr create \ + PR_URL=$(gh pr create \ --base \ --title "Security: Fix CVE-YYYY-XXXXX ()" \ - --body "$PR_BODY" + --body "$PR_BODY") + + # Enable automerge if --automerge flag was passed + if [ "$AUTOMERGE" = "true" ]; then + gh pr merge --auto --squash "$PR_URL" + echo "✅ Automerge enabled on $PR_URL — will merge when checks pass" + fi ``` - Capture the PR URL from the command output - Save PR URL to fix implementation report @@ -1222,6 +1233,12 @@ Fix multiple specific Jira issues: /cve.fix RHOAIENG-4973 RHOAIENG-5821 ``` +Fix and enable automerge on all created PRs (merges automatically when checks pass): +``` +/cve.fix --automerge +/cve.fix RHOAIENG-4973 --automerge +``` + Fix with custom message: ``` /cve.fix Fix open CVEs found in latest scan From effc57ba9cb88020a246600fd3903ad8fbb1fc27 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 11:41:28 -0400 Subject: [PATCH 03/15] feat: add post-fix CVE re-scan to verify fix actually works (Step 10.5) After applying the fix but before creating the PR, re-run the same scanner (govulncheck/pip-audit/npm audit) to confirm the CVE is actually gone. If CVE is still present after fix: - Do NOT create a PR (fix was insufficient) - Add Jira comment explaining the fix failed and why (transitive dep conflict, wrong package, etc.) - Save to artifacts/cve-fixer/fixes/fix-failed-CVE-*.md This catches cases like: - Transitive dependency still pulling in the old vulnerable version - Go toolchain directive not propagating correctly - Wrong package targeted in the fix Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index eb4864b..be5dd77 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -987,10 +987,59 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid **Full test log**: `artifacts/cve-fixer/fixes/test-results/test-run-20260218-143022.log` ``` +10.5. **Post-Fix CVE Verification** + + After applying the fix and running tests, re-run the same scanner used in Step 5 to + confirm the CVE is actually gone. This catches cases where the fix was insufficient + (e.g. transitive dependency still pulling in the old version, toolchain directive not + propagating, or the wrong package was updated). + + ```bash + echo "Re-scanning to verify CVE-${CVE_ID} is resolved..." + # Run same scan as Step 5 with same GOTOOLCHAIN/language tooling + POST_SCAN_OUTPUT=$(GOTOOLCHAIN="go${TARGET_GO_VERSION}" govulncheck -show verbose ./... 2>&1) + # For Python: pip-audit -r requirements.txt 2>/dev/null + # For Node: npm audit --json 2>/dev/null + + if echo "$POST_SCAN_OUTPUT" | grep -q "$CVE_ID"; then + CVE_STILL_PRESENT=true + else + CVE_STILL_PRESENT=false + fi + ``` + + **If CVE is gone** (`CVE_STILL_PRESENT=false`) → proceed to PR creation ✅ + + **If CVE is still present** (`CVE_STILL_PRESENT=true`) → **do NOT create a PR**: + - Print: "❌ CVE-YYYY-XXXXX still detected after fix attempt in [repo] ([branch]). Fix was insufficient." + - Add a Jira comment: + + ```bash + COMMENT_TEXT="Automated fix attempted but CVE still detected after applying changes. + +Fix attempted: ${FIX_DESCRIPTION} +Post-fix scan: CVE still present in ${REPO_FULL} on branch ${TARGET_BRANCH} +Scan date: $(date -u +%Y-%m-%dT%H:%M:%SZ) + +Manual investigation required — the automated fix did not resolve this CVE. +Possible causes: transitive dependency conflict, incorrect package targeted, or +the fix requires additional changes beyond a version bump." + + COMMENT_JSON=$(jq -n --arg body "$COMMENT_TEXT" '{"body": $body}') + AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + curl -s -X POST \ + -H "Authorization: Basic ${AUTH}" \ + -H "Content-Type: application/json" \ + -d "$COMMENT_JSON" \ + "${JIRA_BASE_URL}/rest/api/3/issue/${JIRA_KEY}/comment" + ``` + - Document in `artifacts/cve-fixer/fixes/fix-failed-CVE-YYYY-XXXXX.md` + - Skip to next CVE/branch — do not create PR for this one + 11. **Create Pull Requests** - **CRITICAL**: You MUST actually CREATE the PRs using `gh pr create` command - **CRITICAL**: Create a SEPARATE PR for EACH CVE (NOT combined) - - **CRITICAL**: Only create PRs for CVEs that were ACTUALLY FIXED (not for CVEs that were already fixed in Step 5) + - **CRITICAL**: Only create PRs for CVEs that passed post-fix verification in Step 10.5 (not for CVEs that were already fixed in Step 5 or where the fix was insufficient) - For each CVE fix that was successfully committed and pushed: - Generate PR title: `Security: Fix CVE-YYYY-XXXXX ()` - **Extract Jira issue IDs for this CVE:** @@ -1166,6 +1215,9 @@ EOF - PR URL for the created pull request - **Already Fixed Report**: `artifacts/cve-fixer/fixes/already-fixed-CVE-YYYY-XXXXX.md` (if CVE confirmed not present via both scan and package check) + +- **Fix Failed Report**: `artifacts/cve-fixer/fixes/fix-failed-CVE-YYYY-XXXXX.md` (if post-fix re-scan still detects the CVE) + - Fix attempted, post-fix scan output, Jira comment added, manual review required - CVE ID, repository, and scan evidence - **VEX Justified Report**: `artifacts/cve-fixer/fixes/vex-justified-CVE-YYYY-XXXXX.md` (if auto-detected VEX justification added to Jira) From 9f94c2fa25cfa68b4229bab5e4a691df3efd5289 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 13:01:45 -0400 Subject: [PATCH 04/15] fix: check for Dependabot/Renovate PRs when detecting existing fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously only searched for PRs containing the CVE ID. Bots like Dependabot and Renovate open PRs with just the package name and version bump (e.g. "Bump urllib3 from 1.26.5 to 2.2.3") without mentioning the CVE ID, causing the workflow to create duplicate fix PRs. Now runs 3 searches before creating a PR: 1. CVE ID search — catches our own and manually created PRs 2. Package name + bot author filter — catches Dependabot/Renovate PRs 3. Package name broadly — catches any human PR for the same package Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index be5dd77..486647c 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -570,19 +570,40 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid **How to check:** + Two searches are needed — bots like Dependabot and Renovate open PRs that fix the same + vulnerability without mentioning the CVE ID, only the package name and version bump. + ```bash REPO_FULL="opendatahub-io/models-as-a-service" # org/repo from mapping CVE_ID="CVE-YYYY-XXXXX" + PACKAGE="urllib3" # extracted from Jira summary in Step 1 TARGET_BRANCH="main" # from mapping or user input - - # Search open PRs for this specific CVE ID targeting this branch - EXISTING_PR=$(gh pr list --repo "$REPO_FULL" --state open --base "$TARGET_BRANCH" --search "$CVE_ID" --json number,title,url,headRefName,baseRefName --jq '.[0]' 2>/dev/null) + EXISTING_PR="" + + # Search 1: by CVE ID (catches our own PRs and manually created ones) + EXISTING_PR=$(gh pr list --repo "$REPO_FULL" --state open --base "$TARGET_BRANCH" \ + --search "$CVE_ID" --json number,title,url --jq '.[0]' 2>/dev/null) + + # Search 2: by package name (catches Dependabot/Renovate PRs that don't mention CVE ID) + if [ -z "$EXISTING_PR" ] || [ "$EXISTING_PR" = "null" ]; then + EXISTING_PR=$(gh pr list --repo "$REPO_FULL" --state open --base "$TARGET_BRANCH" \ + --search "$PACKAGE" --json number,title,url,author \ + --jq '[.[] | select(.author.login | test("dependabot|renovate|renovate-bot"; "i"))] | .[0]' \ + 2>/dev/null) + + # If no bot PR, still check for any open PR bumping this package + # (a human may have already opened a fix PR without mentioning the CVE) + if [ -z "$EXISTING_PR" ] || [ "$EXISTING_PR" = "null" ]; then + EXISTING_PR=$(gh pr list --repo "$REPO_FULL" --state open --base "$TARGET_BRANCH" \ + --search "$PACKAGE" --json number,title,url --jq '.[0]' 2>/dev/null) + fi + fi if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number') PR_TITLE=$(echo "$EXISTING_PR" | jq -r '.title') PR_URL=$(echo "$EXISTING_PR" | jq -r '.url') - echo "⏭️ Skipping $CVE_ID — existing open PR found:" + echo "⏭️ Skipping $CVE_ID — existing open PR found (may be Dependabot/Renovate):" echo " PR #${PR_NUMBER}: ${PR_TITLE}" echo " URL: ${PR_URL}" @@ -618,11 +639,13 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid - Proceed with the fix (Step 6 onwards) **Search strategy:** - - Search by exact CVE ID first (e.g., `CVE-2025-61726`) — this is the most reliable match + - **Search 1 — by CVE ID**: catches our own PRs and any manually created ones + - **Search 2 — by package name filtered to bots**: catches Dependabot/Renovate PRs that bump the vulnerable package without mentioning the CVE ID (e.g. "Bump urllib3 from 1.26.5 to 2.2.3") + - **Search 3 — by package name broadly**: catches any human-opened PR for the same package, regardless of author - The `gh pr list --search` command searches PR titles and bodies - - A single PR may address multiple CVEs (e.g., "fix: cve-2025-61726 and cve-2025-68121") — if ANY of the target CVEs appear in an existing PR, consider all CVEs in that PR as already handled - - **IMPORTANT: Only skip for OPEN PRs.** Closed or merged PRs should be ignored — if a previous PR was closed without merging (e.g., it was incorrect or superseded), it is valid to create a new fix PR for the same CVE. The `--state open` flag in the `gh pr list` command ensures only open PRs are checked. - - **Stale remote branches**: A previous automation run may have left a remote branch with the same name (e.g., `fix/cve-YYYY-XXXXX-attempt-1`) from a closed PR. If push fails due to a conflicting remote branch, increment the attempt number (e.g., `attempt-2`) or delete the stale remote branch with `git push origin --delete ` before pushing. + - A single PR may address multiple CVEs — if any of the target CVEs or the affected package appears in an existing open PR, skip creating a duplicate + - **IMPORTANT: Only skip for OPEN PRs.** Closed or merged PRs should be ignored — it is valid to create a new fix PR for the same CVE if a previous PR was closed without merging. The `--state open` flag ensures only open PRs are checked. + - **Stale remote branches**: If push fails due to a conflicting remote branch from a previous run, increment the attempt number (e.g., `attempt-2`) or delete the stale branch with `git push origin --delete `. **Example output when PR exists:** ``` From 5e46a412e3e79c0300ac275114397f9963b5b0f1 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 13:12:17 -0400 Subject: [PATCH 05/15] fix: make Step 10.5 CRITICAL and use binary scan for Go CVE verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-fix verification was being skipped because it wasn't marked as mandatory. Also, re-scanning source code (govulncheck ./...) after a fix can give false negatives — the go.mod may be updated but the CVE could still be present in the compiled binary if a transitive dep overrides it. Changes: - Mark Step 10.5 as CRITICAL/MANDATORY with explicit "Do NOT skip" warning - For Go: build the binary first, then scan with govulncheck --mode binary (gold standard — verifies the actual compiled output, not just source) - Fall back to source scan if binary build fails - For Python: re-run pip-audit on modified requirements - For Node: regenerate lockfile then npm audit The workflow must not proceed to PR creation without completing this step. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index 486647c..3a0120c 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -1012,18 +1012,51 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid 10.5. **Post-Fix CVE Verification** - After applying the fix and running tests, re-run the same scanner used in Step 5 to - confirm the CVE is actually gone. This catches cases where the fix was insufficient - (e.g. transitive dependency still pulling in the old version, toolchain directive not - propagating, or the wrong package was updated). + **CRITICAL — This step is MANDATORY. Do NOT skip it. Do NOT proceed to PR creation without completing this step.** + + Source-level scanning (Step 5) checks the dependency manifests before the fix. + This step verifies the fix actually worked by scanning the **compiled output** after + the fix is applied. A source scan can give false negatives (e.g., go.mod updated but + toolchain not recompiled, or a transitive dep overrides the fixed version at build time). + + **For Go projects (preferred — binary scan):** + + ```bash + echo "Building and scanning binary to verify fix for CVE-${CVE_ID}..." + cd "$REPO_DIR" + + # Build the binary with the fixed go.mod + go build -o /tmp/fixed-binary-${REPO_NAME} ./... 2>&1 + + if [ $? -eq 0 ]; then + # Scan the compiled binary — this is the gold standard + POST_SCAN_OUTPUT=$(GOTOOLCHAIN="go${TARGET_GO_VERSION}" \ + govulncheck -mode binary /tmp/fixed-binary-${REPO_NAME} 2>&1) + rm -f /tmp/fixed-binary-${REPO_NAME} + else + echo "⚠️ Binary build failed — falling back to source scan" + POST_SCAN_OUTPUT=$(GOTOOLCHAIN="go${TARGET_GO_VERSION}" govulncheck -show verbose ./... 2>&1) + fi + ``` + + **For Python projects:** + + ```bash + # Re-run pip-audit on the modified requirements file + POST_SCAN_OUTPUT=$(pip-audit -r requirements.txt 2>&1) + ``` + + **For Node.js projects:** ```bash - echo "Re-scanning to verify CVE-${CVE_ID} is resolved..." - # Run same scan as Step 5 with same GOTOOLCHAIN/language tooling - POST_SCAN_OUTPUT=$(GOTOOLCHAIN="go${TARGET_GO_VERSION}" govulncheck -show verbose ./... 2>&1) - # For Python: pip-audit -r requirements.txt 2>/dev/null - # For Node: npm audit --json 2>/dev/null + # Re-run npm audit after package.json changes + npm install --package-lock-only 2>/dev/null # regenerate lockfile + POST_SCAN_OUTPUT=$(npm audit --json 2>&1) + ``` + **Check result:** + + ```bash if echo "$POST_SCAN_OUTPUT" | grep -q "$CVE_ID"; then CVE_STILL_PRESENT=true else From 4cdb7b537cbd30f2123f6edf29ec7d2ede0ead44 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 13:16:44 -0400 Subject: [PATCH 06/15] fix: enforce all steps are mandatory, mark Step 4.5 as CRITICAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add mandatory execution rule at top: every step must be executed in order, none may be skipped. If a step produces no output, log it and continue — do not silently skip. - Mark Step 4.5 (load .cve-fix/ guidance) as CRITICAL/MANDATORY with explicit bash loop that checks every cloned repo and always logs the result whether guidance is found or not. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index 3a0120c..0749a68 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -3,6 +3,12 @@ ## Purpose Implement secure remediations for prioritized CVEs through dependency updates, patches, code changes, or compensating controls. This command executes the actual fixes that eliminate vulnerabilities. +## Mandatory Execution Rule + +**EVERY step in the Process section below MUST be executed in order. NO step may be skipped, abbreviated, or assumed complete without actually running it.** + +If a step produces no output (e.g., no `.cve-fix/` folder found, no tests discovered), log that result explicitly and continue. Do not silently skip any step. + ## Execution Style **Be concise. Brief status + final summary only.** @@ -263,11 +269,34 @@ Summary: ``` 4.5. **Load Global Fix Guidance from `.cve-fix/` Folder** - - Runs ONCE after all repos are cloned, BEFORE any fixes. Builds a global knowledge base from `.cve-fix/` folders across all cloned repos. - - Check every cloned repo for `.cve-fix/`, read ALL files (e.g., `examples.md`, `config.json`, `dependencies.md`, `pitfalls.md`), and merge into a single context. - - Extract: dependency co-upgrades, lock file requirements, version upgrade patterns, branch-specific instructions, known working fix versions, testing requirements, common pitfalls. - - Apply this guidance to ALL subsequent steps (5-11). Guidance from any repo applies globally; when conflicts exist, prefer the repo-specific instruction. - - If no `.cve-fix/` folders exist, proceed with default strategy. + + **CRITICAL — MANDATORY. Do NOT skip. Must run before any fix is applied.** + + Runs ONCE after all repos are cloned, BEFORE any fixes. Check every cloned repo for + a `.cve-fix/` directory and read ALL files inside it. This guidance contains + repo-specific fix patterns, known working versions, and pitfalls that directly affect + whether the fix will succeed. + + ```bash + for REPO_DIR in "${ALL_CLONED_REPOS[@]}"; do + CVE_FIX_DIR="${REPO_DIR}/.cve-fix" + if [ -d "$CVE_FIX_DIR" ]; then + echo "📖 Reading fix guidance from ${REPO_DIR}/.cve-fix/" + for FILE in "$CVE_FIX_DIR"/*; do + echo " Reading: $FILE" + cat "$FILE" + done + else + echo "ℹ️ No .cve-fix/ folder in ${REPO_DIR} — using default strategy" + fi + done + ``` + + - Extract: dependency co-upgrades, lock file requirements, version upgrade patterns, + branch-specific instructions, known working fix versions, testing requirements, pitfalls + - Apply this guidance to ALL subsequent steps (5-11) + - When conflicts exist between repos, prefer the repo-specific instruction + - **Always log the result** — either "guidance loaded from X" or "no .cve-fix/ found in any repo" --- > **Steps 5-11 repeat for EACH repository identified in Step 3.** From 5d0e62173a4be2bb71e73f74699935797cad7bdf Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Thu, 9 Apr 2026 13:21:46 -0400 Subject: [PATCH 07/15] fix: sync fork branches with upstream before creating fix branch When a fork already exists from a previous run, its branches may be weeks out of date. The fix branch would then be based on stale code, potentially missing upstream changes or causing merge conflicts in the PR. Now uses `gh repo sync --source upstream --branch TARGET_BRANCH` to bring the fork's target branch up to date with upstream before creating the fix branch. Logs a warning if sync fails but continues. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index 0749a68..c5d202f 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -213,15 +213,28 @@ Summary: If `PUSH_ACCESS` is `false` or the push fails: ```bash - # Create a fork under the authenticated user's account - gh repo fork "$REPO_FULL" --clone=false - FORK_USER=$(gh api user --jq '.login') FORK_REPO="${FORK_USER}/${REPO_NAME}" - # Add fork as a remote + # Create fork if it doesn't exist yet + gh repo fork "$REPO_FULL" --clone=false 2>/dev/null || true + + # Sync fork branches with upstream — REQUIRED to ensure fix branches + # are based on current upstream code, not a stale fork. + # This syncs all branches in the fork with the upstream repo. + echo "Syncing fork ${FORK_REPO} with upstream ${REPO_FULL}..." + gh repo sync "${FORK_REPO}" --source "$REPO_FULL" --branch "$TARGET_BRANCH" + + if [ $? -ne 0 ]; then + echo "⚠️ Fork sync failed — fix branch may be based on stale code. Proceeding with caution." + else + echo "✅ Fork branch ${TARGET_BRANCH} synced with upstream" + fi + + # Add fork as a remote and fetch the synced branch cd "$REPO_DIR" - git remote add fork "https://github.com/${FORK_REPO}.git" + git remote add fork "https://github.com/${FORK_REPO}.git" 2>/dev/null || true + git fetch fork "$TARGET_BRANCH" # Push fix branch to fork, PR targets the original repo git push fork "$FIX_BRANCH" From ca57c02e00cd4a2c5ab12cda1a50b31d1cbf49b8 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 07:22:15 -0400 Subject: [PATCH 08/15] feat: add /onboard command for self-service component onboarding New command guides teams through adding their component and repos to component-repository-mappings.json and opens a PR automatically. Features: - Interactive: collects component name, repos, types, branches one step at a time - Validates Jira component name against the API before proceeding - Auto-discovers default branch and active release branches from GitHub - Generates the correct JSON schema entry and shows it for confirmation - Forks ambient-code/workflows if user lacks write access, syncs fork with upstream main before branching - Creates PR with description including component details and next steps Usage: /onboard (fully interactive) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/onboard.md | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 workflows/cve-fixer/.claude/commands/onboard.md diff --git a/workflows/cve-fixer/.claude/commands/onboard.md b/workflows/cve-fixer/.claude/commands/onboard.md new file mode 100644 index 0000000..0089051 --- /dev/null +++ b/workflows/cve-fixer/.claude/commands/onboard.md @@ -0,0 +1,239 @@ +# /onboard - Onboard a New Component to the CVE Fixer Workflow + +## Purpose + +Guides a team through adding their component and repositories to +`component-repository-mappings.json` and opens a PR to the CVE fixer +workflow repository. The Jira component name must exactly match what is +used in Jira — this is validated against the Jira API during onboarding. + +## Process + +1. **Collect Component Information** + + Ask the user for the following, one question at a time: + + a. **Jira component name** — must match exactly what appears in Jira + (case-sensitive). Example: `"AI Evaluations"`, `"llm-d"`, `"AutoML"` + + b. **Repos** — for each repo the user wants to add, collect: + - GitHub URL (e.g. `https://github.com/org/repo`) + - Repo type: `upstream`, `midstream`, or `downstream` + - Subcomponent name (optional — only needed if the component has multiple + distinct container chains, e.g. `"inference-scheduler"`, `"autoscaler"`) + + c. **Container image names** (optional) — the `rhoai/odh-*-rhel9` container + images that map to each repo. These can be left empty and added later. + + Collect all repos before proceeding. Ask: "Do you have more repos to add? + (yes/no)" after each repo until the user is done. + +2. **Validate Jira Component Name** + + Confirm the component exists in Jira and has CVE issues: + + ```bash + JIRA_BASE_URL="https://redhat.atlassian.net" + AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('component = \"${COMPONENT_NAME}\" AND labels = SecurityTracking'))") + + RESULT=$(curl -s -X GET --connect-timeout 10 --max-time 15 \ + -H "Authorization: Basic ${AUTH}" \ + -H "Content-Type: application/json" \ + "${JIRA_BASE_URL}/rest/api/3/search/jql?jql=${ENCODED}&maxResults=1&fields=key,summary") + + ISSUE_COUNT=$(echo "$RESULT" | jq '.issues | length') + ``` + + - If component returns results → confirmed, proceed + - If 0 results → warn user: "No CVE issues found for component + '${COMPONENT_NAME}' with SecurityTracking label. The component name + must match exactly what Jira uses. Do you want to proceed anyway? (yes/no)" + - If Jira credentials not available → skip validation and proceed with a note + +3. **Auto-discover Branch Information** + + For each GitHub repo provided, fetch branch info automatically: + + ```bash + for REPO_URL in "${REPOS[@]}"; do + REPO_FULL=$(echo "$REPO_URL" | sed 's|https://github.com/||') + + # Verify repo exists + gh api repos/${REPO_FULL} --jq '.full_name' 2>/dev/null || { + echo "⚠️ Repo not found: ${REPO_URL}" + continue + } + + # Get default branch + DEFAULT_BRANCH=$(gh api repos/${REPO_FULL} --jq '.default_branch') + + # Get active release branches (rhoai-*, release/*, odh-*, stable) + ACTIVE_BRANCHES=$(gh api repos/${REPO_FULL}/branches --paginate \ + -q '.[].name' 2>/dev/null | \ + grep -E '^(rhoai-[0-9]|release/|odh-[0-9]|stable)' | \ + sort -V | tail -5) # keep 5 most recent + + echo " ${REPO_FULL}: default=${DEFAULT_BRANCH}, active=[${ACTIVE_BRANCHES}]" + done + ``` + + Show the discovered info to the user and ask for confirmation or corrections. + +4. **Build Mapping Entry** + + Construct the JSON entry following the existing schema: + + ```json + { + "": { + "container_to_repo_mapping": { + "": "" + }, + "repositories": { + "": { + "github_url": "https://github.com/", + "default_branch": "", + "active_release_branches": ["", ""], + "branch_strategy": "TBD — to be updated by component team", + "repo_type": "upstream|midstream|downstream", + "subcomponent": "", + "cve_fix_workflow": { + "primary_target": "", + "backport_targets": "" + } + } + } + } + } + ``` + + - Omit `subcomponent` if the user didn't provide one + - Omit `container_to_repo_mapping` entries if no containers were provided + - Show the generated JSON to the user and ask: "Does this look correct? (yes/no/edit)" + +5. **Set Up Workflows Repository** + + The mapping file lives in `ambient-code/workflows`. Check write access and + fork if needed: + + ```bash + WORKFLOWS_REPO="ambient-code/workflows" + FORK_USER=$(gh api user --jq '.login' 2>/dev/null) + + # Check write access + PUSH_ACCESS=$(gh api repos/${WORKFLOWS_REPO} --jq '.permissions.push' 2>/dev/null) + + if [ "$PUSH_ACCESS" = "true" ]; then + # Clone directly + git clone "https://github.com/${WORKFLOWS_REPO}.git" /tmp/workflows-onboard + REMOTE="origin" + PR_HEAD_PREFIX="" + else + # Fork the repo + echo "No write access to ${WORKFLOWS_REPO} — forking..." + gh repo fork "$WORKFLOWS_REPO" --clone=false 2>/dev/null || true + + # Sync fork with upstream main + gh repo sync "${FORK_USER}/workflows" --source "$WORKFLOWS_REPO" --branch main + git clone "https://github.com/${FORK_USER}/workflows.git" /tmp/workflows-onboard + cd /tmp/workflows-onboard + git remote add upstream "https://github.com/${WORKFLOWS_REPO}.git" + REMOTE="origin" + PR_HEAD_PREFIX="${FORK_USER}:" + fi + ``` + +6. **Apply the Mapping Change** + + ```bash + cd /tmp/workflows-onboard + git checkout -b "onboard/${COMPONENT_NAME_SLUG}" + + MAPPING_FILE="workflows/cve-fixer/component-repository-mappings.json" + + # Insert new component using Python (preserves JSON formatting) + python3 - < /dev/null && echo "✅ JSON valid" + + git add "$MAPPING_FILE" + git commit -m "feat: onboard ${COMPONENT_NAME} to CVE fixer workflow + + Add component-to-repository mapping for ${COMPONENT_NAME}: + $(echo "${REPOS[@]}" | tr ' ' '\n' | sed 's/^/- /') + + Co-Authored-By: Claude Sonnet 4.6 (1M context) " + git push "$REMOTE" "onboard/${COMPONENT_NAME_SLUG}" + ``` + +7. **Create Pull Request** + + ```bash + gh pr create \ + --repo "$WORKFLOWS_REPO" \ + --base main \ + --head "${PR_HEAD_PREFIX}onboard/${COMPONENT_NAME_SLUG}" \ + --title "feat: onboard ${COMPONENT_NAME} to CVE fixer workflow" \ + --body "$(cat <" + ``` + +8. **Cleanup** + + ```bash + rm -rf /tmp/workflows-onboard + ``` + +## Usage Examples + +```bash +/onboard +``` + +The command is fully interactive — it will guide you through each question. + +## Notes + +- The Jira component name is case-sensitive and must match exactly +- Branch info is auto-discovered from GitHub — review and correct if needed +- Container image mappings can be added later by re-running `/onboard` or opening a PR directly +- If you don't have write access to `ambient-code/workflows`, the command will automatically fork the repo and open a PR from your fork From c5a39287627966499b80da793159361298dc8323 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 07:44:01 -0400 Subject: [PATCH 09/15] fix: don't stop on bash env var check, always attempt Jira API call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes for the auth detection failure in automated runs: 1. MCP check: don't filter by specific tool name patterns (mcp__jira*). Jira MCP may appear under any name. Search ALL available tools and their descriptions. Also try mcp__session__refresh_credentials first to activate workspace-configured Jira credentials before checking. 2. Curl fallback: the bash env var diagnostic (TOKEN_SET=yes/no) was causing the workflow to stop when vars showed as "no". This is wrong — Ambient secrets can be injected at the curl level even when not visible to shell checks. Now the diagnostic is informational only and the API call is ALWAYS attempted. Only a 401 response (not a "no" from the bash check) stops the workflow. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.find.md | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.find.md b/workflows/cve-fixer/.claude/commands/cve.find.md index 6e5a459..9345096 100644 --- a/workflows/cve-fixer/.claude/commands/cve.find.md +++ b/workflows/cve-fixer/.claude/commands/cve.find.md @@ -58,41 +58,39 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md **2.1: Check for Jira MCP server (do this first, every time)** - Look at the available tools in the current session. If any tool matching `mcp__jira*`, - `mcp__atlassian*`, or any Jira-related MCP tool is present: - - Use the MCP tool directly for all Jira queries in Step 3 + Search ALL available tools — do not limit to specific name patterns. Jira MCP tools + may appear under any naming convention (e.g. `mcp__jira*`, `mcp__atlassian*`, + `mcp__acp__*`, or other names). Look for any tool whose name or description suggests + Jira search, issue lookup, or JQL query capability. + + If any such tool is found: + - Use it directly for all Jira queries in Step 3 - Skip the curl/auth setup entirely - - Print: "✅ Using Jira MCP server — no credentials required" + - Print: "✅ Using Jira MCP tool [tool-name] — no credentials required" - **Do NOT assume MCP is unavailable just because the user has not mentioned it.** - Always proactively check the available tool list before falling back to curl. + If `mcp__session__refresh_credentials` is available, call it first — this can activate + Jira credentials that are configured in the workspace but not yet loaded in the session. + After refreshing, re-check for Jira MCP tools. - **2.2: Fallback — curl with credentials (only if no MCP found)** + **Do NOT assume MCP is unavailable just because the tool name doesn't match a specific + pattern. Check all tools and their descriptions.** - If no Jira MCP server is available, first check whether the credentials are accessible - to the bash shell — Ambient custom env vars are sometimes available to Claude but not - automatically exported to bash subprocesses: + **2.2: Fallback — curl with credentials (always attempt, even if bash says vars are unset)** - ```bash - # Diagnose accessibility before attempting auth - TOKEN_SET=$([ -n "${JIRA_API_TOKEN}" ] && echo "yes" || echo "no") - EMAIL_SET=$([ -n "${JIRA_EMAIL}" ] && echo "yes" || echo "no") - echo "JIRA_API_TOKEN accessible to bash: $TOKEN_SET" - echo "JIRA_EMAIL accessible to bash: $EMAIL_SET" - ``` - - - If either is **"no"** → the Ambient custom env vars are not being passed to bash - subprocesses. Ask the user to export them explicitly in the session: - ```bash - export JIRA_API_TOKEN="your-token-here" - export JIRA_EMAIL="your-email@redhat.com" - ``` - - If both are **"yes"** → proceed with the auth test call: + If no Jira MCP tool is available, attempt the curl auth call regardless of whether the + bash env var check shows the vars as set or not. Ambient secrets can be injected at the + curl level even when not visible to shell variable checks — the only reliable test is the + actual API call response. ```bash JIRA_BASE_URL="https://redhat.atlassian.net" AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + # Diagnostic only — do NOT stop if these are "no" + echo "JIRA_API_TOKEN in bash env: $([ -n "${JIRA_API_TOKEN}" ] && echo yes || echo no)" + echo "JIRA_EMAIL in bash env: $([ -n "${JIRA_EMAIL}" ] && echo yes || echo no)" + echo "Attempting Jira API call regardless..." + for ATTEMPT in 1 2; do TEST_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X GET \ --connect-timeout 10 --max-time 15 \ @@ -106,8 +104,8 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md ``` - **HTTP 200** → credentials valid, proceed - - **HTTP 401** → token is invalid or expired. Generate a new token at - https://id.atlassian.com/manage-profile/security/api-tokens and export it + - **HTTP 401** → credentials truly not available or expired. Only now stop and inform user: + configure `JIRA_API_TOKEN` and `JIRA_EMAIL` as Ambient workspace secrets or export them - **HTTP 000 after retry** → network issue — inform user and stop 3. **Query Jira for CVE Issues** From 6158357485f28662f00a509b68c5dff8c086f81b Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 07:50:53 -0400 Subject: [PATCH 10/15] fix: use targeted ToolSearch to find mcp-atlassian Jira tool Generic ToolSearch queries like "jira search issue JQL" match unrelated tools instead of mcp__mcp-atlassian__jira_search. Now uses: - select:mcp__mcp-atlassian__jira_search (exact) - +atlassian jira (targeted) Also tells the workflow to read the list directly before searching, since the tool name is visible there. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.find.md | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.find.md b/workflows/cve-fixer/.claude/commands/cve.find.md index 9345096..92734b5 100644 --- a/workflows/cve-fixer/.claude/commands/cve.find.md +++ b/workflows/cve-fixer/.claude/commands/cve.find.md @@ -58,22 +58,30 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md **2.1: Check for Jira MCP server (do this first, every time)** - Search ALL available tools — do not limit to specific name patterns. Jira MCP tools - may appear under any naming convention (e.g. `mcp__jira*`, `mcp__atlassian*`, - `mcp__acp__*`, or other names). Look for any tool whose name or description suggests - Jira search, issue lookup, or JQL query capability. + Use targeted `ToolSearch` queries to find the Jira MCP tool. Generic keyword searches + (e.g. "jira search issue JQL") may return the wrong tools. Use these specific queries: - If any such tool is found: + ``` + ToolSearch: "select:mcp__mcp-atlassian__jira_search" + ToolSearch: "+atlassian jira" + ToolSearch: "select:mcp__jira__search_issues" + ``` + + Also check the `` list at the top of the conversation — + the Jira tool will be listed there by its full name (e.g. `mcp__mcp-atlassian__jira_search`). + Read that list directly before searching. + + If any Atlassian/Jira MCP tool is found: - Use it directly for all Jira queries in Step 3 - Skip the curl/auth setup entirely - Print: "✅ Using Jira MCP tool [tool-name] — no credentials required" If `mcp__session__refresh_credentials` is available, call it first — this can activate Jira credentials that are configured in the workspace but not yet loaded in the session. - After refreshing, re-check for Jira MCP tools. + After refreshing, re-check the available tools list. - **Do NOT assume MCP is unavailable just because the tool name doesn't match a specific - pattern. Check all tools and their descriptions.** + **Do NOT run a generic ToolSearch like "jira search" — this will match unrelated tools. + Use the select: syntax or "+atlassian" to target the correct tool.** **2.2: Fallback — curl with credentials (always attempt, even if bash says vars are unset)** From 4d5269bf93afc1ecda407f4321493ceadf9e4e79 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 07:54:11 -0400 Subject: [PATCH 11/15] fix: read available-deferred-tools list directly, skip keyword ToolSearch Generic ToolSearch returns wrong results. The Jira tool name is visible in at conversation start. Workflow now reads that list directly and fetches with select:mcp__mcp-atlassian__jira_search instead of relying on keyword search to discover it. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.find.md | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.find.md b/workflows/cve-fixer/.claude/commands/cve.find.md index 92734b5..5a360d2 100644 --- a/workflows/cve-fixer/.claude/commands/cve.find.md +++ b/workflows/cve-fixer/.claude/commands/cve.find.md @@ -58,30 +58,29 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md **2.1: Check for Jira MCP server (do this first, every time)** - Use targeted `ToolSearch` queries to find the Jira MCP tool. Generic keyword searches - (e.g. "jira search issue JQL") may return the wrong tools. Use these specific queries: + **Do NOT use ToolSearch with generic keywords — it returns wrong results.** + + Instead, look directly at the `` list that appears at the + top of every conversation. If you see any of these tool names listed there: + - `mcp__mcp-atlassian__jira_search` + - `mcp__mcp-atlassian__jira_get_issue` + - or any tool containing "atlassian" or "jira" in the name + + Then fetch it using the `select:` syntax: ``` - ToolSearch: "select:mcp__mcp-atlassian__jira_search" - ToolSearch: "+atlassian jira" - ToolSearch: "select:mcp__jira__search_issues" + ToolSearch: select:mcp__mcp-atlassian__jira_search ``` - Also check the `` list at the top of the conversation — - the Jira tool will be listed there by its full name (e.g. `mcp__mcp-atlassian__jira_search`). - Read that list directly before searching. - - If any Atlassian/Jira MCP tool is found: - - Use it directly for all Jira queries in Step 3 - - Skip the curl/auth setup entirely - - Print: "✅ Using Jira MCP tool [tool-name] — no credentials required" + Once fetched, use it directly for all Jira queries in Step 3. + Skip the curl/auth setup entirely. + Print: "✅ Using mcp__mcp-atlassian__jira_search for Jira queries" - If `mcp__session__refresh_credentials` is available, call it first — this can activate - Jira credentials that are configured in the workspace but not yet loaded in the session. - After refreshing, re-check the available tools list. + If `mcp__session__refresh_credentials` appears in the deferred tools list, call it + first to activate workspace credentials, then re-check the list. - **Do NOT run a generic ToolSearch like "jira search" — this will match unrelated tools. - Use the select: syntax or "+atlassian" to target the correct tool.** + **The tool name is visible in `` at the start of the + conversation. Read that list — do not rely on ToolSearch keyword queries to discover it.** **2.2: Fallback — curl with credentials (always attempt, even if bash says vars are unset)** From bf9ca4661ffe2ed73d7d253a0e5d44540172d1d7 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 15:19:38 -0400 Subject: [PATCH 12/15] fix: always use select: syntax for Jira MCP, never generic keyword search After refresh_credentials activates Jira, the workflow was still doing a generic ToolSearch ('jira search atlassian') which returns unrelated results, causing a false 'tool not found' conclusion. Now: Step A = refresh_credentials, Step B = immediately try select:mcp__mcp-atlassian__jira_search with no generic search step. Explicitly forbids generic keyword searches for tool discovery. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.find.md | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.find.md b/workflows/cve-fixer/.claude/commands/cve.find.md index 5a360d2..5c0ad31 100644 --- a/workflows/cve-fixer/.claude/commands/cve.find.md +++ b/workflows/cve-fixer/.claude/commands/cve.find.md @@ -56,31 +56,27 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md **ALWAYS check for a Jira MCP server first** before attempting any curl/env var approach. - **2.1: Check for Jira MCP server (do this first, every time)** + **2.1: Check for Jira MCP server — follow these exact steps in order** - **Do NOT use ToolSearch with generic keywords — it returns wrong results.** + **Step A**: If `mcp__session__refresh_credentials` is in the deferred tools list, call it now. + This activates workspace integrations including Jira. - Instead, look directly at the `` list that appears at the - top of every conversation. If you see any of these tool names listed there: - - `mcp__mcp-atlassian__jira_search` - - `mcp__mcp-atlassian__jira_get_issue` - - or any tool containing "atlassian" or "jira" in the name - - Then fetch it using the `select:` syntax: + **Step B**: Immediately after (or if no refresh was needed), attempt to fetch the Jira + tool directly using `select:` syntax — do this regardless of whether you think it exists: ``` ToolSearch: select:mcp__mcp-atlassian__jira_search ``` - Once fetched, use it directly for all Jira queries in Step 3. - Skip the curl/auth setup entirely. - Print: "✅ Using mcp__mcp-atlassian__jira_search for Jira queries" + **Step C**: If Step B returns the tool schema → use it for all Jira queries. Done. + Print: "✅ Using mcp__mcp-atlassian__jira_search" - If `mcp__session__refresh_credentials` appears in the deferred tools list, call it - first to activate workspace credentials, then re-check the list. + **NEVER run a generic keyword ToolSearch like "jira search atlassian" or "jira MCP". + Generic searches return unrelated tools and cause false "not found" conclusions. + The `select:` syntax either returns the exact tool or nothing — use only that.** - **The tool name is visible in `` at the start of the - conversation. Read that list — do not rely on ToolSearch keyword queries to discover it.** + If `select:mcp__mcp-atlassian__jira_search` returns nothing → the tool is not available + in this session. Proceed to Step 2.2 (curl fallback). **2.2: Fallback — curl with credentials (always attempt, even if bash says vars are unset)** From 7a8838491e2232b67e70fb72cf7ab246dce8a441 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 15:21:29 -0400 Subject: [PATCH 13/15] docs: add /onboard to README and startup prompt - README: add /onboard command section in Available Commands - README: update Onboarding Steps to use /onboard instead of contacting maintainers manually - ambient.json startupPrompt: mention /onboard as the first step for new teams before /cve.find and /cve.fix Co-Authored-By: Claude Sonnet 4.6 (1M context) --- workflows/cve-fixer/.ambient/ambient.json | 2 +- workflows/cve-fixer/README.md | 38 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/workflows/cve-fixer/.ambient/ambient.json b/workflows/cve-fixer/.ambient/ambient.json index a3203f4..800d227 100644 --- a/workflows/cve-fixer/.ambient/ambient.json +++ b/workflows/cve-fixer/.ambient/ambient.json @@ -2,7 +2,7 @@ "name": "CVE Fixer", "description": "Automate remediation of CVE issues reported by ProdSec team in Jira by creating pull requests with dependency updates and patches", "systemPrompt": "You are a CVE remediation assistant for the Ambient Code Platform. Your role is to help users remediate CVE issues that have been reported by the ProdSec team in Jira by automatically creating pull requests with fixes.\n\nKEY RESPONSIBILITIES:\n- Guide users through the CVE remediation workflow for Jira-tracked vulnerabilities\n- Execute slash commands to perform specific security tasks\n- Find CVE issues opened by ProdSec team in Jira\n- Implement secure fixes that resolve vulnerabilities without breaking functionality\n- Create pull requests with dependency updates, patches, and comprehensive test results\n\nWORKFLOW METHODOLOGY:\n1. FIND - Find CVEs already reported in Jira for a component\n2. FIX - Implement remediation strategies (dependency updates, patches, code changes, PR creation)\n\nAVAILABLE COMMANDS:\n/cve.find - Find CVEs reported in Jira for a specific component\n/cve.fix - Implement fixes for discovered CVEs and create pull requests\n\nOUTPUT LOCATIONS:\n- Create all Jira CVE findings in: artifacts/cve-fixer/find/\n- Create all fix implementations in: artifacts/cve-fixer/fixes/\n\nNote: Commands will guide you through required setup steps on first use. If the user's component is not in component-repository-mappings.json, direct them to the \"Team Onboarding\" section in README.md.", - "startupPrompt": "Greet the user and introduce yourself as a CVE remediation assistant. Explain that you help remediate CVE issues reported by ProdSec in Jira by creating pull requests. Mention the two commands: /cve.find to discover CVEs and /cve.fix to implement fixes. If this is their first time, point them to README.md Team Onboarding for setup. Suggest starting with /cve.find and ask what they'd like to work on.", + "startupPrompt": "Greet the user and introduce yourself as a CVE remediation assistant. Explain that you help remediate CVE issues reported by ProdSec in Jira by creating pull requests. Mention the three commands: /onboard to add a new component, /cve.find to discover CVEs, and /cve.fix to implement fixes. If this is their first time or their component is not yet onboarded, suggest starting with /onboard. Otherwise suggest /cve.find and ask what they'd like to work on.", "results": { "Jira CVE Issues": "artifacts/cve-fixer/find/**/*.md", "Fix Implementations": "artifacts/cve-fixer/fixes/**/*" diff --git a/workflows/cve-fixer/README.md b/workflows/cve-fixer/README.md index a977b0e..8235e0e 100644 --- a/workflows/cve-fixer/README.md +++ b/workflows/cve-fixer/README.md @@ -99,14 +99,15 @@ Each team member using the workflow needs: ### Onboarding Steps -1. **Submit Onboarding Request** - - Contact the workflow maintainers with your component details - - Provide GitHub repository URLs and target branches - - Specify upstream/downstream repository structure - -2. **Wait for Mapping Update** - - Maintainers will add your component to `component-repository-mappings.json` - - PR will be created and merged +1. **Run `/onboard`** + - Run the `/onboard` command — it guides you through the process interactively + - Provide your Jira component name, GitHub repo URLs, and repo types + - The command validates your component name against Jira, auto-discovers branch info, + and opens a PR to add your component to `component-repository-mappings.json` + - No need to contact maintainers manually — the PR is opened automatically using your credentials + +2. **Wait for PR to Merge** + - A maintainer will review and merge your PR - You'll be notified when ready 3. **Coordinate with ProdSec** @@ -195,6 +196,27 @@ Discover and catalog CVEs that have been reported by ProdSec team in Jira for a - Extracts issue metadata (summary, status, priority, created date) - Groups results by status and priority +### `/onboard` - Onboard a New Component + +Add your team's component and repositories to the CVE fixer workflow. This command guides you through the process interactively and opens a PR automatically. + +**Usage:** +```bash +/onboard # fully interactive — guides you through each step +``` + +**What it does:** +1. Collects your Jira component name, GitHub repos, and optional container image names +2. Validates the Jira component name against the Jira API +3. Auto-discovers branch info (default branch, active release branches) from GitHub +4. Shows you the generated mapping entry for confirmation +5. Forks `ambient-code/workflows` if you don't have write access, syncs the fork +6. Opens a PR with your component added to `component-repository-mappings.json` + +**Run this before using `/cve.find` or `/cve.fix` for a new component.** + +--- + ### `/cve.fix` - Implement CVE Fixes Implement remediations for CVEs discovered in Jira by creating pull requests with fixes. From 1b8f24f031de2d5b67dbdff557d34b073b6e3116 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 15:26:00 -0400 Subject: [PATCH 14/15] fix: address all CodeRabbit comments on PR 104 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cve.fix.md: - Search 3 (broad package name): document false-positive trade-off so workflow uses judgment before skipping on common package names - Post-fix scan: check exit code before grepping output — if scanner failed to run, treat as inconclusive and block PR creation - Automerge: add non-empty PR_URL guard before calling gh pr merge - Jira comment: add MCP-first note (use MCP tool if available) onboard.md: - Jira validation: use MCP-first pattern (select:mcp__mcp-atlassian__) with curl fallback; also fix shell injection in JQL encoding - Python heredoc: pass all variables as argv[] instead of interpolating shell vars into Python code — prevents injection from component names containing quotes, backslashes, or newlines Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../cve-fixer/.claude/commands/cve.fix.md | 30 ++++++++++---- .../cve-fixer/.claude/commands/onboard.md | 41 ++++++++++++++----- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index c5d202f..5e2c398 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -633,8 +633,11 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid --jq '[.[] | select(.author.login | test("dependabot|renovate|renovate-bot"; "i"))] | .[0]' \ 2>/dev/null) - # If no bot PR, still check for any open PR bumping this package - # (a human may have already opened a fix PR without mentioning the CVE) + # Search 3: broad package name search to catch human-opened PRs. + # Note: for common package names (e.g. "requests", "yaml") this may + # produce false positives. If the matched PR title does not appear + # related to the CVE (e.g. it's a feature PR that happens to mention + # the package), do NOT skip — use judgment before skipping. if [ -z "$EXISTING_PR" ] || [ "$EXISTING_PR" = "null" ]; then EXISTING_PR=$(gh pr list --repo "$REPO_FULL" --state open --base "$TARGET_BRANCH" \ --search "$PACKAGE" --json number,title,url --jq '.[0]' 2>/dev/null) @@ -1096,10 +1099,18 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid POST_SCAN_OUTPUT=$(npm audit --json 2>&1) ``` - **Check result:** + **Check result — verify scan ran successfully before trusting output:** ```bash - if echo "$POST_SCAN_OUTPUT" | grep -q "$CVE_ID"; then + SCAN_EXIT_CODE=$? + + # If scanner failed (tool missing, network issue, toolchain error), treat as + # inconclusive — do NOT assume CVE is fixed. Log and skip PR creation. + if [ $SCAN_EXIT_CODE -ne 0 ] && [ -z "$POST_SCAN_OUTPUT" ]; then + echo "⚠️ Post-fix scan failed to run (exit code ${SCAN_EXIT_CODE}). Cannot verify fix." + echo "⚠️ Skipping PR creation for safety — manual verification required." + CVE_STILL_PRESENT=true # treat as still present to block PR creation + elif echo "$POST_SCAN_OUTPUT" | grep -q "$CVE_ID"; then CVE_STILL_PRESENT=true else CVE_STILL_PRESENT=false @@ -1110,7 +1121,10 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid **If CVE is still present** (`CVE_STILL_PRESENT=true`) → **do NOT create a PR**: - Print: "❌ CVE-YYYY-XXXXX still detected after fix attempt in [repo] ([branch]). Fix was insufficient." - - Add a Jira comment: + - Add a Jira comment using MCP if available, otherwise curl: + + If `mcp__mcp-atlassian__jira_search` was used in Step 2, use the corresponding + Jira comment MCP tool. Otherwise fall back to curl: ```bash COMMENT_TEXT="Automated fix attempted but CVE still detected after applying changes. @@ -1231,10 +1245,12 @@ EOF --title "Security: Fix CVE-YYYY-XXXXX ()" \ --body "$PR_BODY") - # Enable automerge if --automerge flag was passed - if [ "$AUTOMERGE" = "true" ]; then + # Enable automerge if --automerge flag was passed and PR was created successfully + if [ "$AUTOMERGE" = "true" ] && [ -n "$PR_URL" ] && [ "$PR_URL" != "null" ]; then gh pr merge --auto --squash "$PR_URL" echo "✅ Automerge enabled on $PR_URL — will merge when checks pass" + elif [ "$AUTOMERGE" = "true" ] && [ -z "$PR_URL" ]; then + echo "⚠️ Automerge skipped — PR URL is empty (PR creation may have failed)" fi ``` - Capture the PR URL from the command output diff --git a/workflows/cve-fixer/.claude/commands/onboard.md b/workflows/cve-fixer/.claude/commands/onboard.md index 0089051..d1f69c9 100644 --- a/workflows/cve-fixer/.claude/commands/onboard.md +++ b/workflows/cve-fixer/.claude/commands/onboard.md @@ -30,12 +30,17 @@ used in Jira — this is validated against the Jira API during onboarding. 2. **Validate Jira Component Name** - Confirm the component exists in Jira and has CVE issues: + Use MCP if available (preferred), otherwise fall back to curl. + Follow the same MCP-first pattern as `cve.find`: + - Try `ToolSearch: select:mcp__mcp-atlassian__jira_search` first + - If found, use it to search: `component = "${COMPONENT_NAME}" AND labels = SecurityTracking` + - If not found, fall back to curl: ```bash JIRA_BASE_URL="https://redhat.atlassian.net" AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) - ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('component = \"${COMPONENT_NAME}\" AND labels = SecurityTracking'))") + JQL="component = \"${COMPONENT_NAME}\" AND labels = SecurityTracking" + ENCODED=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$JQL") RESULT=$(curl -s -X GET --connect-timeout 10 --max-time 15 \ -H "Authorization: Basic ${AUTH}" \ @@ -152,24 +157,38 @@ used in Jira — this is validated against the Jira API during onboarding. MAPPING_FILE="workflows/cve-fixer/component-repository-mappings.json" - # Insert new component using Python (preserves JSON formatting) - python3 - < /tmp/new_component.json - with open("${MAPPING_FILE}") as f: + TODAY=$(date +%Y-%m-%d) + + python3 - "$MAPPING_FILE" "$COMPONENT_NAME" /tmp/new_component.json "$TODAY" <<'PYEOF' + import json, sys + + mapping_file = sys.argv[1] + component_name = sys.argv[2] + new_component_file = sys.argv[3] + today = sys.argv[4] + + with open(mapping_file) as f: data = json.load(f) - # Add new component - data["components"]["${COMPONENT_NAME}"] = ${NEW_COMPONENT_JSON} + with open(new_component_file) as f: + new_component = json.load(f) - # Update metadata - data["metadata"]["last_updated"] = "$(date +%Y-%m-%d)" + data["components"][component_name] = new_component + data["metadata"]["last_updated"] = today - with open("${MAPPING_FILE}", "w") as f: + with open(mapping_file, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") + + print(f"Added component: {component_name}") PYEOF + rm -f /tmp/new_component.json + # Validate JSON python3 -m json.tool "$MAPPING_FILE" > /dev/null && echo "✅ JSON valid" From af906eb0de5ad7f0430f2e713e030dde7a8bd365 Mon Sep 17 00:00:00 2001 From: Vaishnavi-Modi Date: Fri, 10 Apr 2026 15:27:57 -0400 Subject: [PATCH 15/15] fix: apply base64 | tr -d newline fix to all auth lines (matches PR 106) Prevents broken Authorization headers when credentials are long enough to trigger base64 line-wrapping at 76 chars. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- workflows/cve-fixer/.claude/commands/cve.find.md | 2 +- workflows/cve-fixer/.claude/commands/cve.fix.md | 4 ++-- workflows/cve-fixer/.claude/commands/onboard.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workflows/cve-fixer/.claude/commands/cve.find.md b/workflows/cve-fixer/.claude/commands/cve.find.md index 5c0ad31..7dd87d6 100644 --- a/workflows/cve-fixer/.claude/commands/cve.find.md +++ b/workflows/cve-fixer/.claude/commands/cve.find.md @@ -87,7 +87,7 @@ Report: artifacts/cve-fixer/find/cve-issues-20260226-145018.md ```bash JIRA_BASE_URL="https://redhat.atlassian.net" - AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64 | tr -d '\n') # Diagnostic only — do NOT stop if these are "no" echo "JIRA_API_TOKEN in bash env: $([ -n "${JIRA_API_TOKEN}" ] && echo yes || echo no)" diff --git a/workflows/cve-fixer/.claude/commands/cve.fix.md b/workflows/cve-fixer/.claude/commands/cve.fix.md index 5e2c398..e3cd5c7 100644 --- a/workflows/cve-fixer/.claude/commands/cve.fix.md +++ b/workflows/cve-fixer/.claude/commands/cve.fix.md @@ -574,7 +574,7 @@ This issue can be closed as 'Not a Bug / ${VEX_JUSTIFICATION}' if the above evid COMMENT_JSON=$(jq -n --arg body "$COMMENT_TEXT" '{"body": $body}') # Post comment via Jira API - AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64 | tr -d '\n') curl -s -X POST \ -H "Authorization: Basic ${AUTH}" \ -H "Content-Type: application/json" \ @@ -1138,7 +1138,7 @@ Possible causes: transitive dependency conflict, incorrect package targeted, or the fix requires additional changes beyond a version bump." COMMENT_JSON=$(jq -n --arg body "$COMMENT_TEXT" '{"body": $body}') - AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64 | tr -d '\n') curl -s -X POST \ -H "Authorization: Basic ${AUTH}" \ -H "Content-Type: application/json" \ diff --git a/workflows/cve-fixer/.claude/commands/onboard.md b/workflows/cve-fixer/.claude/commands/onboard.md index d1f69c9..c5d8a32 100644 --- a/workflows/cve-fixer/.claude/commands/onboard.md +++ b/workflows/cve-fixer/.claude/commands/onboard.md @@ -38,7 +38,7 @@ used in Jira — this is validated against the Jira API during onboarding. ```bash JIRA_BASE_URL="https://redhat.atlassian.net" - AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64) + AUTH=$(echo -n "${JIRA_EMAIL}:${JIRA_API_TOKEN}" | base64 | tr -d '\n') JQL="component = \"${COMPONENT_NAME}\" AND labels = SecurityTracking" ENCODED=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$JQL")