To fix npm vulnerabilities with the Safeguard CLI, scan the project, trace each vulnerable package to the dependency that pulls it in, then let the assistant make the smallest upgrade or override while you approve every edit and command. Finish with npm ci, your tests and a rescan. Nothing is fixed until the rescan agrees.
You can do this with the assistant you already use. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. You can also connect Claude or ChatGPT to the Safeguard MCP server and ask about your npm findings from the chat you already use.
What you will have at the end
By the end of this guide you will have:
- A clear list of npm findings for your project, each marked direct or transitive, with the version that fixes it.
- A way to trace any vulnerable package back to the dependency that brings it in.
- A fix applied with the smallest change that works: a patch inside your range, a direct bump, a parent upgrade, or an
overridesentry. - A lockfile that matches
package.json, proven bynpm ci, and passing tests. - A rescan that shows the finding gone, and a CI step that keeps it gone.
You need Node and npm, a project with a package-lock.json, and the Safeguard CLI. If the CLI is not installed yet, see installing the Safeguard CLI on macOS, or use the one line installers further down.
How npm vulnerabilities get into your project
It helps to know how the vulnerable code arrived before you choose how to remove it. Four npm facts shape every fix.
The lockfile is what ships. Your package.json holds version ranges. The package-lock.json holds the exact versions that were resolved. Scanners read what is resolved, and npm's own docs say that by default npm audit requires a package-lock or shrinkwrap file to run. If a fix changes package.json but not the lockfile, nothing that installs has changed.
Most findings are transitive. A direct dependency is one you listed. A transitive one came along with something you listed. You can bump a direct dependency yourself. A transitive one usually moves when its parent moves, or when you override it.
npm can tell you the path. npm explain (alias npm why) prints the chain of dependencies that causes a package to be installed. npm ls with a package name limits its tree to the paths to that package, and --all shows the full tree instead of only the top level.
npm explain glob
npm ls glob
npm ls --all
Ranges decide what "update" means. npm update updates packages while respecting the semver ranges of your project and its dependencies. With ^1.1.1 it can move to 1.2.2; with ~1.1.1 it stops at the latest 1.1.x. By default it does not change the ranges written in package.json; npm update --save does.
There is also a warning worth reading before anyone suggests a shortcut. npm audit fix --force lets npm install versions outside your stated ranges, including semver major changes, and npm's docs say: "If you don't have a clear idea of what you want to do, it is strongly recommended that you do not use this option!" The same caution applies when an assistant proposes it.
Choose the right kind of fix
Every npm finding falls into one of four shapes. Decide which one you have before you ask for changes, because each needs a different edit.
| Situation | What to change | Files that change | Risk |
|---|---|---|---|
| The fixed version is inside your current range | Refresh the lockfile only | package-lock.json | Low |
| Direct dependency, fixed version outside your range | Raise the range in package.json | package.json and the lockfile | Low for patch and minor, higher for major |
| Transitive, and a newer parent pulls in the fixed version | Upgrade the parent | package.json and the lockfile | Depends on the parent's change |
| Transitive, and no parent release fixes it yet | Add an overrides entry | package.json and the lockfile | The parent was not tested with that version |
The rows go from least to most risky. Prefer the highest row that clears the finding.
Step by step: fix an npm finding in the Safeguard CLI
This walkthrough uses the CLI's interactive terminal. The assistant reads your files and your Safeguard data, writes the fix, and runs commands, and each edit or command waits for your approval under the mode you pick.
1. Install the CLI, if you have not.
# macOS and Linux
curl -fsSL https://cli.safeguard.sh/install | bash
# Windows PowerShell
irm https://cli.safeguard.sh/install.ps1 | iex
It is a single static binary with no Node, Python or JVM required, for macOS, Linux and Windows on x86_64 or arm64 (Windows is x86_64 only).
2. Start from a clean branch.
cd ~/code/payments-api
git switch -c fix/npm-findings
git status
A clean tree means every change you see later came from this fix. It also matters for undo: the CLI snapshots files the assistant edits, but not files a shell command such as npm install writes. Git covers those.
3. Open the session and scan.
safeguard
/login
/scan .
/scan . builds an SBOM for the folder and matches it against vulnerability databases. The same scan from the shell is safeguard scan --dir ., with JSON results under ./output, including vuln.json. If you want a quick look that uploads nothing, safeguard findings sca --dir . runs the scanner locally and prints the findings in one normalized shape, without registering a project.
4. Ask for the findings in a useful shape.
list the npm findings from that scan, highest severity first,
with package, installed version, fixed version, and whether each one is direct or transitive
5. Trace each transitive finding. Ask the assistant to run npm explain for each transitive package. Reading the path is a read-only command, so in Auto it runs without a prompt.
for each transitive finding, run npm explain on the package and tell me which direct dependency brings it in,
and whether a newer version of that direct dependency fixes it
6. Pick the mode. Type /permissions. Auto, the default, runs read-only commands and what the policy allows, and asks about anything else. Use Ask in a repository you do not know: reads run, and every edit and command asks first. Accept edits lets edits in this folder and read-only commands run, while other commands such as npm install still ask. Keep the --yolo flag for throwaway sandboxes only.
7. Ask for the fix with limits. Type @ and attach package.json so the assistant reads the real file. Use ctrl+j for new lines.
@package.json
fix the high and critical npm findings.
use the lowest version that fixes each one.
change only package.json and package-lock.json. do not add packages.
ask me before any major version change.
for transitive findings, upgrade the parent if a release fixes it; otherwise propose an overrides entry and wait.
then run npm install and npm test.
Each line closes a door. "Lowest version" keeps the diff small. "Do not add packages" means a fix never introduces a name you did not have. "Ask before major" keeps breaking changes a deliberate choice.
8. Approve, one prompt at a time. The assistant proposes the edit, then asks before each command. Press y to allow or n to refuse, and say why when you refuse. esc interrupts a running task without leaving the terminal.
The recorded session below shows this on a sample project called payments-api, with test data. It installs with the curl command (checksum verified), runs safeguard --version, opens the interactive terminal with safeguard, and asks "fix the lodash CVE in this repo". The assistant reads the finding, files and advisory, then asks before each command (press y or n), edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and prints a summary with a suggested commit message. It pushes nothing. Then it shows /permissions, /model and /scan ..
Click through it at your own pace, or open it full screen.
9. Prove the lockfile. After npm install, run a clean install and the tests:
npm ci
npm test
npm ci needs an existing lockfile, removes node_modules first, never writes to package.json or the lockfile, and exits with an error if the two disagree. If it passes, the lockfile you are about to commit is the one that installs.
10. Review and rescan.
git diff --stat
git diff package.json
safeguard review
safeguard review reviews the working tree changes for correctness and security. For the diff, expect two files: package.json and package-lock.json. If a source file changed, ask why before you go on. Then type /scan . again. The finding is fixed when the scanner stops reporting it.
11. Commit. Start from the suggested commit message and make sure it names each package with its old and new version, so a reviewer sees exactly what moved.
Transitive findings and overrides
When no release of the parent includes the fixed version, npm's overrides field lets you replace a package anywhere in the tree. npm's docs describe three forms.
{
"overrides": {
"foo": "1.0.1"
}
}
That pins foo to 1.0.1 wherever it appears. To pin it only under one parent, nest it:
{
"overrides": {
"bar": {
"foo": "1.0.1"
}
}
}
A key can also carry a version, so this applies only under version 2.0.0 of bar:
{
"overrides": {
"bar@2.0.0": {
"foo": "1.0.1"
}
}
}
Three rules from the docs save time:
- Overrides are only considered in the root
package.json. Anoverridesblock inside a dependency or a workspace package is ignored for tree resolution. - You may not override a package you also depend on directly unless the dependency and the override share the exact same spec. Otherwise npm throws
EOVERRIDE. - The
$reference, such as"foo": "$foo", points an override at your direct dependency's spec, which avoids that conflict.
When you ask the assistant for an override, ask for the narrowest form that clears the finding, and ask it to add a comment in the commit message naming the parent, so whoever upgrades that parent later can remove the override. Then run the full test suite, because the parent was never tested with the version you forced.
propose the narrowest overrides entry for the remaining transitive finding,
nested under the parent that pulls it in, then wait for my approval before editing
The non-interactive route: safeguard fix
Sometimes you do not want a conversation. safeguard fix analyses vulnerabilities and updates packages to safe versions, writes a fix report, and can generate a patched SBOM. Start with a dry run, which previews changes without modifying files:
safeguard fix --manifest package.json --dry-run
Then narrow what it may do:
# only patch releases, high and critical only
safeguard fix --manifest package.json --patch-only --severity high
# one package only
safeguard fix --manifest package.json --package lodash
Useful defaults from the command's help: minimum severity is high, minor upgrades are allowed, major upgrades are not unless you pass --major-upgrades, it only upgrades and never downgrades, it backs up original files, and the report goes to fix-report.json. It can also open a pull request with --create-pr, which you review like any other. Whichever route you take, finish with npm ci, npm test and a rescan.
Approving from the desktop app
You do not have to sit at the terminal while installs and tests run. A CLI session is live, and it shows in the web app, the desktop app and the phone app. From the desktop or phone app you can send work to a computer running the CLI: pick the computer and the folder, then approve each command and file edit there.
The recorded desktop session below shows that flow. The CLI session shows in the desktop app's conversation list; "2 connected" shows the computer, the browser and Safeguard cloud. You ask for work on your computer, it asks which computer and folder, you pick the folder, then Approve or Deny each command and file edit. It ends with the upgrade done and tests passing.
Click through it at your own pace, or open it full screen.
The desktop app guide covers installing it on Windows, macOS and Linux.
Keep it fixed in CI
A fix that is not checked on every build comes back the next time someone adds a package. Add two checks to the pipeline: npm's own audit threshold and a Safeguard scan.
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm audit --audit-level=high
- name: Safeguard scan
env:
SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
run: safeguard ci scan --local --wait --fail-on high
Notes on each line:
--audit-levelsets the minimum severity that makesnpm auditexit non-zero. It does not filter the report.safeguard ci scan --localscans the checkout on the CI runner and uploads only the results, never the source.--waitwaits for the scan, and--fail-on highfails the step at high or above.- The key comes from Settings, Secret Keys, with the
scans:triggerscope (plusscans:readfor--wait), stored as the CI secretSAFEGUARD_API_KEY. - If your team uses gates,
--fail-on-gate Releasefails the step when that gate fails. Policies and gates are evaluated in the platform, not by the CLI.
The step assumes the CLI is installed on the runner with the install command from step 1. For an assistant written summary on each build, see Safeguard CLI in CI with safeguard exec.
Troubleshooting
npm ci fails with a lockfile mismatch. The lockfile and package.json disagree, usually because a range was edited without reinstalling. Run npm install once (approve it in the session), commit both files, and run npm ci again.
EOVERRIDE after adding an override. You overrode a package you also depend on directly. Either bump the direct dependency instead, or make the override use the $ reference to your direct spec.
The finding is still there after the upgrade. There may be a second copy deeper in the tree. Run npm explain on the package or npm ls --all and look for every path. Point npm explain at a specific folder under node_modules to see why that copy exists.
npm audit refuses to run. It needs a package-lock.json or shrinkwrap file by default. Run npm install to create one.
Tests fail after the bump. Paste the failure into the session and ask for the smallest code change that fixes it, or roll back. /rewind restores files the assistant edited; from the shell, safeguard checkpoints restore with --dry-run shows the diff first. Use git restore package-lock.json for anything npm install wrote.
The scan looks different from npm audit. They are separate tools reading the same lockfile with different data sources. Fix what both report, and use the rescan in the CLI as the check for this workflow.
Common mistakes
Fixing package.json and forgetting the lockfile. The lockfile is what installs. git diff --stat should show both files.
Running npm audit fix --force because it was suggested. It can cross major versions. npm's docs advise against it unless you know exactly what you want.
Overriding before checking the parent. An override pins a version the parent never tested. Upgrade the parent first when a release exists.
Leaving overrides in forever. Note the parent in the commit message and remove the override when the parent ships the fix.
Accepting new packages in a fix. An upgrade should change versions of packages you already have. A new name in the diff is a reason to stop and check.
One giant upgrade. Patch releases together, each minor on its own, each major as its own branch. Small diffs are easy to review and to revert.
Trusting "fixed" without a rescan. The scanner, not the summary, closes the finding.
Using --yolo on a real checkout. It approves everything, including installs that run package scripts. Keep it for throwaway sandboxes.
FAQ
Can I use my Claude, ChatGPT or Codex plan for this?
Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. See the CLI docs or /model for connecting a model. You can also connect Claude or ChatGPT to the Safeguard MCP server and work from the chat you already use.
Should I use npm audit fix or the Safeguard CLI?
Use whichever fits the finding, and many teams use both. npm audit fix applies remediations within your ranges. The CLI adds the conversation: it explains the path, proposes the smallest change, waits for your approval, runs your tests and rescans.
How do I fix a vulnerable transitive dependency?
Find the parent with npm explain, upgrade the parent if a release includes the fix, and use an overrides entry in the root package.json only when no such release exists.
Will the assistant run npm install without asking?
Not in Ask, Accept edits or Auto: in all three, a command like npm install asks first unless your policy pre-approves it. Only the --yolo flag approves everything.
Does my code leave my machine?
When the local runner runs platform scan jobs, your source stays on your machine; only SBOMs and findings are uploaded. safeguard findings uploads nothing at all.
What if the only fixed version is a new major?
Treat it as its own change on its own branch. Read the package's changelog, ask the assistant to list the breaking changes that affect your code, and run the full test suite.
Can I undo a fix?
Yes. /rewind or safeguard checkpoints restore puts back files the assistant edited, and Git covers the lockfile changes npm install made.
Try it yourself
This walkthrough sets up the runner on your own machine: pick the platform, start it, approve it with its device code, then use the same CLI to scan from the terminal.
Click through it at your own pace, or open it full screen.
The download page also lists the desktop app for Windows, macOS and Linux, iOS and Android apps, IDE extensions, a browser extension, and a cloud sandbox tenant against a seeded demo repo that is free for seven days with no card. Fix in the terminal, approve from the desktop, check the result on your phone. Before you merge, read reviewing AI generated code before merge.
Ready? Download the Safeguard CLI, run safeguard in your project, type /login, then /scan .. The full reference is in the CLI docs.
Quick check
Why run npm ci after the assistant runs npm install?
Because npm ci installs exactly what the lockfile says and fails if package.json and the lockfile disagree. Passing it proves the lockfile you commit is the one that installs.
When should you use an overrides entry?
Only for a transitive finding where no release of the parent includes the fix. Put it in the root package.json, keep it as narrow as possible, and remove it when the parent catches up.
What tells you a finding is really fixed?
A rescan that no longer reports it, after npm ci and your tests pass.
Sources
- Safeguard CLI overview
- Safeguard CLI interactive terminal
- Safeguard downloads
- npm audit documentation
- npm ci documentation
- npm explain documentation
- npm ls documentation
- npm update documentation
- package.json overrides (npm docs)
- actions/setup-node README
- GitHub Docs: using secrets in GitHub Actions
Last reviewed: 21 October 2026.