---
name: cve-remediator-v2
description: >-
  Raise Go modules to caller-supplied minimum fixed versions from CVE/GO
  findings in any format, per tracked module root, sync go.mod/go.sum and root
  vendor/, audit the source module graphs, run the given checks, and publish a
  PR, all enforced by one script. Use when the user gives vulnerability findings
  (a table, list, prose or records) naming the affected component or
  dependency, the CVE/GO ID and the fixed version, and wants source-only Go
  dependency bumps without building or scanning images.
---

# CVE Remediator v2 (source-only)

`scripts/cve_remediator_v2.py` generates, audits and validates the change and,
with `--publish`, commits, pushes, and opens or updates the PR in one run. It
enforces the scope, source identity, branch, PR and readback rules itself. It
never fetches advisories, scans images, installs tools, rolls back or retries.
Use `fix-image-cves` for images. Replace `<SKILL_DIR>` with this skill directory.

## Your decisions

- **Scope:** keep only findings that apply to the chosen checkout's Linux
  components and releases, whatever the input layout. Never infer branches from
  image tags. If the scope is unclear or nothing applies, ask.
- **Mode:** ordinary remediation uses `--publish`. Use `--local-only` or
  `--dry-run` only for local-only, no-push, read-only or preview requests.
- **Checks:** real build and unit test commands for the affected roots that
  actually rebuild (for example `make -B`). Never all-root `go test` or live e2e.
- **Gaps:** only for checks the caller accepted as not run, never for a failed
  check. Reasons are published in the PR; keep them truthful and public-safe.
- **Licenses:** ordinary remediation includes needed `LICENSES/` snapshots. A
  refresh is needed when planned bumps add vendored module paths or change
  license files not covered by existing snapshots; then set `licenses.needed`
  to true with `licenses.refresh` argv `["python3",
  "<SKILL_DIR>/scripts/license_refresh_tidy.py", "--", "make",
  "update-vendor-licenses"]` and cwd `"."`. The repo command downloads
  upstream Kubernetes scripts, so it needs network. The wrapper then removes
  only the `go.mod` checksum records the generator adds to root `go.sum`, and
  fails without tidying on any other change outside `LICENSES/`.
  If the user excluded licenses or network, or the command is unavailable, ask
  before generating; use a `licenses` gap only if the user accepts one. Never
  refresh for preview or read-only requests.
- **PR:** explicit targets, and a truthful public title and body based on the
  target checkout's `.github/PULL_REQUEST_TEMPLATE.md` and
  [pull request guidance](../../references/pull-requests.md). The runtime adds
  the release tag to the title and writes the description and reviewer notes
  (see Inputs).

## Inputs

The F, V, P and body files live outside the checkout, and the checkout must be
clean. The selected Go (`--go`) runs generation and the sync, and its directory
is first on `PATH`, so a bare `go` in checks uses it. For `--local-only` and
`--publish` it must be an executable named `go` (a symlink is fine), new enough
for every tracked `go` directive and every target. `--dry-run` accepts any name.

Findings (F) is JSON you prepare from the user's findings; the user need not
supply it. Read the input by meaning, not by layout or column names. Each
finding should give a vulnerability ID, what is affected, and a fixed version.

- Write one row per vulnerability, affected Go dependency and affected tracked
  root. CCM and CNM share the root module `.`; HPP is `health-probe-proxy`.
  Confirm that any other component maps to a tracked root, or ask. Keep each
  row's own fixed version, and keep different dependencies that share an ID as
  separate rows.
- `package` is the affected Go dependency, never the product. A label such as
  "component" may name either; decide by meaning. If the dependency is not
  given, identify it from the supplied details, the checkout or a reliable
  reference, and ask if it is still unclear. Never guess it from the ID or
  version. Set `module` only when the exact module path is known.
- `fixed` is the supplied fixed version or comma-separated list, unchanged;
  never choose among candidates yourself. Use `N/A` only when the finding says
  there is no fixed version; if it is missing or unclear, ask.
- `id` is an opaque label. `installed` is the scanned version. It is required
  (exact semver) when `fixed` lists several versions; otherwise provenance
  only, `""` if not given.

The helper performs no CVE or advisory lookups. A single `fixed` version is
the row's floor. For a list, it picks the lowest version newer than
`installed`. Per root and module the highest row floor wins and is checked
against the resolved checkout. A list blocks if `installed` is missing or
invalid or no listed version is newer. Every listed version must be an exact
Go semver of the module's major version.

```json
{"findings": [
  {"id": "CVE-2026-81870", "package": "go.opentelemetry.io/otel/sdk", "installed": "v1.44.0", "fixed": "1.45.0"},
  {"id": "GO-2026-5932", "package": "golang.org/x/crypto", "installed": "", "fixed": "N/A"}
]}
```

`module_root` defaults to `.`; set it for any other root.

