Hummingbird CVE agent
CLI that investigates cve-needs-attention Jira tickets, using AI for
the tickets that cve_analysis.py could not resolve. Later integrated with
a cve work-item type in workqueue-service.
Diagram
flowchart TB
TICKET["HUM ticket key"] --> CLI
subgraph AGENT["CVE Agent"]
direction TB
CLI["CLI: run HUM-XXXX"]
COL["Collect + validate<br/>Jira · Pulp · SBOM"]
CAND{"Backport candidate?"}
RESULT["InvestigationResult"]
subgraph SANDBOX["Ephemeral rpms sandbox"]
BACKPORT["Secret-free sparse clone<br/>fetch patches · generate file_actions"]
end
AIINV["AI investigation"]
AIADAPT["AI patch adaptation<br/>fresh sandbox"]
CLI --> COL --> CAND
CAND -->|yes| BACKPORT
CAND -->|no| AIINV
BACKPORT -->|needs_change| RESULT
BACKPORT -->|"unsupported · needs_human"| RESULT
AIINV -->|"Verdict / file_actions"| RESULT
end
subgraph QUEUE["workqueue-service"]
direction TB
CVEITEM["cve work item"]
COMMENT["Jira comment"]
MR["mr work item<br/>Draft RPM MR"]
KONFLUX["Konflux build"]
CVEITEM --> COMMENT
CVEITEM --> MR --> KONFLUX
end
RESULT --> CVEITEM
KONFLUX -.->|failed pipeline webhook| AIADAPT
AIADAPT -->|adapted file_actions| MR
classDef agent fill:#dbeafe,stroke:#2563eb,color:#111827
classDef sandbox fill:#dcfce7,stroke:#16a34a,color:#111827
classDef external fill:#f3f4f6,stroke:#6b7280,color:#111827
class CLI,COL,CAND,RESULT,AIINV,AIADAPT agent
class BACKPORT sandbox
class TICKET,CVEITEM,COMMENT,MR,KONFLUX external
The diagram shows the agent as a whole. Today it runs as a standalone CLI that
prints the result and makes no remote writes; running it in production as the
cve work-item type behind workqueue-service, together with the AI paths and
the Jira/MR hand-off, is planned. Infrastructure errors propagate; only
unsupported or ambiguous deterministic inputs become needs_human. The
ticket-level state machine lives in the
CVE Lifecycle & State Vocabulary.
Features
-
Scoped to needs-attention - starts from
cve_analysisoutput and adds investigation only where deterministic analysis already gave up. -
AI behind one seam - all LLM calls go through a small, swappable
aiinterface. The only backend today reuses thehummingbird-agentmodel adapter, so its token/cost metrics feed the shared GCP AI-costs dashboard. -
Structured results - every investigation produces a validated, versioned result contract. The result
statusis aVerdictfrom the shared lifecycle vocabulary (hummingbird_cve_analysis/lib/states.py), so the agent andcve_analysisspeak the same state language. See the CVE Lifecycle & State Vocabulary for the full state machine. -
Deterministic backports - for an unambiguous single-CVE ticket whose analysis names upstream commits, the agent fetches those commits as patches and mechanically emits MR-compatible patch and spec
file_actionswithout an AI call. PR-only analysis is resolved to its commits first, in apply order (GitHub returns PR commits oldest-first, GitLab newest-first). Unsupported or ambiguous RPM layouts are left for human review. Two rpms-repo conventions are applied:- Patch files are named
<NNNNN>-<cve-id>-<slug>.patch, where the slug is up to five words taken from the Jira summary’s description (for example00003-cve-2026-82474-policy-bypass-allows-unauthorized-program.patch). When one CVE needs several commits, the patch number keeps the names unique. Tickets with no usable summary fall back to the short commit ID. Releasegets a trailing micro bump rather than an increment of the base release, which stays owned by rebases:4becomes4.1,4.1becomes4.2, and16.p2%{?dist}becomes16.p2.1%{?dist}. Date-shaped releases are refused and left for human review.
- Patch files are named
-
Ephemeral rpms sandbox - fixes are prepared in a throwaway, uncredentialed clone of the rpms repo: read the package’s code, fetch an upstream commit as a patch, and stage it into the package for an MR. The clone is removed on exit and never holds a push token. It runs no untrusted code; git calls use a minimal, secret-free environment with credential helpers, external config, and submodule recursion disabled. Patch application and package build verification are left to Konflux. HUM-7513 will consume failed-pipeline webhooks and invoke AI to adapt patches that do not apply or build cleanly.
Security model
The agent processes untrusted input — Jira ticket content, upstream commits, and package spec/patch files — so the sandbox is built so that no untrusted code is ever executed and untrusted input cannot reach secrets or a shared host.
- No untrusted code execution. The sandbox only clones the rpms repo, reads
files, resolves upstream PRs, downloads patches, and stages them into the
package. It never runs
rpmbuild, a spec’s%prepscriptlet, or any code supplied by a spec or patch. Verifying the fix by building the package — the step that would execute spec-controlled code — is deliberately delegated to Konflux, which builds the resulting MR in its own hardened pipeline. This keeps a whole class of remote-code-execution surface (and slow, resource-heavy builds of large packages) out of the agent entirely. - Deployment isolation. The agent runs as a container / Kubernetes Job. The rpms fork lives in a private temp dir inside that boundary and is removed on exit, so git operations run inside the deployment sandbox rather than on a shared host — no separate nested container is needed, because nothing here executes untrusted code.
- Secret-free subprocesses. git runs with a minimal, allowlisted environment. The service process env (Jira and model tokens) is never inherited by child processes, so a subprocess cannot read or exfiltrate them.
- Uncredentialed, hardened git. The fork is a public, read-only clone with
no push token. Credential helpers and interactive prompts are disabled,
system/global git config is neutralized (an attacker-controlled
GIT_CONFIG_GLOBALcannot change behaviour), and submodule recursion is off. - Input validation. Package names are restricted to a safe charset (no path
traversal or absolute paths), file reads are confined to the package
directory, saved patch names must be
*.patchand cannot overwrite existing files, and patch URLs must use HTTPS on a recognized upstream forge.
Prerequisites
- Python 3.11+
- Jira API credentials
Installation
cd hummingbird-cve-agent
pip install -e ../hummingbird-agent -e ../hummingbird-cve-analysis -e ".[dev]"
Usage
hummingbird-cve-agent run HUM-1234
This gathers facts for the ticket, runs the investigation, and prints the
result as JSON. Eligible deterministic backports include base64-encoded
file_actions and a commit_message ready for an mr work item. The command
performs no remote writes.
Container
Build from the tools repository root so the image can include the shared
hummingbird-agent and hummingbird-cve-analysis packages:
podman build -f hummingbird-cve-agent/Containerfile \
-t hummingbird-cve-agent .
podman run --rm \
-e JIRA_TOKEN \
-e JIRA_USER \
hummingbird-cve-agent run HUM-1234
Configuration
Operational settings come from the YAML config file; secrets and model credentials come from the environment.
| Variable | Purpose |
|---|---|
HUMMINGBIRD_CVE_AGENT_CONFIG |
Path to a YAML config file (optional) |
JIRA_TOKEN |
Jira API token |
JIRA_USER |
Jira basic-auth user (optional) |
GITHUB_TOKEN |
GitHub token for PR commits (optional) |
GITLAB_TOKEN |
GitLab.com token for MR commits (optional) |
GOOGLE_API_KEY |
Gemini API key (direct API mode) |
GOOGLE_CLOUD_PROJECT |
GCP project for Vertex AI mode |
YAML settings:
| Key | Default | Purpose |
|---|---|---|
jira_url |
Red Hat Jira | Jira base URL |
ai_backend |
agent |
AI backend selector (swappable seam) |
model |
gemini-3.1-pro-preview |
Model name passed to build_model |
model_regions |
{} |
Model-name prefix to Vertex region |
Development
See the main README for development workflows.
make check # lint
make test # run tests
License
This project is licensed under the GNU General Public License v3.0 or later - see the LICENSE file for details.