---
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 <component>: <description> [<version>]`. 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 <TICKET>` and `jira issue edit <TICKET> --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:  <JIRA_KEY>
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 <image-name>: <vulnerability description> [<branch-or-version>]
```

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:<image-name>` 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 `<CVE-ID>` 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://<jira-host>/browse/PROJ-NNNNN",
    "summary": "CVE-YYYY-NNNNN openshift4/ose-operator-sdk-rhel9: <lib>: <description> [openshift-4.17]",
    "status": "New",
    "priority": "Major",
    "assignee": "<assignee-display-name>",
    "components": ["<component>"],
    "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)