Validation (V):

```json
{"checks": [
  {"name": "ccm-build", "group": "build", "argv": ["make", "-B", "bin/azure-cloud-controller-manager"], "cwd": "."},
  {"name": "root-unit", "group": "unit", "argv": ["make", "test-unit"], "cwd": "."}],
 "gaps": [],
 "licenses": {"needed": false, "refresh": null}}
```

`build` and `unit` each need at least one check, or a gap instead
(`{"group": "unit", "reason": "..."}`), never both. If a refresh is needed,
give `"refresh": {"argv": [...], "cwd": "."}` or a `licenses` gap. A refresh
may change only `LICENSES/`.

Publish (P):

```json
{"host": "github.com", "base_repo": "kubernetes-sigs/cloud-provider-azure", "base_remote": "upstream",
 "base": "master", "head_repo": "<you>/cloud-provider-azure", "remote": "origin",
 "head": "cve-fix-otel", "title": "chore: bump otel for CVE-2026-81870", "body_file": "/tmp/pr-body.md"}
```

The head must be a dedicated branch, and remote URLs must match exactly, with
no ports. A fork head must be a personal, direct fork of the base with the same
repository name.

Give `title` without a release tag. For a `release-X.Y` base the runtime
publishes `[release-X.Y] <title>`, for example
`[release-1.33] chore: bump otel for CVE-2026-81870`; a leading tag that already
names the base is not repeated. A leading `[release-...]` tag that does not name
the base branch, a repeated tag, or a tag-only title blocks the run.

The body file must keep each `####` heading of the checkout's
`.github/PULL_REQUEST_TEMPLATE.md` exactly once; without that template the run
cannot publish. Fill the kind, issue, release-note and other required sections.
The runtime replaces everything under "What this PR does / why we need it:"
with a fixed intro and one line per proved fix (see Report), for example:

```markdown
Raises Go dependencies in the source module graphs to at least the reported fixed versions:
- CVE-2026-81870: go.opentelemetry.io/otel/sdk v1.45.0
```

A fix in a root other than `.` ends with the root, such as
`(health-probe-proxy)`. "Special notes for your reviewer:" gets only the
`Unresolved:` (residual and pruned rows) and `Not run (accepted):` (gap
groups and reasons) lists that apply, or stays empty. Check names, argv,
logs and other diagnostics stay in the local report.

## Run

```bash
python3 <SKILL_DIR>/scripts/cve_remediator_v2.py --repo <checkout> --findings F.json [--go <go>] --dry-run
python3 <SKILL_DIR>/scripts/cve_remediator_v2.py --repo <checkout> --findings F.json [--go <go>] --validation V.json --local-only
python3 <SKILL_DIR>/scripts/cve_remediator_v2.py --repo <checkout> --findings F.json [--go <go>] --validation V.json --publish P.json --gh <absolute-gh>
```

## Report

- **Status and exit code:**
  - exit 0: `dry-run`, `validated`, `no-change` or `published`
  - exit 2: `blocked`, before any source change (ref fetches and Go caches may
    still have changed)
  - exit 1: `failed`; `stage` and `remaining_state` say where it failed and
    what was left behind
  - Nothing is rolled back. A rerun on a clean tree repeats everything.
- **Rows:** `raised` and `satisfied` mean the floor is met. `residual` and
  `pruned` stay unresolved.
- **PR:** a new PR is ready for review; an existing PR keeps its draft or ready
  state and is never converted. A known PR
  appears in `publication.pr` as soon as it is found or created, and
  `verified` becomes true only after readback passes; readback also checks the
  exact title, and the body ignoring CRLF and trailing whitespace.
  `publication.title` and `publication.fixes` record the published title and
  the proved fixes.
- **Proved fixes:** a `raised` or `satisfied` row is listed only if its own
  floor is newly met: its baseline is below the floor and `selected_after`
  meets it. A row already met at the baseline is not listed, even when another
  row's higher floor lifted the module. The baseline is `selected_before`; when
  the checkout already has PR-range commits (for example on a rerun), it is the
  merge-base graphs, which the selected Go lists from copies of the merge
  base's committed `go.mod` and `go.sum` files outside the checkout (Go may
  download module metadata into its cache). A missing root or module, or a
  replaced module, proves nothing.
- **Refusals:** title and body-file problems block (exit 2) before generation.
  The run fails at stage `range`, before any commit or push and keeping the
  generated changes, when no fix is proved or the merge-base graphs cannot be
  listed safely: for example an untidy `go.mod`, a `go.mod` or `go.sum` that is
  not a regular file, or a directory replacement outside the copied layout. A
  genuine `no-change` run still exits 0.
- **What to claim:** each selected minimum is met, or the module is no longer
  selected, in the resolved source module graphs, and the listed checks passed.
  Never claim CVEs are cleared or images or deployments verified.
