VEX Checker

A CLI tool that works with the Red Hat CSAF VEX feed and the Hummingbird catalog API to check CVE statuses and track coverage across Jira, VEX advisories, and container image scan results.

Subcommands

Subcommand Purpose
check Look up a single CVE and show Hummingbird product statuses
reconcile Cross-reference Jira CVE tickets with the VEX feed
catalog Cross-reference catalog API CVEs with Jira tickets

Prerequisites

  • Python 3.11+
  • Internet access to security.access.redhat.com (no VPN required) for check/reconcile; to api-hummingbird.hummingbird-project.io (no VPN required) for catalog
  • JIRA_EMAIL Atlassian account email — required for reconcile and catalog
  • JIRA_TOKEN Jira API token — required for reconcile and catalog

check — Single CVE lookup

Fetches the CSAF VEX document for a given CVE and prints the status of all Hummingbird products, filtering out all other RHEL/OpenShift/etc. entries.

check: usage

./check_vex.py check CVE-YYYY-NNNNN [--json]

check: options

Option Description
CVE-ID CVE to look up
--json, -j Output results as JSON

check: examples

$ ./check_vex.py check CVE-2014-8090

CVE-2014-8090 — CVE-2014-8090 ruby: REXML billion laughs attack (Moderate)
  hummingbird-1:ruby.src      known_affected  none_available
  hummingbird-1:ruby3.3.src   known_affected  none_available
  hummingbird-1:ruby3.4.src   known_affected  none_available
  hummingbird-1:ruby4.0.src   known_affected  none_available
$ ./check_vex.py check CVE-2014-8090 --json
{
  "cve": "CVE-2014-8090",
  "title": "...",
  "severity": "Moderate",
  "hummingbird": [
    { "product_id": "hummingbird-1:ruby.src", "status": "known_affected", "remediation": "none_available" },
    ...
  ]
}

check: exit codes

Code Meaning
0 No VEX document found, no Hummingbird products listed, or no known_affected status
1 One or more Hummingbird products are known_affected
2 Invalid CVE ID format

reconcile — Jira/VEX sync check

Queries the Jira HUM project (Security component) for all CVE tickets, then checks each CVE in the VEX feed. Reports two types of mismatches:

  • Open in Jira but not known_affected in VEX — VEX says the issue is resolved but the Jira ticket is still open.
  • Closed in Jira but known_affected in VEX — the Jira ticket was closed but Red Hat’s advisory still marks Hummingbird as affected.

Jira tickets are considered closed when their status is one of: Done, Closed, Won't Fix, Not a Bug.

VEX documents are fetched in parallel (--workers, default 20) so the command is fast even with hundreds of CVE tickets.

reconcile: usage

export JIRA_EMAIL=you@redhat.com
export JIRA_TOKEN=<your-api-token>
./check_vex.py reconcile [--workers N] [--json]

Credentials are read exclusively from environment variables to avoid exposing them in process listings.

reconcile: options

Option Description
--jira-url Jira base URL (default: https://redhat.atlassian.net)
--workers Parallel VEX fetch workers (default: 20)
--json, -j Output results as JSON

reconcile: example

$ ./check_vex.py reconcile

Fetching Jira tickets... 142 ticket(s)
Fetching VEX statuses for 89 unique CVE(s)...
  89/89
2 mismatch(es) found:

  HUM-1234  CVE-2025-1234  closed → known_affected  (VEX still open)
    Jira: https://redhat.atlassian.net/browse/HUM-1234
    VEX:  https://security.access.redhat.com/data/csaf/v2/vex-feed/2025/cve-2025-1234.json

  HUM-5678  CVE-2024-5678  open → fixed  (VEX not affected)
    Jira: https://redhat.atlassian.net/browse/HUM-5678
    VEX:  https://security.access.redhat.com/data/csaf/v2/vex-feed/2024/cve-2024-5678.json

reconcile: exit codes

Code Meaning
0 No mismatches found
1 One or more mismatches detected
2 Missing JIRA_EMAIL or JIRA_TOKEN

catalog — Catalog API / Jira coverage check

Fetches all CVEs currently detected in Hummingbird container images (via Grype scan results stored in the catalog API) and cross-references them with Jira HUM Security tickets. Reports CVEs that have no corresponding Jira ticket.

CVEs with a closed Jira ticket are reported separately as expected propagation delay — the typical flow is CVE fix → RPM → image rebuild → Grype DB update, and Jira tickets are closed early while the catalog API reflects the latest Grype scan (updated ~once daily).

Non-CVE vulnerability identifiers (e.g. GHSA-*) from Grype are filtered out since they are not tracked in Jira.

catalog: usage

export JIRA_EMAIL=you@redhat.com
export JIRA_TOKEN=<your-api-token>
./check_vex.py catalog [--api-url URL] [--workers N] [--json]

Each CVE line shows severity, age since first detection, affected images, and for tracked CVEs the Jira ticket key. Components with known fix versions are shown in brackets when available.

Closed Jira tickets are cross-referenced with the VEX feed to distinguish between different root causes:

  • Done-Errata, VEX still affected – fix shipped in RPM but not yet propagated to container images; needs rebuild or lockfile refresh.
  • Done-Errata, VEX resolved – fix fully propagated, Grype DB just needs its daily update to stop flagging.
  • Won’t Do / Not a Bug, VEX resolved – VEX already marks CVE as not affected; Grype DB delay, will self-resolve.
  • Won’t Do / Not a Bug, VEX still affected – genuine known issue that will persist in scans (accepted risk, upstream won’t fix, etc.).

catalog: options

Option Description
--api-url Catalog API base URL (default: https://api-hummingbird.hummingbird-project.io/v1)
--jira-url Jira base URL (default: https://redhat.atlassian.net)
--workers Parallel VEX fetch workers (default: 20)
--json, -j Output results as JSON

catalog: example

$ ./check_vex.py catalog

Catalog: 72 CVEs across 29 images (scanned 2026-04-28T10:45:38Z)
Jira: 748 tickets (437 unique CVEs)

11 untracked CVE(s) (no Jira ticket):

  CVE-2008-2662  High     8d  ruby (12 tags)  [ruby3.3@3.3.10, +36 more]
  CVE-2026-2950  Medium   8d  aspnet-runtime, ... (30 tags)  [lodash@4.17.21]
  ...

45 tracked CVE(s) open in Jira:

  CVE-2025-68114  High  8d  php (3 tags)  [capstone@5.0.6]  HUM-925
  ...

8 closed in Jira (Done-Errata) but VEX still affected -- verify fix propagated:

  CVE-2026-27143  Critical  8d  caddy, go-fdo-client, ... (10 tags)  HUM-969

4 closed in Jira (Done-Errata), VEX resolved -- Grype DB delay:

  CVE-2025-61732  High  8d  go, xcaddy (6 tags)  HUM-1119

3 closed in Jira (Won't Do / Not a Bug), VEX resolved -- Grype DB delay:

  CVE-2026-27140  High  8d  caddy, go, ... (10 tags)  HUM-967

1 closed in Jira (Won't Do / Not a Bug), VEX still affected -- genuine:

  CVE-2025-12781  Medium  8d  postgresql, python, ... (17 tags)  HUM-1387

catalog: exit codes

Code Meaning
0 All catalog CVEs are tracked (have a Jira ticket)
1 One or more untracked CVEs found
2 Missing JIRA_EMAIL or JIRA_TOKEN

Development

# Run tests
cd vex-checker && python3 -m unittest discover tests -v

# Run linter (from repo root)
ruff check vex-checker

License

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