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_analysis output and adds investigation only where deterministic analysis already gave up.

  • AI behind one seam - all LLM calls go through a small, swappable ai interface. The only backend today reuses the hummingbird-agent model 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 status is a Verdict from the shared lifecycle vocabulary (hummingbird_cve_analysis/lib/states.py), so the agent and cve_analysis speak 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_actions without 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 example 00003-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.
    • Release gets a trailing micro bump rather than an increment of the base release, which stays owned by rebases: 4 becomes 4.1, 4.1 becomes 4.2, and 16.p2%{?dist} becomes 16.p2.1%{?dist}. Date-shaped releases are refused and left for human review.
  • 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 %prep scriptlet, 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_GLOBAL cannot 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 *.patch and 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.