Remediation

Fixing dependencies

Upgrade the packages a CVE names — direct and transitive — with the package manager you already use, verified by re-detecting the vulnerability afterwards.

Fixing dependencies

A dependency CVE has a known answer: move to the fixed version. The work is figuring out how — whether the package is one you declared or something four levels down, which of your project's package managers owns it, and whether the upgrade will hold.

xgrep fix does that part.

xgrep scan .               # find the vulnerable packages
xgrep fix verify           # see the upgrade plan — offline, writes nothing
xgrep fix apply --online   # do it

The fixed version comes from the advisory; xgrep never invents one. Which package manager to use, and whether the package is direct or transitive, come from an offline bill of materials of your project.

Look before you leap

fix verify is offline and never writes. For each vulnerable package it shows the commands it would run, any manifest edit they need first, and how risky the change is. That is usually the thing to read first — it tells you whether this is a one-line version bump or something that will move your lockfile around.

fix apply needs --online, because actually upgrading means running your package manager, which needs the network and the toolchain. Without it, dependency findings come back marked as needing it rather than being silently skipped.

Either way, the fix output names the exact package-manager command: fix verify reports what xgrep would run (will run: npm install lodash@4.17.21), fix apply reports what it ran (ran: npm install lodash@4.17.21), and — when the tool is missing — the command to run by hand. It is always the real command xgrep executes, never a paraphrase, so what you read is what runs.

If the package manager itself isn't installed, that isn't treated as a failure. xgrep already knows the exact command to run — it just can't run it for you — so the finding comes back as a manual step with that command spelled out (npm not found on PATH — run manually: npm install lodash@4.17.21), the other upgrades still run, and fix doesn't fail the run just because a tool is absent. A command that actually runs and fails is still a real failure.

Nothing is left half-done

Before running anything, xgrep snapshots every file the plan could touch. If a command fails, or the vulnerability is still there afterwards, everything is rolled back to where it started.

Success is decided by re-detecting the vulnerability, not by the package manager's exit code — a command that succeeds while leaving the vulnerable version in the tree is not a fix.

The upgrade commands are exercised against the real package managers in a small set of container fixtures (a representative pip/npm/go subset, plus a tool-absent image for the manual-command path), so what xgrep tells you to run is what actually moves the package.

Direct, transitive, and the awkward ones

A dependency you declared is a targeted upgrade — go get pkg@1.2.3, npm install pkg@1.2.3, bundle update pkg, cargo update -p pkg — inside the constraints your manifest already states. Low risk.

A transitive dependency is upgraded through the resolver, where the manager has one that can reach it: Go, Cargo, Bundler, Composer, Mix, SwiftPM, pub and the Python lockers can all be pointed at a package they did not directly declare, with no manifest edit.

Where the resolver won't cooperate, the fix is an override written into your manifest — npm and bun overrides, pnpm.overrides, yarn resolutions, Maven dependencyManagement, Gradle constraints, a NuGet direct pin. That forces a version the resolver would not have picked, which can break a peer or parent constraint, so it is reported as the higher-risk change it is, with a line on what it affects. Decide it with your eyes open.

xgrep edits manifests, never lockfiles: a lockfile is regenerated by the manager that owns it.

Package managers supported: Go, npm, pnpm, yarn (classic and berry), bun, Deno, pip, pipenv, poetry, uv, pdm, Cargo, Bundler, Composer, Maven, Gradle, NuGet, pub, Mix, SwiftPM, rebar3 (Erlang), CocoaPods, conda, renv (R), opam (OCaml), Terraform, Conan, vcpkg, Julia, Cabal and Stack (Haskell) — every source ecosystem xgrep inventories.

Which package manager — and overriding it

xgrep picks the package manager from the file that anchors the finding: a yarn.lock means yarn, a poetry.lock means poetry, and so on. When a project carries only a package.json with no lockfile, its packageManager field (the Corepack declaration, e.g. "yarn@4") decides — so a yarn or pnpm project is not mistaken for npm.

When detection guesses wrong, or a monorepo uses a manager the anchoring file does not imply, force it per ecosystem with --package-manager:

xgrep scan --package-manager npm=yarn .          # plan JavaScript fixes with yarn
xgrep fix verify --package-manager python=poetry  # …and Python fixes with poetry

The form is ecosystem=manager, repeatable or comma-separated (--package-manager npm=yarn,python=poetry). An unknown ecosystem or an incompatible pair (npm=cargo) is rejected up front. The override wins over both the Corepack declaration and the lockfile.

For an ecosystem that has no deterministic single-package upgrade command — Conan, vcpkg, Julia, Cabal and Stack — the plan is a manifest edit with a note (pin the constraint; the resolver applies it) rather than a command, because inventing a command there could be wrong. The only findings reported with no plan at all are those anchored in a read-only artifact (a jar, wheel, gem, .nupkg, or a native binary): there is no project tree to edit, so upgrade the source project instead.

When the advisory is vague

By default only confident upgrades are applied — the advisory named the fixed version. --confirm-deps opts in to the rest: a version parsed out of advisory prose, or an update-to-latest when no fixed version is stated anywhere. That is a deliberate second signature, not a default, because those upgrades are a guess at a target rather than a stated one.

Fix what matters first

Upgrading every flagged package is how dependency work becomes a chore nobody does. Pair this with reachability and start with the vulnerabilities your code can actually reach:

xgrep scan --reachability .   # rank by whether your code reaches the vulnerable code
xgrep fix verify              # then plan the upgrades that matter

Scope a run further with --rule, --max-severity and --min-confidence.

On this page