# Hummingbird CVE agent

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

---

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

```mermaid
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](hummingbird-cve-analysis.md#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](hummingbird-cve-analysis.md#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

```bash
cd hummingbird-cve-agent
pip install -e ../hummingbird-agent -e ../hummingbird-cve-analysis -e ".[dev]"
```

## Usage

```bash
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:

```bash
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][readme] for development workflows.

```bash
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][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
