CVE Analysis
Automated analysis of HUM Jira Security (CVE) tickets. For each ticket, the tool fetches vulnerability data from MITRE and NVD, compares it against the versions shipped in the Hummingbird package repository, searches for upstream and Fedora fixes, and computes whether the ticket should be closed, moved to In Progress, or flagged for manual investigation.
CVE Lifecycle & State Vocabulary
A CVE tracker moves through a shared lifecycle that both this tool and the
CVE agent reason about. The deterministic
analysis decides a target resolution for each ticket; the residual it
cannot resolve (cve-needs-attention) is handed to the agent; closed tickets
are later reconciled against the VEX feed.
stateDiagram-v2
direction TB
[*] --> New
New --> InProgress: affected / needs investigation
New --> DoneErrata: fixed by analysis
New --> NotABug: component not present
New --> WontDo: package EOL
InProgress --> Agent: upstream-fix-available / cve-needs-attention
Agent: CVE Agent
Agent --> Review: fix MR opened
Agent --> DoneErrata: already_fixed
Agent --> NotABug: not_affected
Agent --> InProgress: no_upstream_fix / needs_human / error
Review --> DoneErrata: MR merged
Review --> Review: MR conflicts / needs human
state "Closed (JiraStatus)" as Closed {
DoneErrata: Done-Errata
NotABug: Not a Bug
WontDo: Won't Do
}
DoneErrata --> AwaitingVEX: enqueue awaiting-vex
NotABug --> AwaitingVEX: enqueue awaiting-vex
WontDo --> [*]: no VEX reconciliation
AwaitingVEX --> [*]: VEX matches resolution
AwaitingVEX --> AwaitingVEX: pending / mismatch
classDef jiraStatus fill:#e5e7e9,stroke:#27ae60,color:#1f2937
classDef closedStatus fill:#e5e7e9,stroke:#1f5f8b,color:#fff
classDef resolution fill:#d5f5e3,stroke:#27ae60,color:#1f2937
classDef automation fill:#e5e7e9,stroke:#7f8c8d,color:#1f2937
class New,InProgress,Review jiraStatus
class Closed closedStatus
class DoneErrata,NotABug,WontDo resolution
class Agent,AwaitingVEX automation
Notes on the diagram:
- Agent is the CVE agent. See that tool’s doc for the full internals and automated sweep deployment.
- Blue states are Jira statuses, green states are resolutions, and gray states represent supporting automation.
- Won’t Do (package EOL) closes without VEX reconciliation;
Done-ErrataandNot a Bugenqueueawaiting-vexand leaveAwaitingVEXonly once the VEX feed matches the resolution.
These state names live in exactly one place,
hummingbird_cve_analysis/lib/states.py, imported by both tools:
JiraStatus—New,In Progress,Review,Closed.Resolution—Done-Errata,Not a Bug,Won't Do(set when closing).Disposition— the second field of acomputed_resolutionline: the three resolutions above plus the open-ticket pseudo-dispositionsaffectedandneeds investigation.Verdict— the CVE agent’s per-ticket outcome (needs_change,not_affected,already_fixed,no_upstream_fix,needs_human,error), withVERDICT_OUTCOMEmapping each verdict to the Jira status/resolution it implies.
How It Works
1. Ticket Selection
The tool queries Jira for HUM project tickets with component Security.
Tickets can be filtered by creation period (--show-since "2 weeks"),
limited to specific keys (HUM-796 HUM-518), or scanned in bulk. Closed
tickets are skipped by default unless --include-closed is specified or
specific ticket keys are given on the command line.
Embargoed tickets (security level “Embargoed Security Issue” or embargo status field set to true) are always skipped.
Tickets with no valid CVE-YYYY-NNNN in the CVE ID field or Summary
(for example GHSA/OSV-only trackers) are postponed: analysis is skipped,
an INFO message is logged, and with --resolve a one-time tracker comment
is posted and the cve-needs-attention label is applied (open tickets
only). Use --skip-no-cve to omit those tickets without posting the
postpone notice or adding the label.
2. CVE Data Collection
For each ticket the tool:
-
Extracts the CVE ID from the Jira CVE ID field (
customfield_10667) when populated, otherwise from the Summary via a regex match for CVE-YYYY-NNNN patterns. If neither source has a CVE ID, analysis is postponed (see above). -
Loads the CVE record from a local clone of the cvelistV5 repository and parses the CVE 5.0
affectedblock, following the CVE 5.0 Product and Version Encodings specification. This includes:- Parsing
lessThanandlessThanOrEqualversion ranges - Handling
lessThan: "*"(no upper bound) andlessThan: "4.*"(end-of-series, all versions in the 4.x series) wildcards per the spec - Processing
changeslists that subdivide ranges into affected and unaffected segments - Respecting
defaultStatusat the product level - Filtering out
versionType: "git"entries that use commit hashes instead of numeric versions (these are preserved as informational data but not used for version comparison) - Detecting CNA data errors where git commit hashes are used in
version fields without
versionType: "git", including hashes wrapped in operator syntax (e.g.,< 6374ae0bcdfe...) - Parsing inline comparison operators (
< 12.3.0,>= 4.0,<= 2.5) that are not part of the CVE 5.0 schema but are widely used by CNAs in practice - Parsing compound range bands (
>= 2.0, < 2.2.26and> 1.32.3, < 1.34.6) used by some CNAs to express a closed-open range in a single version string
- Parsing
-
Looks up version ranges from the NVD data feeds and merges them with the MITRE data. NVD data is obtained from the JSON 2.0 data feeds at
nvd.nist.gov/feeds/json/cve/2.0/(per-year.json.gztarballs updated daily, plus a CVE-Modified overlay updated every 2 hours). Feeds are cached locally (--nvd-cache-dir) and only re-downloaded when the NVD.metafile SHA256 indicates newer data is available. When MITRE has no vendor-specific data (all vendors are “n/a”), NVD ranges replace the MITRE data entirely. NVD references tagged “Patch” or matching commit URL patterns are also extracted and used for upstream fix detection (see below). -
Looks up the Hummingbird package version by scraping the Pulp repository index at
packages.redhat.comfor the latest RPM matching the package name extracted from the ticket summary.
3. Resolution Computation
The tool compares the shipped Hummingbird version against the affected version ranges to compute a recommended resolution:
- Closed / Done-Errata: The repo version is explicitly unaffected (an
exact version, an unaffected range, or product
defaultStatus: unaffected), or NVD-only data establishes that it is past the fix/outside affected ranges. An exact unaffected version does not cover later versions. For CVE 5 data, when no entry matches, omitteddefaultStatusmeans unknown, not safe. Overlapping affected ranges from multiple streams prevent a version-based close unless the version is explicitly unaffected. - In Progress / affected: The repo version falls within an affected range.
- In Progress / affected (no version data): Neither MITRE nor NVD has
affected version information. The ticket is moved to In Progress for
manual review but does not receive the
cve-needs-attentionlabel. - In Progress / needs investigation: Version data was available but
could not be compared (e.g., CVE only provides git commit hashes), or
multiple distinct products with different versioning schemes are listed
and no
cve_productoverride is configured. These cases receive thecve-needs-attentionlabel.
4. Product Mismatch Detection
The tool detects when a CVE was filed against the wrong Hummingbird
package. It compares the CVE vendor/product names against the Hummingbird
package name and upstream repo URL from the package map. For example, a CVE
for isaacs/node-tar (an npm package) filed against the tar RPM (GNU
tar) is flagged as a mismatch. This detection works even without a repo URL
by comparing normalized product names.
Mismatch triage uses a two-phase evidence gate:
- Identity mismatch — CVE product/vendor does not match the package
(
cve_productoverride or heuristic name/repo matching) - SBOM (+ binary) evidence — only then decide misfiled vs vendored vs needs investigation
Phase 2 fetches the latest package SBOM from the Hummingbird Pulp
repository and records the artifact used (NVR/URL) plus match evidence.
Go module matching accepts the module root and major-version paths
(github.com/vendor/product or .../v5). Deeper import paths such as
.../api or .../daemon only count as a hit when a CVE path hint
(from malformed/version-path fields) aligns with that subpackage, so a
client/API module does not satisfy a daemon-only CVE.
If the CVE product is found as a vendored dependency, the tool runs
binary confirmation in this order:
- SPDX SBOM evidence —
sourceInfopointing at shipped paths (e.g. Go buildinfo under/usr/bin/...) orCONTAINSfrom binary RPM roots (*.x86_64,*.aarch64, …) markspresent_in_binarywithout Syft. Evidence only under.src/go.modis treated as source-only for the next step. - RPM
Provides: bundled(...)— downloaded non-debug binary RPMs are queried withrpm -qp --provides. A matching Fedora bundled Provide (npmbundled(npm(name)), Gobundled(golang(IMPORT_PATH)), Node corebundled(nodejs-undici), etc.) markspresent_in_binarywithevidence_method=rpm_bundled_provides. Ecosystem wrappers such asnpm(...)are unwrapped so a CVE/SBOM search fornanoidmatchesbundled(npm(nanoid)). This catches dependencies embedded into binaries (including minified frontend JS) that Syft cannot inventory as on-disk packages. - Syft on binary RPMs — Syft scans the same RPMs, even after a
Provides or SPDX hit, to find additional shipped copies. When OSIDB reports
subpackage names that match RPMs for the NVR, only those RPMs are
scanned for absence checks; vendored-version checks scan the full NVR.
If OSIDB data is missing or the names do not match this
package’s RPMs (for example a Redis CVE filed against boost), the
full NVR set is used. Pulp download URLs use the arch from the RPM
filename (
aarch64files underaarch64/,x86_64underx86_64/; noarch stays on thex86_64listing).
Syft absence only becomes absent_in_binary_confirmed when SBOM
evidence is source-only (or no SBOM document was available) and Syft
actually ran. A miss with ambiguous SBOM evidence stays unknown so
embedded Go/Rust modules are not closed as source-only when SPDX
already ties them to a binary. A Provides-only scan (Syft unavailable)
never confirms absence. Per-RPM download or scan failures are skipped so
sibling RPMs can still confirm presence; a failed scan never proves
absence or that all vendored copies are fixed. A Provides query failure
falls through to Syft for that same RPM but leaves the version evidence
incomplete for auto-closure.
present_in_binary(mismatch_vendored): legitimate vendored ticket; compare the confirmed binary/Provide version (falling back to the SBOM vendored dep version) against the CVE ranges (parentlatest_repo_version/ Hummingbird repo version stay the package), and applycve-needs-attentionso operators can review the binary hit. Matching versions across binary RPMs, Syft artifacts, SPDX binary packages, and bundled Provides are all collected. A binary scan must complete before the versions can justify auto-closure; a confirmed presence with an incomplete scan remains open for investigation. Resolution is worst-of / all-must-be-fixed: the ticket stays affected if any copy is still affected;Done-Errataonly when every copy is not affected. The tool does not invent multi-major fix mappings from a single CVE fixed line — Fixed in Build remains the override when CVE version data only describes one release line. Placeholder dep versions such as cargostd@0.0.0are not comparable: fall back to the parent package version when available; otherwise setnon_comparable_vendored_version, recommendNew / needs investigation, and applycve-needs-attention— neverDone-Erratafrom “0.0.0 is not in any affected range”absent_in_binary_confirmed(mismatch_source_only): source-only; eligible to close as Not a Bug / Component not Present. Must never set Fixed in Build or take the advisory / Done-Errata path.unknown(mismatch_binary_unknown): leave for investigation; do not auto-close or call the ticket misfiled
Stable reason codes are emitted in analysis output (Reason codes:)
and on each CVE entry (reason_code / mismatch_gate) for automation:
| Reason code | Meaning |
|---|---|
mismatch_sbom_miss |
Identity mismatch + SBOM miss → misfiled candidate |
mismatch_sbom_unavailable |
Identity mismatch but SBOM could not be checked |
mismatch_binary_unknown |
SBOM hit but binary presence unknown |
mismatch_source_only |
SBOM hit confirmed absent from binaries |
mismatch_vendored |
SBOM hit confirmed present in binaries → label for review |
non_comparable_vendored_version |
Vendored version is a placeholder (e.g. 0.0.0) with no parent fallback → needs attention |
A misfiled recommendation requires identity mismatch + SBOM miss
(no vendored hit). Ambiguous checks and confirmed binary RPM matches keep
cve-needs-attention with the reason code above. When present in binaries,
normal resolution flow still proceeds (In Progress / Close) in addition to
the attention label. The upstream fix search targets the parent package’s
repo. Each analysis cycle re-fetches the SBOM and re-runs binary
confirmation.
5. Upstream Fix Detection
Fix search uses a precedence model so tickets are not dual-labeled from both Fedora and upstream when a definitive path exists:
- CVE/NVD fix links (preferred): NVD references tagged “Patch” or matching commit URL patterns, plus CVE/NVD PR/MR references that match the package’s upstream repo. When these yield a fix label, Bodhi and forge search are skipped.
- Otherwise, repo-aware search from
metadata/*.jsonupstream_repo:- Fedora-based (
src.fedoraproject.org,pagure.io, or missing repo URL): Bodhi + DistGit only (see §6) - Non-Fedora forge (GitHub/GitLab/cgit): forge search only
- Fedora-based (
Forge Search (non-Fedora fallback):
- GitHub: Searches PRs via the GitHub API (
/search/issues) for PRs mentioning the CVE ID. Fetches commit counts from the pulls API. - GitLab: Searches merge requests via the GitLab API on supported
instances (gitlab.com, gitlab.gnome.org, gitlab.freedesktop.org, etc.).
The
--gitlab-tokenis only sent to gitlab.com to avoid 401 errors on other instances. - cgit: Scrapes commit log pages on cgit hosts (Savannah, Sourceware,
kernel.org, busybox.net, etc.) for commits mentioning the CVE ID.
Handles URL rewrites for Savannah hosts (e.g.,
git.savannah.gnu.org/git/tocgit.git.savannah.gnu.org/cgit/).
Fix status is classified as:
upstream-fix-available: At least one merged/closed PR, committed fix, or NVD patch referenceupstream-fix-in-progress: At least one open PR with no merged fixes
6. Fedora Fix Detection
Used only for Fedora-based packages (or when no upstream repo is configured)
and only when CVE/NVD fix links did not already produce a label. Hits are
classified with the same upstream-fix-* labels as forge/NVD evidence
(Bodhi update links are still shown in analysis output):
- Bodhi: Queries the Fedora Bodhi API for updates matching the package
name that reference the CVE ID (in the
cveslist ornotesfield). Supports multi-page results. Stable →upstream-fix-available; testing/pending →upstream-fix-in-progress. - DistGit Spec Scan: When Bodhi has no matches, fetches the Fedora
DistGit spec file and scans for CVE references in patch filenames,
changelog entries, and comments to detect backported fixes
(
upstream-fix-available).
7. Fixed Build Detection
The tool automatically detects whether the current Hummingbird build fixed a CVE by checking if all CVE IDs are mentioned anywhere in the package directory (patch names, changelogs, comments in the .spec file and so on). If found, it returns the current source RPM name. Assuming that we run the analysis frequently enough, this is precise enough, otherwise it errs on the side of caution (i.e. it possibly marks a higher version as fixed even if an earlier one already carried the fix).
The detected build is shown in the output as Detected Fix: package-version-release.hum1.src.rpm.
When --resolve is specified and a fixed build is detected, the tool
automatically populates the “Fixed in Build” Jira field with the detected SRPM
name, but only if:
- The field is not already set (manual values take precedence)
- The issue is not in Closed status (respects human closure decisions)
- The assessment is not
mismatch_source_only/source_only_confirmed(Component not Present closes as Not a Bug and clears any Fixed in Build) - The computed resolution is already Closed / Done-Errata or every ticket
CVE ID appears on an uncommented
PatchN:line (HUM-6182). A spec comment or leftover unapplied file that merely mentions the CVE is not enough. Product-mismatch and EOL tickets never take this PatchN override.
Version-range analysis can still say “affected” after a backport that does not
bump Version (for example an unbounded range such as 1.1.1+ on popt 1.19).
In that case an applied CVE-YYYY-NNNN.patch listed as PatchN: is allowed to
set Fixed in Build. The SRPM must already be in Pulp (the existing
fix_committed_not_built gate is unchanged). The ticket is not closed here;
advisory review remains the backstop.
The RPMs repository can be provided via --rpms-repo, pointing to a local
clone. This is preferable with multiple runs to avoid the git clone operation.
When not given, the repository will be (shallow) cloned into a temporary
directory.
Similarly, the cvelistV5 repository (used for CVE record lookups) can be
provided via --cve-repo. When not given, a shallow clone is performed into a
temporary directory. For production use, pre-clone and update the repo
externally (e.g. via a CronJob) to avoid the clone overhead on each run. The
tool does not attempt to update the provided repo itself.
8. Advisory Integration (with --resolve)
When a CVE is resolved as “Closed / Done-Errata”, the tool integrates with the CEE GitLab advisories repo to document the fix:
- Clone: The advisories repo (default:
releng/advisories) is shallow-cloned via a bot fork using netrc-based authentication (no tokens in process arguments or logs). - Modify: For each resolved ticket, the advisory YAML is updated:
cves.fixedentries are added, the type is changed from RHBA to RHSA, and CVE references are appended. - Batch MR: All advisory changes are accumulated as individual commits
on a single branch (
cve-analysis/batch). One merge request is created at the end of the run covering all tickets. - Review state: Tickets are transitioned to “Review” (not directly closed) and the batch MR URL is posted to each affected ticket.
- Auto-close: On subsequent runs, tickets in “Review” are checked:
if the advisory MR has been merged, the ticket is closed as
“Done-Errata”. If the MR has unresolvable rebase conflicts, a comment
with manual resolution steps is posted and the
advisory-mr-failedlabel is added. - Slack notifications: When
--slack-webhook-url(or theSLACK_WEBHOOK_URLenv var) is set, the tool sends a Slack message when the batch MR fails to merge or the post-merge pipeline fails. Merge-failure messages @-mention prarit and jstibran.
The advisory flow is skipped when --advisories-project points to a
non-production URL, allowing safe testing without modifying Jira.
The clone lives in a temporary directory and is removed at the end of the
run. It is intentionally left on disk when the batch MR push fails or
--keep-advisory-repo is passed, so an engineer can inspect it; a leftover
clone from a prior run is automatically removed once it is a few hours old
to prevent repeated failures from exhausting disk/inode space.
8.1 Recovery for failed advisory MRs
Troubleshoot an advisory MR
An advisory MR updates release metadata for a published fixed build.
| What changes | Advisory field | Purpose |
|---|---|---|
| CVE associations | spec.content.artifacts[].cves.fixed |
Records which CVEs the applicable artifacts fix. |
| Tracker links | spec.issues.fixed |
Links the HUM trackers covered by the advisory. |
| Security references | spec.references |
Adds links to CVE details and security information. |
| Advisory type | spec.type |
Changes RHBA to RHSA when the update includes security fixes. |
| Advisory text, when needed | spec.synopsis, spec.topic, spec.description |
Describes the security fixes and packages covered. |
If a retry is not enough, the fix depends on the failure: resolve merge conflicts against the current advisory, or check that the CVE, tracker, Fixed in Build and artifact entries refer to the right package/build. Correct advisory text and references where needed, preserving unrelated fixes, artifact provenance, advisory IDs and ship dates. Permission failures need an authorized author/workflow rather than another retry.
- Open the Advisory MR linked in the Jira tracker comments or the failure notification. Check its pipeline, failed job logs and merge status.
- For a transient failure, retry the failed job or rerun the pipeline in GitLab. This is often enough; no advisory edits are needed.
- If retrying does not help, or the MR has conflicts, permission failures or
needs an advisory correction, ask ProdSec Team for help. Include
the MR URL, affected HUM tracker(s), failed job/error and what you retried.
For manual changes, see the advisories repository’s
contribution guide
and current
field permissions.
Note: changes to
spec.typerequire an allowlisted author and must be committed through theHummingbird CVE Analysis <hummingbird-jira-bot-admins@redhat.com>bot. - Confirm that the MR merges and its default-branch publication pipeline succeeds. Leave Done-Errata closure to advisory automation. If the tracker or dashboard remains out of sync, use the recovery steps below.
Recover lifecycle events after advisory delivery
In some failure cases (for example, advisory MR conflicts followed by manual ticket closure), Jira status can be corrected to Done-Errata while dashboard lifecycle metadata remains incomplete. This leaves R-Time rows PENDING (elapsed-to-now) until image, VEX, and close all exist.
Use the recovery script to backfill delivery events:
rpm_fix_published_to_pulp(hb_rpm_fix) frompackages.redhat.comsource RPM timestampsimage_rebuilt_on_quay(hb_image_fix) from catalog/quay image historyhum_ticket_closedfrom Jiraresolutiondate
Only tickets that are Closed / Done-Errata are used for event derivation and
dashboard import. Tickets that remain in Review (or any non-closed state) are
reported and skipped for event backfill.
For tickets still in Review, the script also attempts advisory reconciliation
before deriving events:
- Read advisory MR URL from Jira comments (
Advisory MR: https://...) - If no MR URL is present in comments, search merged advisories MRs for the HUM ticket key in MR description/title
- When a merged MR is found and
--applyis used, transition the ticket toClosed / Done-Errataand then derivehum_ticket_closed
Script location:
hummingbird-cve-analysis/scripts/cleanup_advisory_mr_failure_events.py
Run in dry-run mode first (default):
cd /path/to/tools
export JIRA_TOKEN=your_jira_token
export CEE_GITLAB_TOKEN=your_cee_gitlab_token
export CVE_REPORT_TOKEN=your_dashboard_token
python3 hummingbird-cve-analysis/scripts/cleanup_advisory_mr_failure_events.py \
--jira-user your-jira-user@example.com \
--cee-gitlab-token your_cee_gitlab_token \
--output-json /tmp/cleanup-advisory-derived.json
Apply the backfill to dashboard event storage:
python3 hummingbird-cve-analysis/scripts/cleanup_advisory_mr_failure_events.py \
--jira-user your-jira-user@example.com \
--cee-gitlab-token your_cee_gitlab_token \
--output-json /tmp/cleanup-advisory-derived.json \
--apply
Target specific tickets (instead of default JQL):
python3 hummingbird-cve-analysis/scripts/cleanup_advisory_mr_failure_events.py \
--jira-user your-jira-user@example.com \
--cee-gitlab-token your_cee_gitlab_token \
--apply \
HUM-4884 HUM-4871 HUM-4844
Verification example:
# Replace with the actual dashboard URL (same as --dashboard-url default)
DASHBOARD_URL=https://hummingbird-dashboard-hummingbird--internal.apps.int.spoke.prod.us-east-1.aws.paas.redhat.com
curl -s "${DASHBOARD_URL}/api/cve/r-time?days=30" | jq -r '
.entries[]
| select(.key=="HUM-4884")
| [.key, (.fix_delivered_at // "-"), (.stages.hb_rpm_fix // "-"), (.stages.hb_image_fix // "-")]
| @tsv
'
If hb_rpm_fix and hb_image_fix remain empty in the derived JSON output,
the issue is source-data availability (no resolvable RPM/image publish signal),
not dashboard ingestion.
If a ticket is in Review and no advisory MR can be found in Jira comments or
merged advisories MRs, it is skipped and reported for manual follow-up.
By default, the script targets advisory-mr-failed tickets (for both
Closed / Done-Errata and Review states); override with --jql when needed.
8.2 Dashboard collection ticket selection
Dashboard lifecycle collection (HUM-5603) uses a separate ticket-selection
path from cve_analysis mutations. Helpers live in
hummingbird_cve_analysis/dashboard_selection.py.
Watermark (start of previous run): read the newest entry from the
dashboard Run Log (GET /api/cve/run-log?limit=1). Approximate previous-run
start as run_at - duration_seconds (Run Log run_at is ingest time), then
subtract a small buffer (default 2 minutes) for clock skew. Using start — not
end — avoids missing tickets updated while the previous collector run was in
progress. Overlap is intentional; /api/cve-report event upserts are
idempotent.
JQL selection:
- All still-open HUM Security tickets (rescan for updates)
- Closed tickets with Jira
updated >=watermark - Bootstrap (empty Run Log): open tickets only
- Explicit ticket keys:
key in (...)(any status)
Use --start-time (ISO-8601, e.g. 2026-08-01T00:00:00Z) to override the
Run Log watermark and recollect Closed tickets with updated >= that
start, so dashboard data can be overwritten/backfilled.
Inspect watermark and JQL without modifying Jira or the dashboard.
From the tools repo root, either install the package
(make hummingbird-cve-analysis/setup) or set PYTHONPATH:
cd /path/to/tools
export PYTHONPATH=hummingbird-cve-analysis
export CVE_REPORT_TOKEN=your_dashboard_token
python3 -m hummingbird_cve_analysis.dashboard_selection --prod
# Optional: also search Jira and list matching keys
export JIRA_TOKEN=your_jira_token
python3 -m hummingbird_cve_analysis.dashboard_selection \
--prod \
--jira-user user@example.com \
--fetch-issues -o json-pretty
| Option | Environment Variable | Description |
|---|---|---|
--prod / --preprod |
Required. Hardcoded production or preprod dashboard URL | |
--cve-report-token |
CVE_REPORT_TOKEN |
Bearer token for /api/cve/run-log |
--start-time |
Explicit UTC collection start (ISO-8601); skips Run Log fetch | |
--watermark |
Alias for --start-time |
|
--watermark-buffer-minutes |
Minutes subtracted from previous-run start estimate (default: 2) | |
--fetch-issues |
Also search Jira with the constructed JQL | |
--jira-token / --jira-user |
JIRA_TOKEN |
Jira credentials (only with --fetch-issues) |
--jira-url |
JIRA_URL |
Jira base URL (only with --fetch-issues) |
--max-results |
Max issues to fetch with --fetch-issues (default: 2000) |
|
--output, -o |
human, json, or json-pretty |
8.3 Analysis → collector handoff file
cve_analysis writes a small JSON handoff file for
collect_cve_dashboard (HUM-5799 / HUM-5800). This carries Run Log counters,
captured log_output, and per-ticket fields that are expensive to
recompute (upstream_fix, fedora_fix) plus human_text for Run Log
details. Delivery timestamps (hb_rpm_fix / hb_image_fix) and
hum_ticket_closed are gathered by the collector, not this file. Analysis
stdout is human text only; it does not emit /api/cve-report JSON.
PYTHONPATH=hummingbird-cve-analysis python -u -m hummingbird_cve_analysis.cve_analysis \
--jira-user "$BOT_USER" --jira-token "$BOT_JIRA_TOKEN" \
--handoff-file /tmp/cve_analysis_handoff.json \
...
Handoff shape (schema_version: 1):
- Top level:
run_started_at,duration_seconds, mutation counters (labels_changed,advisories_created/advisories_failed,tickets_closed,comments_posted,attachments_uploaded,fatal_errors),log_output tickets[]:key,upstream_fix,fedora_fix,human_textvex_updates[](HUM-5843):key,vex_status,vex_match_state,vex_resolved,labelsfrom the awaiting-vex reconcile pass
8.4 collect_cve_dashboard
collect_cve_dashboard (HUM-5798 / HUM-5800) owns dashboard JSON emission.
cve_analysis no longer prints /api/cve-report JSON on stdout; it prints
human analysis text and optionally writes --handoff-file for the collector.
- Resolve Run Log watermark + open-ticket rescan (section 8.2). Selection
always includes Closed tickets labeled
awaiting-vex(HUM-5843),awaiting-cve-published, orawaiting-image-rebuild(HUM-9975), then anyvex_updates[]keys missing from that JQL - For each ticket, gather slim lifecycle fields from Jira / cvelistV5 /
Fixed-in-Build → Pulp (
cve_published,hum_ticket_created,hum_ticket_closed,hb_rpm_fix) and catalog image history (hb_image_fix), pluscve_ids– every CVE ID the ticket tracks, from its dedicated CVE ID Jira field (falling back to Summary text), not just the first found in labels; a ticket can legitimately track more than one CVE, and consumers keyed by CVE ID (e.g. the dashboard’s Competitive page HUM ticket matching) index it under every entry here, not just one (see hummingbird-dashboard.md), plus OSIDBosidb_flaw_created/osidb_affect_createdfrom the public OSIDB API (omitted when OSIDB has no data),cve_embargo_lifted– the OSIDB flawunembargo_dt, included only when it postdates the flaw’screated_dt(i.e. the CVE was genuinely embargoed; omitted otherwise) – andosidb_flaw_public– the sameunembargo_dt, but included unconditionally whenever OSIDB reports one, regardless of whether the flaw was ever embargoed –computed_resolutionfrom Jira status/resolution (R-Time gates onDone-Erratain that string),catalog_image_sourcefrom the catalog source map (R-Time delivery is image publish when true, RPM publish when false), andcatalog_images– every{image}:{stream}the ticket’s package actually appears in, per the same catalog source map’s SBOM data (used by the dashboard’s Competitive page to match a ticket to a specific image precisely instead of guessing from the package name; see hummingbird-dashboard.md). Timestamp helpers live inlib/lifecycle.pyand are shared withanalyze_issue - Merge
--handoff-filecounters,log_output, per-ticketupstream_fix/fedora_fix/human_text, andvex_updates - Dry-run prints
/api/cve-reportJSON;--applyPOSTs it
The collector builds the catalog source map once per run for
hb_image_fix, catalog_image_source, and catalog_images. When a delivery
timestamp is omitted, the dashboard keeps any existing
rpm_fix_published_to_pulp / image_rebuilt_on_quay value rather than
clearing it. An empty or failed catalog map omits catalog_image_source and
catalog_images so the dashboard keeps requiring an image / falls back to
its own package-name heuristics.
Catalog fetches (an image’s tag listing, or a specific stream’s SBOM) are
not retried; a failure is logged and that image/stream is silently skipped
when building the source map, so catalog_images’s absence of an
image/stream is not always confirmed ground truth – it can also mean that
image/stream’s data simply failed to collect this run.
build_catalog_source_map() returns the set of (image, stream) pairs it
couldn’t verify alongside the map (stream is None when the image’s own
tag listing failed, so every one of its streams is unknown). Every ticket
in the run carries this set as catalog_unverified_images (rendered as
{image}:{stream}, or {image}:* for a whole unverified image) so a
downstream consumer – the dashboard’s _match_cve_tickets – can tell
“confirmed absent” from “unknown” before excluding a ticket candidate
based on catalog_images alone (see hummingbird-dashboard.md).
PYTHONPATH=hummingbird-cve-analysis python -u -m hummingbird_cve_analysis.collect_cve_dashboard \
--preprod \
--cve-report-token "$CVE_REPORT_TOKEN" \
--jira-user "$BOT_USER" --jira-token "$BOT_JIRA_TOKEN" \
--cve-repo /tmp/cvelistV5 \
--handoff-file /tmp/cve_analysis_handoff.json
# POST to preprod dashboard
PYTHONPATH=hummingbird-cve-analysis python -u -m hummingbird_cve_analysis.collect_cve_dashboard \
--preprod \
--cve-report-token "$CVE_REPORT_TOKEN" \
--jira-user "$BOT_USER" --jira-token "$BOT_JIRA_TOKEN" \
--cve-repo /tmp/cvelistV5 \
--handoff-file /tmp/cve_analysis_handoff.json \
--apply
| Option | Environment Variable | Description |
|---|---|---|
--prod / --preprod |
Required. Hardcoded production or preprod dashboard URL | |
--cve-report-token |
CVE_REPORT_TOKEN |
Bearer token for Run Log + /api/cve-report |
--handoff-file |
CVE_ANALYSIS_HANDOFF_FILE |
Analysis handoff JSON from cve_analysis --handoff-file |
--apply |
POST report to /api/cve-report (default: dry-run JSON on stdout) |
|
--start-time |
Explicit UTC collection start (ISO-8601); skips Run Log fetch | |
--watermark |
Alias for --start-time |
|
--cve-repo |
Local cvelistV5 repo for cve_published |
|
--jira-token / --jira-user |
JIRA_TOKEN |
Jira credentials |
--output, -o |
human, json, or json-pretty |
8.5 verify_image_publish
verify_image_publish (HUM-721) catches CVE tickets that claim a fix via
image rebuild (hb_image_fix in the whiteboard cve_cycle) but whose image
was never actually published to the registry — hb_image_fix is set from
catalog release-history metadata (oldest_created), which can lag or be
wrong; this re-verifies with a live check.
- Search Jira for Closed HUM Security tickets
updatedwithin the last--window-minutes(default 60) - For each, read
hb_rpm_fix/hb_image_fixfrom the whiteboard; skip tickets wherehb_image_fixfalls outside the window - Re-derive the source package name from the summary/labels (no SBOM /
network calls) and re-run the same catalog-history lookup
(
find_image_publish_release) that originally producedhb_image_fix, recovering the specific image/stream/variant/digest - HEAD the image manifest by digest directly against the registry
(
image_manifest_exists) — this is the parthb_image_fixitself never did - Post a Slack alert for each unverified claim; log a summary either way
Read-only: never modifies Jira, the dashboard, or the CVE analysis pipeline.
PYTHONPATH=hummingbird-cve-analysis python3 -m hummingbird_cve_analysis.verify_image_publish \
--jira-user "$BOT_USER" --jira-token "$BOT_JIRA_TOKEN" \
--window-minutes 60
| Option | Environment Variable | Description |
|---|---|---|
--window-minutes |
Look back this many minutes for hb_image_fix (default: 60) |
|
--slack-webhook-url |
SLACK_WEBHOOK_URL |
Webhook for unverified-claim alerts |
--dry-run |
Check and log, but do not post to Slack | |
--catalog-api-base |
Catalog API base URL | |
--jira-token / --jira-user |
JIRA_TOKEN |
Jira credentials |
--jira-url |
JIRA_URL |
Jira base URL |
9. Jira Actions (with --resolve)
When --resolve is specified, the tool modifies Jira tickets:
The deployed analysis CronJob also uses --publish-cve-agent-sweep with
--resolve. After a completed cycle it publishes a small SNS event
(source=cve-analysis, event_type=cve_agent_sweep_requested) to wake the
CVE-agent SQS worker. The event contains only the run timestamp; the agent
searches Jira for eligible tickets itself. SNS publishing failures are logged
without undoing or failing completed Jira analysis; the next scheduled run
publishes another request. See the message bus contract
for the exact message body and attributes.
- Fixed in Build: When a fixed build is detected and the field is not
already set, the tool populates it with the detected SRPM name (only for
non-closed issues). Not-affected
Closed / Done-Erratacloses also set Fixed in Build fromdetected_fixed_buildorlatest_srpmwhen empty, so R-Time image timestamps can be resolved. - Closed / Done-Errata: Tickets where the shipped version is not affected are transitioned to Review with an advisory MR (see above). After the MR merges, they are closed as Done-Errata on the next run.
- Move to In Progress: Tickets where the shipped version is affected are transitioned to In Progress with a comment including affected version ranges, upstream fix status, and Fedora update links.
- Needs Investigation: Tickets with incomplete version data get a comment explaining why automatic resolution was not possible.
- Product Mismatch: A comment is posted explaining the mismatch (with
SBOM artifact evidence) and the
cve-needs-attentionlabel is applied. No transition is performed. Auto-detected Fixed-build / delivery timestamps (hb_rpm_fix,hb_image_fix) are cleared so R-Time does not treat misfiled tickets as done. Human Fixed in Build overrides are kept. When--resolveruns against the production advisories project, the analyzed package SBOM JSON is also attached to the ticket as{nvr}.sbom.json(skipped if that filename is already present). The same attachment is applied for other SBOM-backed review paths (vendored binary hits, source-only closes, binary-unknown). - Package Not Present: If the package is missing from the rpms repo,
the tool still runs an SBOM-first check against the CVE product(s). An
SBOM hit triggers Syft binary confirmation:
absent_in_binary_confirmedcloses as Not a Bug / Component not Present; presence or unknown leaves the ticket for investigation. An SBOM miss/unavailable closes as Not a Bug with VEX Component not Present. The close timestamp is persisted ashum_ticket_closedfor dashboard ingestion. - Package EOL (
fix_status: 0): Tickets for packages marked End-Of-Life in rpms metadata are closed as Won’t Do with no VEX Justification. This takes precedence over Done-Errata and Fixed-in-Build / advisory paths. Automation labels (upstream-fix-*, legacyfedora-fix-*,cve-needs-attention,cve-next-release,advisory-mr-failed) are removed;fedora-bz-filedis kept as an audit record. The close timestamp is persisted ashum_ticket_closedfor dashboard ingestion. - Label Management: Labels are applied and updated:
upstream-fix-available/upstream-fix-in-progress(including Bodhi/DistGit evidence for Fedora-based packages)cve-needs-attention(applied when human review is needed; removed when resolved)- Legacy
fedora-fix-*labels are stripped on subsequent runs - Labels are upgraded (in-progress to available) and stale labels are cleaned up on ticket closure.
Every Jira action (label add/remove, status transition, product mismatch) includes a single comment with a bold action summary heading followed by the full analysis output in a preformatted code block, giving the reader the same detail they would see on the CLI.
10. Assignee-Based Automation Control
When --resolve is active, the tool checks each ticket’s assignee. If
the assignee is not the bot account (--jira-user), the tool skips all
automated actions (comments, labels, transitions) for that ticket. A
one-time comment is posted explaining that automation is skipped. This
allows humans to take ownership of a ticket by assigning it to
themselves, preventing the bot from interfering with manual work.
Reassigning back to the bot account re-enables automation.
For testing purposes, use --skip-assignee-check to process all tickets
regardless of assignee.
11. Human Closure Protection
When the tool encounters a Closed ticket whose analysis recommends a non-Closed resolution (e.g., In Progress), it checks the Jira changelog to determine who performed the last status transition. If a human (not the bot) closed the ticket, the tool skips the transition to respect the human’s decision. This prevents the tool from reopening tickets where a fix was backported without a version bump or where a human determined the CVE does not apply.
12. Stale Closure Detection (with --include-closed)
When --include-closed is used, the tool also analyzes Closed tickets. If
the current analysis recommends a non-Closed resolution (e.g., the package
version changed and is now affected), a warning is emitted flagging the
ticket for review.
13. Sprint and Epic Assignment
When --resolve is active, the tool automatically adds CVE tickets to the
current Hummingbird sprint and links them to a per-sprint CVE tracking epic
when human interaction is detected. This ensures sprint metrics capture
human work on CVE tickets.
The tool queries the Jira Agile API for the active sprint on the
Hummingbird board (--board-id, default 1489) and filters by name prefix
(--sprint-prefix, default “Hum S”) to identify the correct sprint among
multiple teams sharing the HUM project.
Sprint and epic assignment is triggered in two cases:
- Self-assigned tickets: When a ticket is assigned to someone other than the bot, the ticket is added to the current sprint and linked to the sprint’s CVE epic.
- Human-set Fixed in Build: When the Fixed in Build field was set by a human (not the bot), as determined by the issue changelog, the ticket is added to the current sprint.
If no matching epic exists for the current sprint, one is created automatically with the summary “CVE tickets for sprint <name>”. The epic key is cached for the duration of the run. A Jira comment is posted on the ticket recording the sprint and epic assignment.
Tickets that already have a sprint assigned are skipped. Tickets closed manually without going through either automated path should be added to the sprint by hand (see the manual process guide).
14. Continuous Operation
When --time-between-runs N is set (N > 0), the tool runs in a loop,
re-executing the full analysis every N minutes. The default is 0 (single
run). Shutdown is graceful: SIGINT/SIGTERM finishes the current cycle
before exiting.
15. Feature Flag
When --feature-flag-name is set (default: cve_analysis_enabled), the
tool checks a GitLab feature flag on the project specified
by --feature-flag-project-id (default: 73447720, i.e. redhat/hummingbird/rpms) at
the start of each cycle. If the flag is inactive, the cycle is skipped
and the tool sleeps for 60 seconds before checking again.
The check uses the --gitlab-token / GITLAB_TOKEN credential, which
must have Developer role or higher on the target project. The check is
fail-open: if GitLab is unreachable or the token lacks permissions, the
tool assumes the flag is enabled and proceeds.
Set --feature-flag-name "" to disable the check entirely.
See Also
- CVE Analysis Reference — Package map, vendored dependency detection, prerequisites, usage, configuration, managed labels, output, logs, library layout, development, license