# Cve Remediation

> Scan dependency manifests against known CVEs and remediate by upgrading vulnerable dependencies to patched versions, then rebuild and re-scan to confirm. Self-contained scan→fix→verify loop for any project with a dependency manifest. Use when: a cve-remediation task is dispatched; dependency set changed (version bump, new framework); assessment flagged vulnerable or EOL dependencies; or user asked to "fix CVEs", "patch vulnerabilities", or "dependency security". Triggers: "cve", "remediate cve", "fix cves", "patch vulnerable dependencies", "vulnerability scanning", "dependency security", "vulnerable dependencies", "security advisories", "npm audit", "pnpm audit", "maven audit", "gradle audit", "dependency scan", "vulnerability remediation". NOT for: security audit of auth/input/secrets/OWASP code paths (use security-review).

- Skill: `microsoft/cve-remediation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add microsoft/cve-remediation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/microsoft/cve-remediation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: Microsoft (https://skillmd.com/u/microsoft)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/microsoft/cve-remediation

---


# CVE Remediation

This skill is the implementer-owned scan→fix→verify loop for dependency CVEs. It is
dispatched as an **execute-phase** task to an implementer role (backend), runs
**per group**, and produces patched dependency manifests plus an audit trail.

## Ownership & boundaries

- **This is implementer work, not audit work.** The `security` role audits and escalates
  but does NOT fix. This skill performs the *fix* (editing dependency manifests), so it is
  owned by the implementer who owns those manifests — not by the `security` role.
- **The rebuild/re-scan is a self-check, not a quality gate.** It is the implementer
  confirming their own change took — analogous to compiling after editing code. It must
  NOT masquerade as the project's security gate, and it stays within the **Implementation**
  phase label. The independent gates (`smoke-test`, `runtime-validation`, and the
  coordinator's verdict rules) remain separate and unchanged.
- **Scope is per-group.** Each in-scope group has its own dependency manifests; remediate
  the manifests belonging to the dispatched group.

## Scanning: tool-first with LLM fallback

CVE scanning is performed by the shared **`appmod-cve-assessment`** tool, which all
modernization surfaces (VS Code extension, IntelliJ plugin, Copilot CLI plugin, MCP server)
expose. It queries the GitHub Security Advisories API for a set of package ecosystems and
writes structured findings to a result file. Using the shared tool keeps scanning
consistent across products and emits the standard telemetry.

- **Primary — `appmod-cve-assessment` tool** (preferred whenever it applies). Consult the
  tool's own `ecosystem` parameter for the ecosystems it currently accepts — treat that
  schema as the source of truth rather than assuming a fixed list, since it may grow over
  time.
- **Fallback — LLM-only scan** when the tool path does **not** apply. This covers **two**
  cases, both signalled by the tool itself:
  1. **Tool unavailable** — `appmod-cve-assessment` is not registered in the current
     runtime (e.g. a standalone Copilot CLI / rearchitecture runtime without the tool wired
     up).
  2. **Ecosystem not accepted** — the tool rejects the project's ecosystem (its
     `ecosystem` parameter does not accept it / input validation fails). Whichever
     ecosystems the tool does not yet cover fall here automatically.

  In either case the model identifies known CVEs for the listed dependency versions from
  its own knowledge and writes the **same findings schema** (see Step 3). This is
  best-effort — model knowledge has a training cutoff and may miss recent advisories — so
  prefer the tool whenever it applies.

Map the project's package manager to the ecosystem identifier the tool accepts (for example,
Maven and Gradle both resolve to the same JVM identifier). If the tool does not accept any
identifier for the project's ecosystem, go straight to the LLM-only fallback.

## Artifact path

The coordinator provides an artifact path for this task. Throughout this skill,
`{{ARTIFACT_PATH}}` refers to that directory. Write all reports and the fix summary there.

## Workflow

### Step 1: Precheck — detect project type and build tool

Before running any build or dependency commands, verify that the required build tool is
available.

1. **Detect the project type** by examining the group's project root:

   | File(s) found | Project type | Tool ecosystem id (common mapping) |
   |---|---|---|
   | `pom.xml` | Maven (Java) | maven |
   | `build.gradle` or `build.gradle.kts` | Gradle (Java) | maven |
   | `*.sln` | .NET solution | nuget |
   | `*.csproj` | C# project | nuget |
   | `packages.config` | Legacy .NET | nuget |
   | `package.json` | Node.js (npm/pnpm/yarn) | npm |
   | any other manifest (e.g. `requirements.txt`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `Gemfile`, `composer.json`) | other | whatever the tool accepts, else LLM-only fallback (Step 2c) |

   > The ecosystem ids above are the **common** mappings at the time of writing. The tool's
   > `ecosystem` parameter is authoritative — if it accepts an identifier for the project's
   > ecosystem, use the tool; otherwise use the LLM-only fallback.

2. **Resolve the build command** — prefer project-local wrappers over global tools:

   | Project type | Check order (prefer first match) | Fallback |
   |---|---|---|
   | Maven | `./mvnw` (Unix) or `mvnw.cmd` (Windows) | `mvn` on PATH |
   | Gradle | `./gradlew` (Unix) or `gradlew.bat` (Windows) | `gradle` on PATH |
   | .NET | `dotnet --version` | — |
   | Node.js | `npm` / `pnpm` / `yarn` (only needed to *apply* fixes) | — |

3. **If the required tool is not found**, stop and report the error. Do not proceed.

### Step 2: Scan dependencies against CVE databases

The scan also serves as **detection**: a clean result (an empty findings array) means the
group has no known dependency CVEs and the task exits cheaply.

#### Step 2a: Collect dependency coordinates and locations

Extract the dependency coordinates for the group, capturing for each one the
workspace-relative file path and 1-based line number where it is declared. Prefer
**resolved** versions (which include transitive dependencies — the common source of CVEs)
and fall back to manifest-declared versions when a resolver is unavailable.

| Ecosystem | Coordinate format | Where to read |
|---|---|---|
| `maven` | `groupId:artifactId:version` | `mvn dependency:list` / `gradle dependencies` (resolved), else `pom.xml` / `build.gradle` / `gradle.properties` |
| `nuget` | `PackageName@version` | `dotnet list package` (resolved), else `*.csproj` / `Directory.Packages.props` / `packages.config` |
| `npm` | `package-name@version` (e.g. `express@4.18.2`, `@angular/core@16.0.0`) | lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` — resolved, includes transitives), else `package.json` |

