--- name: jira-cve-extraction description: Use when `/compliance:analyze-cve` is invoked with `--jira=` or `--jql=` and needs the CVE ID, image name, branch, and enriched ticket context from a Jira issue. --- # Jira CVE Extraction Fetches a Jira ticket and extracts three things: 1. **CVE ID** — passed to Phase 1 (`cve-intelligence-gathering`) 2. **Image name** — passed to Phase 0.7 via the `image-repo-mapping` skill to resolve which repo to clone 3. **Enriched context** — CVSS, CWE, priority, target versions, workarounds — embedded in the Phase 3 report and used to seed the CVE profile > **Key insight:** Security tracking tickets (e.g. `OCPBUGS` CVE trackers) follow a consistent summary format: `CVE-YYYY-NNNNN : []`. Parsing the summary is the most reliable single extraction path and should always be tried first — it typically yields the CVE ID, image name, and branch in one step. ## When to Use This Skill Use this skill when the user invokes `/compliance:analyze-cve` with `--jira=PROJ-NNN` or `--jql="..."`. --- ## Prerequisites ### Preferred: Atlassian MCP Use the Atlassian Rovo MCP tools bundled with the `jira` plugin (or an equivalent Atlassian MCP server configured for this Claude Code instance): - `getJiraIssue` — fetch a single ticket - `searchJiraIssuesUsingJql` — fetch a batch of tickets (Phase 0.3 / idempotency lookups) - `editJiraIssue` — update labels (idempotency marker) ### Fallback: jira-cli If the MCP server is unavailable: `jira issue get ` and `jira issue edit --label ...` (from [go-jira](https://github.com/go-jira/jira)). Requires `~/.jira.d/config.yml` configured for the target Jira instance. > **Credential rule:** Never print, echo, or log any token, password, or key value — not in shell commands, not in model responses, not in debug output. Reference credentials only via environment variable names (e.g. `$JIRA_API_TOKEN`). --- ## Implementation Steps ### Step 1: Validate Ticket Format ``` PROJECT-NNNNN e.g. OCPBUGS-12345, CNTRLPLANE-678 ``` Pattern: `^[A-Z]+-[0-9]+$` - IF invalid → Return error: "Invalid Jira ticket format. Expected PROJECT-NNNNN." - IF valid → Continue ### Step 2: Fetch the Ticket ```python issue = getJiraIssue(issue_key="PROJ-12345") ``` **Fallback (jira-cli):** ```bash jira issue get PROJ-12345 ``` **Error Handling:** - 404 / not found → "Ticket not found. Verify the key and your access." - Auth failure → IF `AUTO_APPROVE=no`, prompt user to authenticate and retry. IF `AUTO_APPROVE=yes`, exit with error — there is no credential to fix automatically. - Network down → IF `AUTO_APPROVE=no`, ask the user to supply the CVE ID manually and skip the enrichment. IF `AUTO_APPROVE=yes`, exit with error (never gated — cannot fabricate ticket data). --- ### Step 2.5: Idempotency Check — Already Processed? Inspect the ticket's `labels` list from the response above. Check whether **`ai-cve-analyzed`** is present (case-sensitive exact match). This check always runs here regardless of entry point (`--jira=` direct or `--jql=` batch mode) — it is the authoritative guard against re-processing. ```python labels = issue["fields"]["labels"] # list of strings if "ai-cve-analyzed" in labels: # Already processed — exit immediately ``` **IF label is present → Stop immediately and output:** ``` ⚠️ Skipping analysis — this ticket has already been processed by /compliance:analyze-cve. Ticket: Label: ai-cve-analyzed To force a re-analysis, remove the label from the ticket and re-run. ``` Return `status: skipped` and exit. Do not proceed with analysis. **IF label is absent → Continue to Step 3.** --- ### Step 3: Extract CVE ID and Image Name from Summary The ticket summary follows this common format: ``` CVE-YYYY-NNNNN : [] ``` Example: ``` CVE-2024-45338 openshift4/ose-operator-sdk-rhel9: some-lib: vulnerability description [openshift-4.17] ``` Parse with: ``` ^(CVE-\d{4}-\d{4,})\s+([\w/:\-\.@]+)\s*:.*\[([\w\.\-]+)\] group 1 = CVE ID group 2 = image name group 3 = branch/version ``` This is the **primary and most reliable extraction path**. If this succeeds, groups 1 and 2 are immediately available — no further searching needed for CVE ID or image name. - IF summary matches → set `CVE_ID`, `IMAGE_NAME`, `BRANCH` → skip to Step 5 - IF summary does not match → continue to Step 4 --- ### Step 4: Fallback Extraction (when summary doesn't match) Try in order until both `CVE_ID` and `IMAGE_NAME` are found: **CVE ID fallbacks:** 1. A dedicated `CVE ID` custom field, if the project has one — always accurate when present 2. Labels — look for a label matching `CVE-\d{4}-\d{4,}` exactly 3. Description body — scan for `CVE-\d{4}-\d{4,}` pattern **Image name fallbacks:** 1. `pscomponent:` label — parse `pscomponent:` from the labels list; strip the `pscomponent:` prefix 2. `Downstream Component Name` custom field, if the project has one — dedicated field mapping directly to the affected image 3. Description body — scan for known image name prefixes (`openshift4/`, `cert-manager/`, `external-secrets-operator/`, `zero-trust-workload-identity-manager/`, `redhat-user-workloads/`) **Multiple CVE IDs found:** List all found. IF `AUTO_APPROVE=no` → ask the user which to analyze (or analyze all with confirmation). IF `AUTO_APPROVE=yes` → **always exit with error** listing the candidates and asking the caller to re-run with a direct `` argument (or a ticket/JQL that resolves to a single CVE). This case is never gated by `AUTO_APPROVE` — guessing which CVE to analyze is a correctness risk. **Decision Point:** - IF no CVE ID found anywhere → IF `AUTO_APPROVE=no`, ask the user to supply it manually; if declined → Exit. IF `AUTO_APPROVE=yes`, there is no one to ask → Exit immediately with error (never gated by `AUTO_APPROVE`). - IF no image name found → leave `IMAGE_NAME` blank; Phase 0.7 will prompt the user for `--repo=` (or hard-fail if `AUTO_APPROVE=yes`, per its own rules) — this is never guessed. --- ### Step 5: Extract Additional Context Fields Read the following fields from the ticket response. Field names vary by Jira instance/project — look them up by display name if the custom field ID is unknown (e.g. via issue-type field metadata), rather than hardcoding an ID that may not match this instance. | Field | Typical location | Notes | |---|---|---| | Status | `fields.status.name` | | | Priority | `fields.priority.name` | Blocker/Critical → urgency escalation | | Assignee | `fields.assignee.displayName` | | | Components | `fields.components[].name` | | | Labels | `fields.labels[]` | Full label list — needed intact for Step 4.5 of `report-to-jira` | | Affects versions | `fields.versions[].name` | | | Fix versions | `fields.fixVersions[].name` | | | Target version | project-specific custom field (e.g. "Target Version") | | | CVSS Score | custom field named "CVSS Score" | Format often `7.5 CVSS:3.1/AV:N/...` — extract score and vector separately | | CWE ID | custom field named "CWE ID" | e.g. `CWE-409` | | Embargo Status | custom field named "Embargo Status" | `True`/`False` — **security-critical, see Step 5.5** | | Downstream Component Name | custom field named "Downstream Component Name", if the project has one | Redundant image name source — cross-check against the summary/label extraction | | Release Note Text | custom field named "Release Note Text" | May already describe the fix | | Description | `fields.description` | Scan for workaround/mitigation keywords | **Scan description for workarounds:** look for sections or sentences containing "workaround", "mitigation", "disable", "restrict" — extract the first ~300 chars of any such passage. **Linked issues:** ```python issue["fields"]["issuelinks"] # each has inwardIssue/outwardIssue + type.name ``` --- ### Step 5.5: Embargo Check — MUST run before returning Read the `Embargo Status` custom field from the ticket (if the project defines one). - IF value is `True` (case-insensitive) → **immediately stop all processing** and return: ``` ❌ Embargoed CVE — cannot proceed. This ticket is marked as embargoed. Embargoed CVEs must not be analysed, disclosed, or shared outside authorised channels. Exit. ``` Do NOT output any CVE details, CVSS scores, image names, or other ticket data. - IF value is `False`, empty, or the field does not exist on this project → Continue to Step 6. --- ### Branch Resolution The `BRANCH` value extracted in Step 3/4 (e.g. `openshift-4.17`, `ztwim-1.0`) uses a **different naming convention** from actual git branches. Resolve it before Phase 0.7 clones anything — do not pass the raw Jira value straight to `git clone -b`. **Pattern A components** (direct repo — Operator SDK, Ansible Operator, must-gather, Secrets Store CSI): | Jira `BRANCH` value | `git_branch` to use | |---|---| | `openshift-X.Y` | `release-X.Y` (e.g. `openshift-4.17` → `release-4.17`) | | `openshift-X.Y.z` | `release-X.Y.z` | | Anything else (e.g. `ztwim-1.0`, `main`) | Use verbatim — Pattern B components resolve their own release-repo branch inside `image-repo-mapping` | Set `git_branch` to the resolved value and `git_branch_source` to `jira_summary`. Phase 0.7 uses `git_branch` directly for the `-b` flag when cloning a Pattern A repo. For a **Pattern B** component (cert-manager, ESO, ZTWIM), pass the raw `BRANCH` value through unchanged — `image-repo-mapping`'s own branch table (e.g. `cert-manager-X-Y` → `release-X.Y` in the release repo) is what actually resolves it, and applying this Pattern A table first would corrupt it. If `BRANCH` was not extracted (no Jira ticket, or the ticket didn't have a parseable version/branch token): leave `git_branch` unset — Phase 0.7 clones the repository's default branch and notes this in the report. --- ### Step 6: Compile and Return ```json { "skill": "jira-cve-extraction", "status": "success", "cve_id": "CVE-YYYY-NNNNN", "image_name": "openshift4/ose-operator-sdk-rhel9", "branch": "openshift-4.17", "jira_context": { "ticket_key": "PROJ-NNNNN", "ticket_url": "https:///browse/PROJ-NNNNN", "summary": "CVE-YYYY-NNNNN openshift4/ose-operator-sdk-rhel9: : [openshift-4.17]", "status": "New", "priority": "Major", "assignee": "", "components": [""], "labels": ["CVE-YYYY-NNNNN", "SecurityTracking", "pscomponent:openshift4/ose-operator-sdk-rhel9"], "affects_versions": ["4.17"], "fix_versions": [], "target_versions": [], "cvss_score": "7.5", "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "cwe_id": "CWE-NNN", "embargo_status": "False", "downstream_component_name": "openshift4/ose-operator-sdk-rhel9", "internal_notes": "", "release_note_text": "", "linked_issues": [] }, "analysis_hints": { "urgency_override": null, "workaround_present": false, "cve_extraction_source": "summary", "image_extraction_source": "summary", "git_branch": "release-4.17", "git_branch_source": "jira_summary" } } ``` `cve_extraction_source` values: `summary`, `custom_field`, `label`, `description`, `user_provided` `image_extraction_source` values: `summary`, `pscomponent_label`, `downstream_component_field`, `description`, `user_provided` --- ## Error Handling | Situation | Action | |---|---| | Ticket not found (404) | Exit with error: "Ticket not found or access denied" | | Auth failure | Prompt to authenticate and retry (or exit if `AUTO_APPROVE=yes`) | | No CVE ID in ticket | `AUTO_APPROVE=no`: ask user to supply manually. `AUTO_APPROVE=yes`: exit with error (never gated). | | No image name in ticket | Leave blank; Phase 0.7 will prompt for `--repo=` (or hard-fail if `AUTO_APPROVE=yes`) | | Multiple CVEs | List all. `AUTO_APPROVE=no`: ask user which to analyze. `AUTO_APPROVE=yes`: exit with error (never gated). | | Embargo `True` | **Stop immediately.** Return error: "This ticket is under embargo. Embargoed CVEs must not be analysed or disclosed outside authorised channels. Exiting." | | Label `ai-cve-analyzed` present | **Stop immediately.** Return `status: skipped` — ticket already processed. | --- ## Integration with Parent Command Called from **Phase 0.5** of the [analyze-cve](../analyze-cve/SKILL.md) skill, only when `--jira=` or `--jql=` was provided. **Output is used as:** - `cve_id` → Phase 1 (`cve-intelligence-gathering`) - `image_name` → Phase 0.7 via the `image-repo-mapping` skill to resolve the clone URL - `analysis_hints.git_branch` → Phase 0.7 Step 3 — the `-b` flag for `git clone` (Pattern A) or the release-branch lookup (Pattern B) - `jira_context` → Phase 1 (merged into vulnerability profile) + Phase 3 report "Jira Context" section - `jira_context.cvss_score` + `cvss_vector` → seeds Phase 1 before NVD lookup - `analysis_hints.urgency_override` → can escalate the final risk level - `analysis_hints.workaround_present` → noted in Phase 4 remediation plan - `jira_context.ticket_key` → becomes `SOURCE_TICKET` for `report-to-jira` (Phase 4) and `create-fix-pr` (Phase 6)