# CVE Analysis Reference

LLMS index: [llms.txt](https://hummingbird-project.io/llms.txt) | Full content: [llms-full.txt](https://hummingbird-project.io/llms-full.txt)

---

Reference and operational documentation for the CVE Analysis tool.
This page covers package map, vendored dependency detection, prerequisites, usage, configuration,
managed labels, output format, logs, library layout, development, and license.
For conceptual documentation (CVE lifecycle, how the analysis pipeline works), see
[CVE Analysis](https://hummingbird-project.io/docs/background/tools/hummingbird-cve-analysis/).

## Package Map

The package map is loaded from the rpms repo's per-package metadata files
(`metadata/<package>.json`). Each file contains an `upstream_repo` field
pointing to the canonical upstream git repository, plus optional fields:

- `upstream_branch` -- upstream branch (for versioned packages sharing a repo)
- `cve_product` -- CVE vendor/product override
- `version_transform` -- version transform rule
- `fix_status` -- package fix policy (`0` = End-Of-Life / will not fix CVEs;
  close as **Won't Do**)

If `fix_status` is `0`, analysis always resolves to **Closed / Won't Do**
(including when the shipped version is outside the CVE affected range or a
product mismatch would otherwise need investigation). No VEX Justification
is set. If `fix_status` is missing or set to `1`, behavior is unchanged from
the default analysis flow.

When an upstream fix is available, Jira backport guidance keeps the
`NEXT STEPS: Backport upstream patches to Hummingbird` marker and reminds
maintainers to keep independent packages' `modification_status` as
`independent` and omit `modification_reason`, mark Fedora-imported packages
`modified` with a `modification_reason`, and leave metadata `release`
unchanged. This guidance does not affect the CVE assessment or fix decision.

The `cve_product` field is an optional human-curated value that specifies
which CVE vendor/product entry maps to this package. It supports these
formats:

- **`Vendor / Product`** (exact match): matches a specific vendor and
  product pair. Example: `"F5 / NGINX Open Source"` for the `nginx`
  package, which excludes NGINX Plus entries that use incompatible
  R-versioning.
- **`Vendor`** (vendor prefix match): matches any product whose vendor
  starts with the given string. Example: `"Go "` for `golang` packages,
  where the vendor varies (`Go standard library`, `Go toolchain`, etc.)
  and the product varies by module (`net/url`, `os`, `crypto/x509`).
- **Multiple selectors**: provide more than one acceptable value either
  as a JSON list in metadata (preferred) or as a semicolon-delimited
  string. Example:
  `["vda-linux / busybox_mirror", "BusyBox / BusyBox"]` or
  `"vda-linux / busybox_mirror; BusyBox / BusyBox"`. The tool treats this
  as "match any selector".

When `cve_product` is set, the tool uses it for exact product matching in
both resolution computation (filtering to the correct product in
multi-product CVEs) and mismatch detection. When empty, the tool falls
back to heuristic name matching.

The rpms repo is cloned automatically at startup (or provided via
`--rpms-repo`). A CSV override can be passed via `--package-map` for
backward compatibility.

## Vendored Dependency Detection

Vendored dependency detection uses live SBOM lookups from the
Hummingbird Pulp repository. When a CVE product mismatch is detected,
the tool fetches the SPDX SBOM for the ticket's package from
`packages.redhat.com/.../metadata/sboms/{package}-main/` (same public
index used by the rpms CVE skill), selects the newest dated
`sha256-….sbom` entry from that listing, and searches that document for
the CVE product(s) only (via purl references), stopping on the first
product hit. Binary evidence then collects all matching copies of that
product. The analysis output always records the SBOM artifact used and
whether the lookup hit, missed, or was unavailable.

The same SBOM-first check also gates **Component not Present** closures
for packages missing from the rpms repo. An SBOM hit alone no longer
blocks that closure: binary confirmation must reach
`absent_in_binary_confirmed` (no bundled Provide match and Syft miss on
source-only SPDX evidence) before Not a Bug / Component not Present is
recommended. A binary hit or unknown result keeps the ticket open for
investigation.

Go subpackages are matched by progressively stripping path components
(e.g., `github.com/jackc/pgx/v5/pgproto3` matches
`github.com/jackc/pgx/v5`). When a match is found and confirmed in
binaries, the confirmed binary/Provide version is preferred for
resolution over the SBOM lockfile version. The SBOM and binary
confirmation are re-run each analysis cycle.

Requires the `rpm` and/or `syft` CLI on `PATH` (both are available in
the analysis container image via `rpm-build` and the Syft install).
Without either tool, binary confirmation returns `unknown`. With only
`rpm`, a bundled Provide can still confirm `present_in_binary`, but
absence is never auto-confirmed without Syft.

The `generate_vendored_map_sbom.py` script in `package_maps/` can still
be used for auditing vendored dependencies across all packages, but is
no longer required at runtime.

## Prerequisites

- Python 3.11 or later
- Jira API token (Bearer or Basic auth)
- Network access to `redhat.atlassian.net`, `github.com`,
  `nvd.nist.gov`, `bodhi.fedoraproject.org`,
  `src.fedoraproject.org`, and `packages.redhat.com`
  (note: `github.com` is only needed when `--cve-repo` is not provided
  and the tool must clone cvelistV5 itself)
- `rpm` and `syft` CLIs on `PATH` (for binary RPM confirmation after
  SBOM hits: bundled Provides via `rpm`, component inventory via Syft)
- GitHub API token (optional, for upstream PR search)
- GitLab API token (optional, for gitlab.com MR search)
- CEE GitLab API token (required with `--resolve`, for advisory repo)

## Usage

```bash
# Basic usage with Jira token from environment
export JIRA_TOKEN=your_token
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com

# Analyze specific tickets
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com HUM-796 HUM-518

# Show only tickets from the last 2 weeks
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com --show-since "2 weeks"

# Analyze with upstream fix detection (report findings without modifying Jira)
export GITHUB_TOKEN=your_github_token
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com

# Resolve tickets and apply labels
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com --resolve

# Include closed tickets and check for stale closures
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com --include-closed

# Include closed tickets but exclude specific ones
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com \
  --include-closed --exclude "HUM-555,HUM-552"

# Skip issues without CVE links; write collector handoff
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com \
  --skip-no-cve --handoff-file /tmp/cve_analysis_handoff.json

# Continuous mode: resolve tickets every 30 minutes
python3 -m hummingbird_cve_analysis.cve_analysis --jira-user user@example.com \
  --resolve --time-between-runs 30
```

Dashboard `/api/cve-report` JSON is produced by `collect_cve_dashboard`
([section 8.4](https://hummingbird-project.io/docs/background/tools/hummingbird-cve-analysis/#84-collect_cve_dashboard)),
not by `cve_analysis` stdout (HUM-5800).

### Inspecting lifecycle timing with `dump_lifecycle`

`scripts/dump_lifecycle` reads lifecycle milestones and package data from the
dashboard HTTP API and prints them for one or more HUM tickets. `CVE_REPORT_TOKEN` is needed.

```bash
# Text output for one ticket against prod
scripts/dump_lifecycle --prod HUM-1234

# JSON output for multiple tickets against preprod
scripts/dump_lifecycle --preprod --json HUM-1234 HUM-5678

# Custom dashboard URL
scripts/dump_lifecycle --dashboard-url https://... HUM-1234
```

Output includes all milestone timestamps (`cve_published`, `osidb_flaw_created`,
`osidb_affect_created`, `cve_embargo_lifted`, `hum_ticket_created`, …), computed
duration legs, and
`rpm_first_published (package)` sourced from
`package_lifecycle`. The `--json` flag emits the output as a JSON array instead
of human-readable text.

### Backfilling OSIDB timestamps with `backfill_osidb_timestamps.py`

`scripts/backfill_osidb_timestamps.py` is a one-time helper that fills
`osidb_flaw_created` and `osidb_affect_created` for tickets already stored
in `cve_ticket_events`. It reads existing rows from `/api/cve-export`,
looks up timestamps from the public OSIDB API, and POSTs new rows to
`/api/cve-import` with `reset=false` (write-once; existing timestamps are
kept). Tickets that already have both OSIDB events are skipped unless
`--force` is set. `CVE_REPORT_TOKEN` is needed.

```bash
# Backfill prod
scripts/backfill_osidb_timestamps.py --prod

# Dry run against preprod
scripts/backfill_osidb_timestamps.py --preprod --dry-run

# Limit to specific tickets
scripts/backfill_osidb_timestamps.py --prod HUM-1234 HUM-5678
```

Ongoing collection is handled by `collect_cve_dashboard`. After a prod
backfill, `copy_prod_to_preprod.sh` copies the new event types with the
rest of `cve_ticket_events` (they are on the dashboard import allowlist).

### Collecting `rpm_first_published` with `collect_rpm_first_published`

`scripts/collect_rpm_first_published` sweeps the Hummingbird Pulp repo for all
packages, finds the earliest SRPM upload timestamp for each, and writes the
results to the `package_lifecycle` table via `POST /api/cve-export` - no direct
DB access.

```bash
# Sweep all packages from the rpms repo and write to prod
scripts/collect_rpm_first_published --prod --rpms-repo /path/to/rpms

# Only process specific packages
scripts/collect_rpm_first_published --prod --rpms-repo /path/to/rpms --package curl wget

# Dry run — log what would be written without posting
scripts/collect_rpm_first_published --prod --rpms-repo /path/to/rpms --dry-run
```

Re-runs are safe: the dashboard upsert only updates `occurred_at` if the
incoming timestamp is earlier than the stored one.

### Analyzing CVE Response Delays with `analyze_rpm_first_published`

`scripts/analyze_rpm_first_published` inspects the relation between CVE publication,
package onboarding (`rpm_first_published`), and HUM ticket creation:

```bash
# Analyze all tickets on prod
scripts/analyze_rpm_first_published --prod --token $CVE_REPORT_TOKEN

# Filter to new packages onboarded after CVE publication
scripts/analyze_rpm_first_published --prod --filter new-pkg

# Filter to pre-existing packages with at least 30 days actionable delay
scripts/analyze_rpm_first_published --prod --filter pre-existing --min-days 30 --sort actionable
```

CLI options:

- `--filter {all,new-pkg,pre-existing}`: filter by onboarding timing relative to CVE.
- `--min-days N`: minimum delay threshold in days (default: 0).
- `--sort {cve_to_hum,actionable,cve_to_rpm}`: sort column (default: `actionable`).
- `--limit N`: max rows to display (default: 50).

## Configuration

| Option                      | Environment Variable        | Description                                                                               |
| --------------------------- | --------------------------- | ----------------------------------------------------------------------------------------- |
| `--jira-token`              | `JIRA_TOKEN`                | Jira API or Bearer token                                                                  |
| `--jira-url`                | `JIRA_URL`                  | Jira base URL (default: `https://redhat.atlassian.net`)                                   |
| `--jira-user`               |                             | Username for Basic auth                                                                   |
| `--output`, `-o`            |                             | Stdout format: `human` only (`json` / `json-pretty` removed in HUM-5800)                  |
| `--show-since`              |                             | Filter by creation period (e.g. `2 weeks`, `3 hours`)                                     |
| `--resolve`                 |                             | Transition Jira tickets and apply labels                                                  |
| `--skip-assignee-check`     |                             | Skip assignee validation (for testing)                                                    |
| `--include-closed`          |                             | Include Closed tickets in analysis; warn on stale closures                                |
| `--exclude`                 |                             | Comma-separated ticket keys to skip (e.g. `HUM-555,HUM-552`)                              |
| `--skip-no-cve`             |                             | Omit issues with no CVE ID (CVE ID field or Summary); no postpone comment/label           |
| `--max-results`             |                             | Max number of issues to fetch (default: 2000)                                             |
| `--github-token`            | `GITHUB_TOKEN`              | GitHub API token for upstream PR search                                                   |
| `--gitlab-token`            | `GITLAB_TOKEN`              | GitLab API token for gitlab.com MR search                                                 |
| `--nvd-cache-dir`           | `NVD_CACHE_DIR`             | Directory for caching NVD data feed files; avoids re-downloading unchanged feeds          |
| `--cee-gitlab-token`        | `CEE_GITLAB_TOKEN`          | CEE GitLab token for advisory repo operations                                             |
| `--advisories-project`      | `ADVISORIES_REPO`           | Advisories repo URL (default: `releng/advisories`)                                        |
| `--advisories-fork`         | `ADVISORIES_FORK`           | Bot's fork URL for advisory MR creation                                                   |
| `--slack-webhook-url`       | `SLACK_WEBHOOK_URL`         | Slack webhook URL for failure notifications; empty or unset disables Slack                |
| `--no-merge-request`        |                             | Skip advisory MR creation during `--resolve`                                              |
| `--keep-advisory-repo`      |                             | Do not delete the cloned advisory repo after the run (useful for debugging)               |
| `--test-advisory`           |                             | Create advisory MR then immediately close it (for testing)                                |
| `--package-map`             |                             | Path to CSV override; by default uses rpms repo metadata                                  |
| `--rpms-repo`               |                             | Path to RPMs git repo for fixed build detection                                           |
| `--cve-repo`                |                             | Path to local cvelistV5 git repo for CVE record lookups                                   |
| `--time-between-runs`       |                             | Re-run every N minutes; 0 = single run (default)                                          |
| `--feature-flag-project-id` |                             | GitLab project ID for feature flag lookup (default: `73447720`, rpms project)             |
| `--feature-flag-name`       |                             | Feature flag name to check each cycle (default: `cve_analysis_enabled`; empty = disabled) |
| `--board-id`                |                             | Jira Agile board ID for sprint lookup (default: `1489`)                                   |
| `--sprint-prefix`           |                             | Sprint name prefix to identify Hummingbird sprints (default: `Hum S`)                     |
| `--handoff-file`            | `CVE_ANALYSIS_HANDOFF_FILE` | Write analysis→collector handoff JSON (counters, log, fix times, human_text)              |
|                             | `SENTRY_DSN`                | Optional Sentry DSN for error tracking                                                    |

## Managed Labels

The tool manages the following labels on Jira tickets. These are
automatically applied, upgraded, and cleaned up:

| Label                      | Meaning                                                                         |
| -------------------------- | ------------------------------------------------------------------------------- |
| `upstream-fix-available`   | A fix exists (forge PR/commit, NVD/CVE link, or Fedora Bodhi/DistGit)           |
| `upstream-fix-in-progress` | A fix is in progress (open forge PR, or testing/pending Bodhi update)           |
| `fedora-bz-filed`          | A Fedora Bugzilla has been filed for this CVE                                   |
| `cve-needs-attention`      | Analysis needs human attention (see below)                                      |
| `advisory-mr-failed`       | Advisory MR has unresolvable rebase conflicts                                   |
| `cve-next-release`         | Fix will arrive via the next upstream release (set manually; see below)         |
| `awaiting-vex`             | Closed ticket awaiting VEX agreement with Jira resolution (HUM-5843 work queue) |

Legacy `fedora-fix-available` / `fedora-fix-in-progress` labels are no
longer applied; the bot removes them on subsequent runs (Bodhi/DistGit
evidence now uses `upstream-fix-*`).

Labels are upgraded automatically (e.g., `upstream-fix-in-progress` is
replaced by `upstream-fix-available` when a fix is merged). Stale labels
are removed when tickets are closed, except `fedora-bz-filed` which is
preserved as an audit record and `awaiting-vex` which is managed by the
VEX reconcile pass. The `cve-needs-attention` label is removed
automatically when the warning condition no longer applies.

### awaiting-vex (HUM-5843)

When a ticket is closed as **Done-Errata** or **Not a Bug** in production,
analysis adds `awaiting-vex`. Each `--resolve` cycle then:

1. Queries Closed tickets labeled `awaiting-vex`, plus Closed **Done-Errata**
   / **Not a Bug** tickets resolved in the last 7 days (so human/agent closes
   that skipped the close-time enqueue still enter the queue)
2. Fetches Hummingbird status from the Red Hat CSAF VEX feed for every CVE ID
   in the ticket summary, scoped to that ticket's package (not a CVE-wide
   worst-case across other Hummingbird products)
3. Compares to the Jira resolution for the `awaiting-vex` work queue
   (`Done-Errata` → `fixed`, `Not a Bug` → `known_not_affected`,
   `package_not_listed` when CSAF omits the package, or `fixed` -- CSAF's
   known_not_affected/fixed split tracks dependency-version presence, not
   code-path reachability, so a Not a Bug verdict is compatible with either,
   e.g. a downstream package swept into an unrelated dependency's
   rebuild-and-fix); match requires all CVE IDs to agree
4. On match: records `vex_status` + `vex_resolved` (scan time)
   in the collector handoff and removes the label if present
5. On pending/mismatch: adds `awaiting-vex` if missing, otherwise keeps it,
   and still emits current `vex_status`. Catch-up closes with no CVE ID in
   the summary are not labeled: MATCH is impossible, and adding the label
   would re-select the ticket forever.

The Closed tab computes MATCH from stored `vex_status` plus Jira resolution;
it does not use stored `vex_match_state` as source of truth. Dashboard
reads merged event metadata (later rows overlay earlier ones) so a ticket
closed after `cve_published` does not keep leftover New / In Progress
analysis text as Resolution (HUM-6091).

The collector also fetches any `vex_updates[]` keys that the watermark JQL
missed, so a same-cycle catch-up match still lands on the Closed tab.

**Won't Do** closures do not enqueue `awaiting-vex`. Open-ticket VEX
mismatches remain a `vex-checker reconcile` concern.

### cve-next-release

The `cve-next-release` label is used for CVEs where no immediate action is
possible and the fix will arrive via the next upstream release. It is set
manually. Common scenarios include:

- **Vendored dependencies**: A fix exists in a vendored package (e.g.
  `ws` inside `dotnet`) but cannot be consumed until the parent package
  updates its vendored copy.
- **No backport path**: The fix cannot be backported to the current
  release and must wait for a future upstream version.
- **Upstream fix pending**: A fix is expected upstream but has not landed
  yet, and no interim mitigation is available.

Package metadata `fix_status: 0` (EOL) does **not** use this label; those
tickets are closed as **Won't Do** instead.

When this label is present on a ticket:

- The automation skips applying `upstream-fix-available` and
  `upstream-fix-in-progress` labels, since they would be misleading,
  and removes them if already present
- The SBOM version check continues each analysis cycle, so when a new
  upstream release containing the fix is consumed, the ticket is closed
  normally
- Fedora labels and `cve-needs-attention` are still managed normally
- The label is not removed automatically; it must be removed manually
  when no longer applicable

`cve-next-release` tickets are not closed just because Hummingbird shipped a
new build. They close when analysis concludes the CVE is no longer affected;
for vendored dependencies, that means the SBOM shows the fixed upstream
version, not merely a new parent NVR.

### cve-needs-attention conditions

The `cve-needs-attention` label is applied when any of these conditions
are detected:

- **Product mismatch**: CVE vendor/product does not match the Hummingbird
  package (e.g. node-tar CVE filed against GNU tar) after an SBOM-first
  check finds no vendored dependency hit
- **Package missing from rpms but present in SBOM/binaries**: package
  directory is absent from the rpms repo, yet the CVE product appears in
  the package SBOM and binary confirmation is present or unknown
- **Multiple products**: CVE lists multiple distinct products with different
  versioning schemes and no `cve_product` override is configured
  (e.g. NGINX Open Source + NGINX Plus)
- **Git-only version data**: CVE version data uses commit hashes instead of
  numeric versions (e.g. libsodium with `lessThan: ad3004ec...`)
- **CNA data error**: A git commit hash is used where a version number is
  expected without setting `versionType: "git"`, including hashes embedded
  in operator syntax (e.g., `"version": "< 6374ae0bcdfe..."`). The warning
  includes a link to the [CVE 5.0 source control versions spec][cve-spec]
- **Malformed version field**: The version field contains syntax that could
  not be parsed (e.g., compound operator+hash ranges like
  `>= hash1, < hash2`)
- **CVE record not found**: the CVE record file was not found in the cvelistV5 repo
- **No repo version**: the Hummingbird package was not found in the Pulp
  repository (package name mismatch or missing SRPM)
- **Stale closure**: ticket is Closed in Jira but current analysis recommends
  a different resolution

### Human override workflow

When a human wants to take over a ticket from the bot:

1. **Assign the ticket to yourself** -- the bot skips all automation on
   tickets not assigned to the bot account. Reassign to the bot to
   re-enable automation.

2. **Set "Fixed in Build"** -- if you know the fix is in a specific SRPM,
   set the "Fixed in Build" field to the SRPM name (e.g.
   `libarchive-3.8.7-1.hum1.src.rpm`). The bot will use this to create
   the advisory MR and close the ticket, bypassing its own analysis.

**Migration note:** The `cve-analysis-okay` label is deprecated and no
longer suppresses automation. Existing tickets with this label will be
re-processed by the bot on its next run. To keep the bot from touching a
specific ticket, reassign it to yourself before the next run. The
`cve-analysis-okay` label can then be removed manually.

## Output

### Human-Readable

```text
Project: HUM  Component: Security

  HUM-796  CVE-2026-2673 openssl: buffer overflow [hummingbird-1]
    Open since:       7 days (Mar 24 2026)
    Labels resolution: upstream-fix-available
    OpenSSL / OpenSSL:
      Affected versions: 3.5.0 < 3.5.6
      Fixed in:          3.5.6
      Hummingbird repo (latest):  3.5.5 / openssl-3.5.5-1.hum1.src.rpm
    Repo:             https://github.com/openssl/openssl
    CVE-2026-2673: upstream-fix-available (3 PRs)
      [CLOSED, 3 commits] Fix group tuple handling in DEFAULT expansion (3.5)
              https://github.com/openssl/openssl/pull/30110
    Fedora update: FEDORA-2026-abc123 openssl-3.5.6-1.fc44 (stable, security)
              https://bodhi.fedoraproject.org/updates/FEDORA-2026-abc123
    Jira current:     In Progress / (none)
    Jira resolution:  In Progress / affected (repo 3.5.5 is in affected range 3.5.0 < 3.5.6)
```

### JSON

Each issue includes: `key`, `summary`, `labels`, `open_since`, `cve_ids`,
`cves` (with version and resolution data), `jira_current` (status/resolution),
`computed_resolution`, and `upstream` (with PR/MR search results, Bodhi
updates, and Fedora version data).

## Viewing Logs

The CVE analysis tool runs as a pod in the `hummingbird--internal`
namespace on mpp-prod. See the [OpenShift MP+][ocp-docs] internal docs
page for general log access instructions.

For CVE analysis specifically:

- **Live:** Open the [mpp-prod pods list][mpp-pods] in the OpenShift
  console, filter for `cve-analysis`, and open the **Logs** tab.
- **Grafana/Loki:** Open the Hummingbird Grafana logs dashboard, select
  cluster `mpp-prod` and namespace `hummingbird--internal`, then filter
  for `hummingbird-cve-analysis` pods.

[ocp-docs]: https://internal-documentation.hummingbird-project.io/l/infra-openshift
[mpp-pods]: https://console-openshift-console.apps.mpp-e1-prod.9e4s.p1.openshiftapps.com/k8s/ns/hummingbird--internal/pods

## Library layout

Reusable library code lives under `hummingbird_cve_analysis/lib/`. The CLI
entry point remains `cve_analysis.py` at the package root.

| Module                      | Responsibility                                                   |
| --------------------------- | ---------------------------------------------------------------- |
| `lib/github_client.py`      | GitHub PR and release API reads                                  |
| `lib/gitlab_client.py`      | gitlab.com merge request, release, and feature-flag reads        |
| `lib/fedora.py`             | Fedora Bodhi, DistGit spec parsing, and Bugzilla helpers         |
| `lib/upstream.py`           | Upstream forge search, cgit commits, and fix-status analysis     |
| `lib/net.py`                | Shared network error tuple used by library HTTP callers          |
| `lib/nvd.py`                | NVD JSON 2.0 feed cache, download, and reference parsing         |
| `lib/cvelist.py`            | Local cvelistV5 repository access and CVE 5.0 affected parsing   |
| `lib/jira_client.py`        | Jira REST read/write primitives and ADF comment helpers          |
| `lib/versions.py`           | Pure version comparison and range helpers                        |
| `lib/analysis.py`           | CVE decision pipeline, product matching, and lifecycle analysis  |
| `lib/formatting.py`         | Human and Jira comment/output formatting                         |
| `lib/resolve.py`            | Jira/advisory mutation helpers (labels, close, attach SBOM)      |
| `lib/ticket.py`             | Shared one-ticket analyze/resolve API                            |
| `lib/advisory_handler.py`   | CEE advisories repo clone/edit/MR helpers                        |
| `lib/osidb_client.py`       | OSIDB subpackage PURL lookups and flaw/affect created timestamps |
| `lib/pulp.py`               | Hummingbird Pulp repo RPM/SRPM listings and repodata lookups     |
| `lib/catalog.py`            | Container catalog API for image publish times                    |
| `lib/rpms_repo.py`          | Local rpms git repo, package map loading, fixed-build detection  |
| `lib/sbom.py`               | SBOM fetch, vendored dependency lookup, binary RPM confirmation  |
| `lib/slack.py`              | Slack webhook helper                                             |
| `lib/version_transforms.py` | Named version-transform helpers                                  |

`cve_analysis.py` is the CLI entry point only (argument parsing, JQL building,
signal handling, and the main orchestration loop). Library code lives under
`lib/`; import and patch those modules directly. New cron jobs, webhooks, and
other integrations should prefer `ticket.process_ticket` for analyzing (and
optionally resolving) a single Jira issue:

```python
from hummingbird_cve_analysis.lib import ticket

result = ticket.process_ticket(
    issue,
    pkg_map,
    catalog_source_map,
    github_token,
    gitlab_token,
    base_url=base_url,
    token=token,
    resolve=False,
)
```

## Development

See the main [README][readme] for development workflows.

```bash
make hummingbird-cve-analysis/setup  # Install dependencies
make check                            # Lint code (ruff)
make test                             # Run unit tests
```

## License

This project is licensed under the GNU General Public License v3.0 or later -
see the [LICENSE][license] file for details.

[readme]: https://gitlab.com/redhat/hummingbird/tools/-/blob/main/README.md
[license]: https://gitlab.com/redhat/hummingbird/tools/-/blob/main/LICENSE
[cve-spec]: https://github.com/CVEProject/cve-schema/blob/main/schema/docs/versions.md