> These are the common ecosystems and their coordinate formats. For the authoritative
> coordinate format expected by each ecosystem the tool accepts, consult the tool's
> `dependencies` / `ecosystem` parameter descriptions. For an ecosystem the tool does not
> accept (LLM-only fallback), use that ecosystem's natural coordinate form (e.g.
> `package==version` for Python) read from its manifest or lockfile.

#### Step 2b: Scan with the `appmod-cve-assessment` tool (primary)

Call `appmod-cve-assessment` with:

- `cveResultFilePath` — an **absolute** path of `{{ARTIFACT_PATH}}/cve-report-N.json`
  (start at `1`, increment each scan to preserve history).
- `ecosystem` — the identifier the tool accepts for the project's package ecosystem (see
  the tool's `ecosystem` parameter for the accepted values).
- `dependencies` — the coordinate array from Step 2a.
- `dependencyLocations` — `{ coordinate, filePath, lineNumber }` for each dependency.

The tool fetches CVEs, writes the findings JSON to `cveResultFilePath`, and returns a
summary message. For very large dependency sets, scan in batches, writing each batch to its
own numbered report. **If the tool is unavailable, or it rejects the `ecosystem` as not
accepted, switch to the LLM-only fallback (Step 2c).**

#### Step 2c: LLM-only scan (fallback — tool unavailable OR ecosystem not accepted)

Use this path when the tool cannot do the scan — either because `appmod-cve-assessment` is
**not available** in the current runtime, or because the tool **does not accept** the
project's ecosystem (its `ecosystem` parameter rejects it). Perform the scan from model
knowledge instead: for each coordinate + version, identify known CVEs and write the **same
findings schema** (see Step 3) to `{{ARTIFACT_PATH}}/cve-report-N.json`. Note in the fix
summary that the LLM fallback was used (and why), since it is best-effort and may miss
recent advisories.

**Large projects**: if scanning is slow, run it in a **background (async) terminal** and,
while it runs, review and fix vulnerabilities already known from earlier runs. Address any
newly discovered issues once it completes.

### Step 3: Review the report and identify vulnerable dependencies

Parse the latest `cve-report-N.json` and examine each finding:

1. Read the report file and parse the JSON (an array of findings; `[]` means no CVEs).
2. Group by `severity`: **critical** > **high** > **medium** > **low**.
3. For each finding note: the affected dependency + current version and the upgrade target
   — both are in the `evidence.explanation` (the `Affected dependencies` and
   `Recommended fix` lines) — plus the CVE identifier (`id`) and `name`.
4. Prioritize critical and high severity for immediate remediation.

Report schema (one object per CVE):
```json
[
  {
    "id": "CVE-2022-22965",
    "name": "Spring Framework RCE via Data Binding on JDK 9+",
    "status": "FOUND",
    "category": "CVE",
    "severity": "critical",
    "storyPoint": 1,
    "evidence": {
      "files": ["pom.xml:20"],
      "explanation": "[CVE-2022-22965](https://github.com/advisories/GHSA-36p3-wjmg-h94x): Spring Framework RCE via Data Binding on JDK 9+\n\nSeverity: CRITICAL\n\nAffected dependencies:\n  - org.springframework:spring-core@5.3.9\n\nRecommended fix:\n  - Upgrade org.springframework:spring-core to 5.3.18 or later"
    }
  }
]
```

> A finding whose `explanation` contains **no** `Recommended fix` line has no upstream patch
> available (unfixable). An empty array (`[]`) means no known CVEs were found.

### Step 4: Update vulnerable dependencies to secure versions

For each vulnerable dependency, update to a secure version (the upgrade target from the
`Recommended fix` line) using the appropriate method.

**Fix principles** (consistent with the security agent):
- **Direct upgrade, not a framework upgrade.** Bump the affected dependency **directly** to
  the patched version — no stepping through intermediate versions. CVE remediation is a
  targeted patch, not a version migration.
- **Respect a pinned target version.** If the user's request pins a target version/line for
  a dependency (e.g. "upgrade Spring Framework to 6.2.18"), remediate **within** it — a
  patch bump within that line to clear a CVE is fine. Only when no in-range patch exists do
  you leave it — record it as a follow-up (Step 7.3) rather than forcing a major jump.
- **Minimal changes.** Change only what is needed to clear the CVE. Do not refactor,
  reformat, or make unrelated edits.
- **Batch related fixes.** When a single upgrade clears several CVEs (e.g. a shared
  BOM/parent), apply it once for all of them.

#### Java — Maven (pom.xml)
1. Find the version (may be in `<properties>`, `<dependencyManagement>`, or inline `<version>`).
2. Update to the patched version (or the latest stable if the patched version is also outdated).
3. If the version is inherited from a parent POM (e.g. Spring Boot starter parent), update the parent version instead.

> **Where CVEs hide — check BOM overrides first.** Pay special attention to dependencies
> that explicitly declare a `<version>` tag *overriding* a managed BOM (e.g. the Spring Boot
> dependencies BOM). These inline overrides bypass BOM management and are the most common
> source of missed CVE vulnerabilities — bumping the BOM/parent version alone will not patch
> them. Cross-check the `<version>` tags in each sub-module's `pom.xml` against the
> vulnerable dependencies and update the override (or remove it to fall back to the managed
> version) as needed.

#### Java — Gradle (build.gradle)
1. Find the version in `build.gradle` or `gradle.properties`.
2. Update the version string to the patched version.
3. If using a BOM or platform dependency, update the BOM version.

#### .NET (csproj)
1. Find `<PackageReference Include="PackageName" Version="X.Y.Z" />`.
2. Update the `Version` attribute to the patched version.
3. If versions are managed centrally via `Directory.Packages.props`, update them there instead.
4. Alternatively: `dotnet add package PackageName --version X.Y.Z`.

#### Node.js — npm / pnpm / yarn (package.json)
1. Find the version in `package.json` under `dependencies` or `devDependencies`.
2. Update the range to the patched version (e.g. `"^4.17.21"`), then **regenerate the
   lockfile** so the resolved transitive versions update too:
   - npm: `npm install pkg@X.Y.Z` (or edit `package.json` then `npm install`)
   - pnpm: `pnpm add pkg@X.Y.Z` (or edit then `pnpm install`)
   - yarn: `yarn add pkg@X.Y.Z` (or edit then `yarn install`)
3. **Transitive dependencies** (the common case for npm CVEs) are pulled in by other
   packages and have no direct entry in `package.json`. Force a patched version with an
   override instead of a direct edit:
   - npm: add an `"overrides"` block (`{ "overrides": { "pkg": "X.Y.Z" } }`)
   - pnpm: add `"pnpm": { "overrides": { "pkg": "X.Y.Z" } }`
   - yarn (Berry): add a `"resolutions"` block
   Then re-run the install command to regenerate the lockfile.

### Step 5: Re-scan to confirm issues are resolved (self-check)

Re-run the Step 2 scan and write the output with the next sequential number. Then compare
the new report against the previous one and **exit the loop** when ANY of these hold:

- **Clean** — the report is an empty array (`[]`). Success.
- **Only unfixable CVEs remain** — every remaining finding has no `Recommended fix` line in
  its `explanation` (no upstream patch available). Success; record these in the summary as
  accepted/unfixable.
- **No progress** — after a fix attempt the **same** fixable CVEs persist — compare by CVE
  **id**, not by raw count. A fix that resolves the targeted CVE but surfaces a *different,
  newly-disclosed* CVE is **progress, not a stall**: bump to the highest recommended
  **released** version and keep going. Treat it as no-progress (and stop) only when the same
  CVE id keeps reappearing despite a fix, **or** when the only way forward is a recommended
  version that is **not yet released** (no installable artifact) — record those as
  accepted/stuck in the summary. Stop here rather than looping forever.

> **Fixable vs. unfixable — important.** A CVE is *unfixable* **only** when it has no
> `Recommended fix` line (no patched version exists upstream). A CVE that requires a
> **major-version upgrade** still has a patched version, so it is **fixable** — leaving it
> means remediation is **incomplete** (the *No progress* / deferred path, surfaced as a
> Step 7 follow-up), **not** the *Only unfixable* success. Treat the run as fully successful
> only when **no fixable CVEs remain**. This matches the security agent's rule: do not claim
> success while patchable CVEs are still outstanding.

Otherwise — if the count dropped but fixable CVEs (those with a `Recommended fix`) still
remain — return to Step 4, fix the newly reported CVEs, and re-scan, incrementing the report
number each time. This loop is the implementer's own confirmation — not a separate gate.

### Step 6: Build and test after updates

Use the build command resolved in Step 1.

```shell
./mvnw clean verify     # Maven
./gradlew clean build   # Gradle
dotnet build && dotnet test   # .NET
npm install && npm test       # Node.js (npm) — use pnpm/yarn equivalents as appropriate
```

Verify the build completes, existing tests pass, and the app starts (if applicable). If the
build fails due to breaking API changes from an upgrade, apply the necessary code fixes and
re-run the build. **Cap this at 3 fix attempts** — if the build still fails, stop, keep the
dependency changes that scanned clean, and document the build issue in the summary (Step 7)
rather than looping. Keep this within the Implementation phase — it is a build recheck, not
a new phase.

### Step 7: Document the changes

1. Write a summary of CVEs fixed (CVE/GHSA ID, dependency + version, patched version,
   severity, brief description) to `{{ARTIFACT_PATH}}/cve-fix-summary.md`. Note whether the
   scan used the `appmod-cve-assessment` tool or the LLM-only fallback.
2. Run a final scan and write it as the final report
   (`{{ARTIFACT_PATH}}/final-cve-report.json`). All intermediate numbered reports are
   preserved for audit history.
3. If any fix requires a **major** version upgrade (breaking-change risk), record it as a
   follow-up item in the summary rather than forcing it silently — and surface it to the
   coordinator so dependent tasks and reviews are aware.

---

## Environment Setup

### Prerequisites

1. **Build tool** (one of): `mvn` / `mvnw`, `gradle` / `gradlew`, or `dotnet` — to apply
   fixes and rebuild.
2. **Node.js package manager** (`npm` / `pnpm` / `yarn`) — only needed to apply Node.js fixes.
3. **GitHub token** (optional, recommended for the tool path — raises the GitHub Security
   Advisories rate limit from 60 to 5000 req/hr): export `GITHUB_TOKEN` (or `GITHUB_PAT`).
   The `appmod-cve-assessment` tool reads it from the environment when present.

---

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `appmod-cve-assessment` not available | Tool not registered in the current runtime | Use the **LLM-only fallback** (Step 2c) |
| Tool rejects the `ecosystem` | The tool does not accept this project's ecosystem | Use the **LLM-only fallback** (Step 2c) |
| Rate limit / HTTP 403 from the tool | Too many advisory API calls without auth | Set `GITHUB_TOKEN` / `GITHUB_PAT` in the environment |
| Empty / malformed coordinate rejected | Wrong coordinate format | maven: `groupId:artifactId:version`; nuget: `PackageName@version`; npm: `package-name@version` |
| `Maven/Gradle not found` | Build tool not in PATH | Install or ensure the wrapper (`mvnw`/`gradlew`) exists |
| `dotnet CLI not found` | .NET SDK not installed | Install .NET SDK |
| `No supported project files found` | Unrecognized project type | Ensure the root has pom.xml, build.gradle, *.sln, *.csproj, packages.config, or package.json |

---

## Troubleshooting

### List dependencies manually (to build the coordinate list for Step 2a)

```bash
# Maven
mvn dependency:list -DoutputFile=deps.txt -q
grep -E "^   [a-zA-Z]" deps.txt | sed 's/^   //' | awk -F: '{print $1":"$2":"$4}' > coordinates.txt

# Gradle
./gradlew dependencies --configuration compileClasspath > deps.txt

# .NET
dotnet list package > deps.txt

# Node.js (npm) — from package-lock.json (lockfileVersion 2/3)
jq -r '.packages | to_entries[] | select(.key|startswith("node_modules/")) | select(.value.version) | "\(.key|sub(".*node_modules/";""))@\(.value.version)"' package-lock.json | sort -u > coordinates.txt
```

Feed the resulting coordinates (with their file/line locations) into the
`appmod-cve-assessment` tool, or — when the tool is unavailable — into the LLM-only
fallback scan.

### Rate limit errors

Set a GitHub token in the environment so the tool authenticates its advisory API calls:
```bash
export GITHUB_TOKEN=$(gh auth token)
```

