Your service is on the list, so start by finding out how the vulnerable package gets into your build: directly, or three levels down through something else. Then move it to the first version outside the affected range (with an override if the parent will not move), rebuild, rescan the artifact you actually ship, and write the pull request so a reviewer can check every claim in it.
The rest of this post is that checklist, with commands for npm, pip, Maven, Gradle and Go from each ecosystem's own documentation, and a pull request template you can paste.
Key takeaways
- Trace first, patch second. The fix for a direct dependency is a version bump in your manifest. The fix for a transitive one might be a parent upgrade, an override, or a wait. You cannot choose until you know the path.
- The fixed version comes from the advisory's affected ranges, not from "latest". Read the
introducedandfixedevents in the OSV record, then pick the lowest fixed version your other constraints accept, unless there is a reason to go further. - KEV and EPSS change the clock, not the method. A CVE on CISA's Known Exploited Vulnerabilities catalog is being exploited somewhere today. EPSS gives a daily probability of exploitation in the next 30 days. Both tell you how fast; neither tells you how.
- Overrides are a scalpel. npm
overrides, pip constraints files, MavendependencyManagement, Gradle dependency constraints and Goreplaceorexcludeall only apply at the root of your build. They pin a version your parents did not test with, so they need tests and a note to remove them later. - "Fixed" means the shipped artifact is clean, not that the manifest changed. Check the lockfile, the rebuilt image and a fresh scan of that image.
- Safeguard covers the same path: software composition analysis with deep transitive scanning (GA), reachability analysis and prioritisation (GA), autonomous remediation with Griffin fix pull requests (GA, ships switched off with every category set to Manual), and the CLI and IDE extensions (GA). You still review, test and merge.
The situation
Security posted a list. It might be a spreadsheet, a ticket per service, a channel message, or a dashboard export. Your service is on it, next to a CVE identifier, a package name, a severity and probably a due date. Somebody will ask in standup tomorrow whether it is done.
What the list usually does not tell you:
- whether your code pulls the package directly or whether it arrives through a framework, an SDK or a build tool;
- which of your deployable artifacts carry it (the API image, the worker image, the CLI you ship to customers, the Lambda bundle);
- whether the affected function is anywhere near a code path your service uses;
- what the fixed version is for the major line you are on;
- whether the fix will break anything.
A bad week looks like this. You bump the version in your manifest, the build passes, you close the ticket. Three days later the next scan still shows the vulnerable version, because a second copy came in through another parent, or the lockfile was not regenerated, or the image was rebuilt from a cached layer. The ticket is reopened and the due date has passed.
A good week looks like this. Within an hour you know the path. Within a day you have a pull request that moves the package to a fixed version, with test results, a lockfile diff, a rescan of the built artifact and a description a reviewer can check in five minutes. When someone asks whether it is done, you send the link.
The rules behind the list
Three public sources sit behind almost every "affected services" list. Knowing what each one actually says saves you from arguing with the wrong number.
OSV ranges: what "affected" and "fixed" mean
Many ecosystem advisory databases publish in the Open Source Vulnerability (OSV) format, a schema maintained under the OpenSSF that lists the databases using it. The schema describes affected software as a list of packages, each with ranges made of events. The events are introduced, fixed, last_affected and limit.
In plain terms:
introducedis the first version that carries the flaw.fixedis the first version that does not. Every version fromintroducedup to, but not including,fixedis affected.last_affectedis used when the advisory knows the last bad version but does not name a fix, for example when a fix has not shipped.- A package can have several ranges. A library that maintains two major lines often has two
introducedand twofixedevents, one per line.
That last point matters more than any other. If your service is on the 2.x line and the advisory says the fix is in 3.4.1, read the full ranges. There may be a 2.17.3 backport that fixes the same flaw without a major upgrade. Many "we have to do a major version migration" panics end here.
CISA KEV and BOD 26-04: when the clock is short
CISA's Known Exploited Vulnerabilities catalog is, in CISA's words, "the authoritative source of vulnerabilities that have been exploited in the wild." Each entry has a date added and a due date, and current entries point to Binding Operational Directive 26-04, "Prioritizing Security Updates Based on Risk", which replaced BOD 22-01. The directive binds US federal civilian agencies, not your company, but the catalog is the plainest public signal that a CVE is being used against real systems now. CISA recommends that every organization use KEV as an input to its prioritisation.
If your CVE is on KEV, assume your security team's due date is short for a reason, and treat this as today's work rather than this sprint's.
FIRST EPSS: how likely, not how bad
The Exploit Prediction Scoring System, published by FIRST, estimates the probability that a published CVE will be exploited in the wild in the next 30 days. It publishes a score between 0 and 1, with a percentile, every day for every CVE. CVSS tells you how bad exploitation would be; EPSS tells you how likely it is to be attempted soon. A high CVSS with a very low EPSS and no KEV entry is still a fix, but it is rarely a drop everything fix.
What none of these tell you
None of these sources know your code. An advisory does not say whether your service calls the affected function. Reachability answers that, but "we do not call it" is a reason to prioritise, not to skip a clean upgrade. Code paths change.
This post is engineering guidance, not legal or compliance advice. If your company has contractual or regulatory patch timelines, those win over anything here.
Step by step: from the list to a merged fix
Here is the full checklist. The sections after it go deeper on each step for each ecosystem.
- Confirm the finding applies to you. Check the package name, the ecosystem and the version actually resolved in your lockfile or built artifact, not the range in your manifest. Note every deployable artifact built from this repository.
- Trace the path. Find whether the package is a direct dependency, a transitive one, or both, and which parents pull it in. Write the chain down; you will need it in the pull request.
- Read the advisory ranges. Find the
fixedversion for the line you are on. Check for backports. Note whether the CVE is on KEV and its current EPSS score. - Decide the move. Use the decision table below: direct upgrade, parent upgrade, override, mitigation, or a documented wait.
- Make the change on a branch. One CVE or one package per branch keeps the review readable.
- Regenerate the lockfile with the package manager, never by hand.
- Run the tests, including integration tests that exercise the affected package, if you have them. Read the changelog between your version and the target.
- Rebuild the artifact you ship, without cached layers for the dependency install step.
- Rescan the built artifact and confirm the vulnerable version is gone from every path.
- Write the pull request with the template below.
- After merge, confirm deployment. Check that the running version is the rebuilt one. Then reply on the list with the link.
- If you used an override or a mitigation, open a follow-up ticket to remove it when the parent catches up.
The decision table
| What you found | Usual move | Watch out for |
|---|---|---|
| Direct dependency, fixed version in the same major line | Bump it in your manifest and regenerate the lockfile | Other copies arriving through parents |
| Direct dependency, fix only in a new major | Check for a backport first; otherwise plan the migration and consider a mitigation while you do it | Breaking API changes, removed defaults |
| Transitive, parent has a release that pulls the fixed version | Upgrade the parent | The parent's own breaking changes |
| Transitive, parent has not released, fixed version exists | Override or constrain the transitive version at the root | Untested combination; add a removal ticket |
| Transitive through a dev or build only dependency | Fix it, but check it actually ships; it may not be in the runtime artifact | Build tools that end up in images |
| No fixed version exists | Mitigate, consider removing or replacing the package, document and watch the advisory | Treating "no fix" as "no risk" |
| Package is in the base image, not your manifest | Rebuild on a patched base image | Pinning by tag instead of digest |
The "affected list" worksheet
Copy this into the ticket before you start. It takes ten minutes and saves the back and forth later.
CVE / advisory ID:
Package and ecosystem:
Version resolved in lockfile:
Version found in built artifact(s):
Artifacts affected (image, bundle, binary):
Path(s) into the tree:
root -> parent -> ... -> package@version
Direct or transitive:
Affected range(s) from advisory:
Fixed version(s), per major line:
On CISA KEV? (date added):
EPSS score and percentile (date checked):
Does our code reach the affected function? (how we know):
Chosen move: upgrade / parent upgrade / override / mitigation / wait
Removal ticket (if override or mitigation):
How do you find whether the package is direct or transitive?
The first question is always where the package comes from. Every major package manager can answer it, but the command differs.
A direct dependency is one you declared in your manifest. A transitive dependency is one that one of your dependencies declared, or one of theirs, and so on. A package can be both, and it can appear at several versions in one tree. For background on why transitive dependencies dominate most trees, see direct versus transitive dependencies. If you are not sure your current tool even looks at transitive or dev dependencies, this post on audit tool coverage walks through how to check.
npm
npm documents two commands for this. npm ls prints installed packages as a tree; since npm stopped printing the whole tree by default, you need --all to see everything below the top level. npm explain (alias npm why) gives the "bottoms up" view: the chain of dependencies that caused a given package to be installed.
# Every installed copy of the package, with the path to each
npm ls example-lib --all
# Why each copy is here, from the package up to your root
npm explain example-lib
npm explain is usually the faster read. Each block in its output shows one installed copy, its location under node_modules, and the dependency spec that required it, walking up to "the root project". If one copy is marked dev, it came in through a dev dependency.
If you use workspaces, run these from the root and check each workspace, because overrides and resolutions only take effect at the root.
pip and Python
pip has no single "why is this installed" command. Two habits help.
First, look at what your lock or pinned file says. If you use a fully pinned requirements.txt generated by a compile step, the generator often writes a comment under each pin naming what required it. If you use a different lock tool, its lockfile records the same relationships.
Second, ask pip about the installed environment:
# Installed version, location and the package's own requirements
python -m pip show example-lib
# Resolve without installing, and write a JSON report of what would be installed
python -m pip install --dry-run --ignore-installed --report report.json -r requirements.txt
# Check the installed set for broken or conflicting requirements
python -m pip check
The pip documentation describes --report as generating a JSON file describing what pip did, and notes that combined with --dry-run and --ignore-installed it can be used to resolve requirements without installing them. pip show confirms which version is actually installed and what that package itself requires; the report and your lock file tell you which of your requirements pulled it in. Run these inside the same virtual environment, or the same image, that your service runs in; a developer laptop environment is not evidence.
Maven
Maven's dependency plugin prints the tree, and its includes filter narrows it to the artifact you care about:
# Show only the paths that lead to the vulnerable artifact
mvn dependency:tree -Dincludes=com.example:example-lib
# Show omitted duplicates and conflicts as well
mvn dependency:tree -Dverbose -Dincludes=com.example:example-lib
Maven's own documentation explains why the verbose view matters. When the same artifact appears at several versions, Maven's dependency mediation picks the "nearest definition": the version closest to your project in the tree, and if two are at the same depth, the first declaration wins. So the version that actually ends up on your classpath may not be the one you expect from reading any single POM. The verbose tree shows the versions that were considered and omitted.
Gradle
Gradle reports the same thing through its built in tasks:
# Why this module is on the runtime classpath, and which version won
./gradlew dependencyInsight --dependency example-lib --configuration runtimeClasspath
# The full tree for one configuration
./gradlew dependencies --configuration runtimeClasspath
Check the configuration that ships, usually runtimeClasspath, not just compileClasspath.
Go
Go modules use minimal version selection (MVS), described in the Go modules reference: the build uses the minimum version of each module that satisfies every requirement in the graph. Two commands show you the graph:
# The shortest import path from your main module to the package
go mod why -m example.com/examplelib
# Every edge in the module requirement graph, filtered to the module
go mod graph | grep example.com/examplelib
The Go reference describes go mod why as showing "a shortest path in the import graph from the main module" to each listed package, and go mod graph as printing the requirement graph one edge per line. Go also has a symbol level vulnerability checker, covered under reachability below.
Containers and base images
If the package is in an operating system layer, not your application manifest, none of the above will find it. Scan the image itself, find which layer added the package, and check whether the base image publisher has a patched tag. The fix is usually a base image bump and a rebuild, not a code change.
How do you pick the target version?
You now know the path. The next question is what version to move to. The answer is rarely "latest".
Start from the fixed version for your line
Read the advisory's affected ranges, as above, and find the fixed event for the major line you are on. That is the lowest version that is not affected. Then decide whether to stop there or go further:
- Stop at the fixed version when you want the smallest possible change under time pressure, especially on a KEV listed CVE.
- Go to the latest patch release in the same minor line when there are later bug fixes you want and the changelog shows nothing risky.
- Go further only when the next release also fixes other open findings for the same package, and you have time to test the larger jump.
Pin to an exact version, or to a range that cannot resolve to an affected version.
Read the changelog between your version and the target
Semantic versioning is a convention, not a guarantee. Read the changelog or release notes for every release between your current version and the target. Look for removed functions, changed defaults, new required configuration, dropped runtime versions, and changes to transitive dependencies of the package itself.
If the jump crosses a major version, read the migration guide first.
Use KEV and EPSS to set the timeline, not the version
KEV and EPSS do not change which version you pick. They change how fast you need it and how much risk you accept to get it fast:
| Signal | What it means for you |
|---|---|
| On CISA KEV | Exploited in the wild. Take the smallest safe change today; schedule the cleaner upgrade later if needed. |
| High EPSS, not on KEV | Likely to be exploited soon. Same day or next day, with normal testing. |
| Low EPSS, not on KEV, reachable in your code | Normal sprint work with normal testing. |
| Low EPSS, not on KEV, not reachable | Still fix it, but it can follow the regular dependency update cadence. Record why. |
Your security team may have its own service levels. Follow those; this table is a starting point for the conversation, not a policy.
Is the vulnerable code reachable?
Reachability analysis asks whether your code calls the vulnerable function, or can. It is a priority signal, not an exemption. For Go, the language team ships govulncheck, which the Go documentation describes as reporting vulnerabilities based on which functions in your code are transitively calling vulnerable functions. Run it from your module root:
govulncheck ./...
For other ecosystems, reachability tools exist, and some security platforms build it in. Whatever you use, check what the tool does with code it cannot analyse (reflection, dynamic imports, plugins, native code). A tool that marks those as "not reachable" is hiding risk from you.
When is an override or resolution the right move?
An override forces a transitive dependency to a version that its parent did not ask for. It is the right move when:
- a fixed version of the transitive package exists;
- the parent that pulls it in has not released a version that depends on the fix, or that release brings breaking changes you cannot take today;
- the fixed version is compatible with the parent's declared range, or close enough that your tests give you confidence.
It is the wrong move when the fix is a major version jump the parent has never been tested with, or when you could simply upgrade the parent. Every override is a combination nobody upstream tested. Treat it as temporary and ticket its removal.
Each ecosystem has its own mechanism, and each only works at the root of your build.
npm: overrides
npm's package.json documentation describes overrides as a way to replace a package in your dependency tree with another version, giving "replacing the version of a dependency with a known security issue" as a use case. The docs also note that overrides are only considered in the root package.json; overrides in installed dependencies or workspaces are ignored.
{
"overrides": {
"example-lib": "2.17.3"
}
}
You can scope an override to one parent, so it only applies when the package is a child of that parent:
{
"overrides": {
"parent-sdk": {
"example-lib": "2.17.3"
}
}
}
Two details from the docs trip people up. First, you cannot set an override for a package you also depend on directly unless the specs match exactly; npm throws an EOVERRIDE error. The documented workaround is to reference your direct dependency's spec with a $ prefix, so the override follows whatever you declared:
{
"dependencies": {
"example-lib": "^2.17.3"
},
"overrides": {
"example-lib": "$example-lib"
}
}
Second, npm audit fix can do some of this for you, but the docs are clear about its limits: when the vulnerable chain reaches your root and cannot be updated without changing dependency ranges, npm audit fix needs --force, which can install SemVer major updates. Use npm audit fix --dry-run --json to see what it would do before letting it touch your tree.
Other JavaScript package managers have their own equivalents. Use the one documented for your package manager.
pip: constraints files
pip's user guide describes constraints files as requirements files that "only control which version of a requirement is installed, not whether it is installed or not." Including a package in a constraints file does not install it. That makes them a clean way to say "if anything pulls in this package, it must be this version or later":
# constraints.txt
example-lib>=2.17.3
python -m pip install -r requirements.txt -c constraints.txt
The pip docs suggest exactly this pattern: write a single constraints file for your organisation and use it everywhere. If you use a lock tool on top of pip, put the constraint where that tool reads it and regenerate the lock, otherwise the next lock run can undo your change.
Maven: dependencyManagement
Maven's documentation explains that dependency management "takes precedence over dependency mediation for transitive dependencies." Declaring the version in dependencyManagement therefore pins it wherever it appears in the tree, without making it a direct dependency:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>example-lib</artifactId>
<version>2.17.3</version>
</dependency>
</dependencies>
</dependencyManagement>
If you import a bill of materials (BOM) with import scope, check whether a newer BOM release already carries the fixed version. Bumping the BOM is often cleaner than pinning one artifact. The Maven docs also note that dependency management does not affect the dependencies of plugins, so a vulnerable library pulled in by a build plugin needs a different fix.
Gradle: dependency constraints
Gradle's dependency constraints let you set a minimum version for a module wherever it appears, and record why:
dependencies {
constraints {
implementation("com.example:example-lib:2.17.3") {
because("fixes the vulnerability in the affected range")
}
}
}
Gradle's docs say a constraint takes part in version conflict resolution just like a declared dependency version, and because records the reason in the build. Run dependencyInsight again afterwards to confirm the constraint is what selected the version.
Go: go get, replace and exclude
In Go, the first move is usually just to require the fixed version. Because of minimal version selection, requiring a higher version in your own go.mod raises it for the whole build:
go get example.com/examplelib@v2.17.3
go mod tidy
If you need more control, the Go modules reference documents two directives. An exclude directive prevents a module version from being loaded; since Go 1.16, a requirement on an excluded version is ignored, and go get or go mod tidy may add a requirement on a higher version instead. A replace directive replaces the contents of a module version with another module path and version, or a local directory. Both only apply in the main module's go.mod and are ignored in other modules.
exclude example.com/examplelib v2.17.1
replace example.com/examplelib v2.17.1 => example.com/examplelib v2.17.3
Prefer go get of the fixed version. Use exclude when you need to keep a specific bad version out. Use replace to point at a patched fork only when no fixed release exists, and say so loudly in the pull request.
What if no fixed version exists?
Sometimes the advisory has introduced and last_affected events and no fixed event. Or the fix exists only on a major line you cannot reach this week. You still have moves.
- Check the ranges again. Make sure there is not a fix on another line, or a fixed release that the advisory has not been updated to mention. Check the project's release notes and issue tracker.
- Check whether you need the package at all. Unused dependencies are common. If nothing imports it, remove it from the manifest and rebuild.
- Check whether you need the affected feature. Many advisories affect one parser, one protocol handler or one optional module. If the library lets you disable that feature through configuration, do it, and add a test that fails if it is ever turned back on.
- Add a compensating control. Input validation at the boundary, a size limit, a stricter content type check, a web application firewall rule, a network policy that removes exposure. None of these fix the flaw; they reduce the chance it is reached.
- Replace the package if a maintained alternative exists and the migration is small.
- Patch it yourself in a fork, as a last resort and with care. You now own that fork until upstream ships.
- Document and watch. Record the risk, the mitigation and the date you will check again. Subscribe to the advisory and the project's releases.
Do not mark the finding as resolved while you wait. Mark it mitigated, or record an exception with an expiry date, depending on how your team tracks them. "Resolved" should mean the vulnerable version is gone.
How do you verify the fix shipped?
This is where most reopened tickets come from. A change to the manifest is a claim. The verified fix is the built artifact, rescanned, and the running service on the new build.
Check the lockfile diff
Look at the lockfile diff in the pull request, not just the manifest diff. You want to see:
- the vulnerable version removed from every location it appeared;
- the new version added, with its integrity hash;
- no unexpected changes to unrelated packages (a large unrelated churn suggests the lockfile was regenerated with a different package manager version, which is a separate change).
Then rerun the trace command from earlier. npm explain, mvn dependency:tree -Dincludes=..., ./gradlew dependencyInsight, and go mod why -m should now show only the fixed version, and pip show should report it as installed.
Rebuild without cache for the install step
Container builds cache layers. If the dependency install step is cached from before your change, the image can still carry the old version. Rebuild the image with the install layer invalidated, or with no cache at all for this build.
If the package came from the base image, rebuild on the patched base image and pin it by digest so the next rebuild does not silently drift.
Rescan the built artifact
Scan the image or bundle you actually deploy, not the source tree. A source scan reads manifests and lockfiles; an image scan reads what is installed in the image, including operating system packages and anything vendored. The two can disagree, and the image is what runs.
Keep the scan output and attach the relevant part to the pull request or ticket.
Confirm what is running
After the deploy, check the version that is running. How you do that depends on your platform: the image digest on the running workload, a version endpoint, a build information file baked into the artifact. Compare it with the digest of the image you rescanned.
Then reply on the list with the pull request link, the rescan result and the running digest. If the first hour after a critical dependency flaw was about triage, this step is what lets security close the incident.
What should the pull request description say?
A good fix pull request lets a reviewer confirm every claim without asking you a question. Paste this template and fill it in:
## Fix: <CVE or advisory ID> in example-lib
**Affected list item:** link to the security ticket or list entry
**What was vulnerable**
- Package: example-lib (npm)
- Version before: 2.17.1 (resolved in lockfile and in the built image)
- Affected range: >=2.0.0, <2.17.3 (from the advisory)
- KEV: yes/no (date checked). EPSS: score and percentile (date checked)
**How it got in**
- root -> parent-sdk@4.2.0 -> example-lib@2.17.1
- root -> other-tool@1.9.0 (dev) -> example-lib@2.16.0
**What changed**
- Upgraded parent-sdk to 4.3.1, which depends on example-lib ^2.17.3
- Added an override for the dev path until other-tool releases (removal ticket: link)
**Version after:** example-lib@2.17.3 in every path
**Testing**
- Unit and integration tests: pass (link to CI run)
- Exercised the code that uses example-lib: describe how
- Changelog reviewed 2.17.1 to 2.17.3: no breaking changes noted
**Verification**
- Lockfile diff: only example-lib and parent-sdk changed
- Rebuilt image digest: sha256:...
- Rescan of the rebuilt image: example-lib@2.17.1 no longer present (attached)
**Follow-ups**
- Remove override when other-tool ships a fixed release (ticket link)
Three rules for the description. Name the CVE in the title, because that is what the security team searches for. Show the path, because that is what reviewers check first. Attach the rescan of the built artifact, because that is the proof. For the reviewer's side of this, there is a companion checklist for reviewing a fix pull request.
How Safeguard does this
Everything above works with whatever tools you already have. This section shows how the same checklist runs in Safeguard, using only flows from Safeguard's documentation. The capabilities involved are software composition analysis and deep transitive dependency scanning (GA), vulnerability management with EPSS and KEV signals (GA), reachability analysis and prioritisation (GA), autonomous remediation with Griffin fix pull requests (GA, ships switched off with every category set to Manual), the Safeguard CLI and local runner (GA), the IDE extensions for VS Code, Open VSX editors such as Cursor, and JetBrains IDEs (GA), and the Safeguard MCP server (GA).
-
Find the finding on your project. Safeguard builds an SBOM for each project and matches it against vulnerability sources including NVD, GHSA, OSV and CISA KEV. Open the project and go to its Findings tab. Each finding card shows the title, type, severity, status, affected component, the gate that caught it and when it was first seen. Filter by severity, type, status, gate or component to get to your CVE quickly.
-
Trace the path in the dependency tree. On the Project Overview page, the Dependencies tab shows the full dependency tree, with a tree view of parent and child relationships, dependency depth, versions and vulnerability indicators, and a sortable table view. This is the same "how does it get in" question from earlier, answered for the whole tree at once. Safeguard's SCA covers the full transitive inventory, including container layers, and deep dependency scanning follows long transitive chains.
-
Check whether it is reachable and how urgent it is. The Vulnerabilities tab has Priority and Reachability columns and sorts by priority by default. Safeguard's docs describe four reachability classes: reachable, conditionally reachable, unreachable and unknown. Code the analyser cannot decide, such as reflection or dynamic dispatch, is marked unknown and treated as potentially reachable rather than suppressed. EPSS and KEV feed the same priority, so filters like
reachable:trueandkev:truenarrow the list to the findings that need today's attention. Reachability depth varies by language; the docs publish a table per language. -
Scan from your own machine before and after the change. The Safeguard CLI runs the same scan locally. The docs show signing in with
safeguard login(an OAuth device code flow), then scanning a directory or the image you ship:safeguard scan --dir . safeguard scan --image nginx:latestSwap the image name for the image you ship. Results are written as JSON under
./output, includingvuln.json. The--resolve-transitiveflag resolves transitive dependencies, and--org-namespacemarks your own private package namespaces. To see the results in the platform next to your other projects, scan from the interactive terminal instead: runsafeguard, type/login, then/scan .. The docs state that the runner uploads the SBOM and findings, never your source. One detail that matters in CI:safeguard scanexits 0 when the scan ran, whatever it found, and 2 when results were written but the vulnerability scan did not actually run. There is no severity threshold flag; gating is done by policy gates in the platform, with Block, Warn and Notify actions and approvals handled through exceptions. -
See it while you code. The IDE extensions bring Safeguard's security insight into VS Code, Cursor and JetBrains IDEs as you code.
-
Open a fix pull request with Griffin. From the vulnerability, click AI Remediate, review the proposed changes, and click Create Pull Request. The docs describe the flow as analysis, solution generation (the safest upgrade path), impact assessment (breaking changes and compatibility), pull request creation and validation. Griffin can upgrade vulnerable packages to fixed versions, handle transitive dependency conflicts and update lockfiles. Each change gets a breaking change rating of Safe, Minor, Major or Critical. The generated pull request carries a title with the CVE reference, a description of the vulnerability, a summary of changes, breaking change warnings, test results when tests are configured, and links to the advisories. That lines up closely with the template above. Bulk mode fixes several vulnerabilities at once and opens one pull request per repository.
-
Keep it under your team's control. Autonomous remediation ships switched off, with every category set to Manual, so nothing is opened on its own until your team turns it on. The AI Remediate settings let you require manual approval before pull requests are created, run tests before creating them, and set a policy for breaking changes. The docs' own advice is to start with safe fixes and not auto merge breaking changes.
-
Ask from your assistant. If you work in an AI assistant, the Safeguard MCP server lets it query risk and pull SBOMs for your projects through tenant scoped tools. Safeguard is listed in the Claude connector directory as a community connector.
-
Record what you did. When you cannot fix yet, use the finding's Mitigate action, or record a risk acceptance with an expiry date on the Mitigations tab. Use Resolve only when the vulnerable version is gone. That keeps "resolved" honest, the same rule as in the manual checklist.
Manual checklist vs Safeguard
| Checklist step | Without a platform | In Safeguard | Still yours |
|---|---|---|---|
| Confirm the finding | Lockfile and image inspection | Findings tab per project | Deciding it applies |
| Trace the path | npm explain, mvn dependency:tree, go mod why | Dependencies tab tree view (GA) | Reading it |
| Prioritise | KEV, EPSS, your own call graph reading | Priority and Reachability columns (GA) | The call on "unknown" |
| Pick the version | Advisory ranges and changelogs | Griffin solution and impact assessment (GA, ships off) | Reviewing the breaking change rating |
| Make the change | Branch, manifest, lockfile | Fix pull request with lockfile update | Tests that cover your code |
| Verify | Rebuild and rescan | safeguard scan --image and the platform rescan | Confirming the running version |
| Record | Ticket comments | Resolve, Mitigate, risk acceptance with expiry | The decision itself |
What Safeguard does not do
Be clear about where the tool stops and your job starts.
- It does not write your tests. A Griffin fix pull request can run your configured tests and report the results. If your tests do not exercise the code that uses the vulnerable package, a green run proves little. Test coverage of that code is your job.
- It does not merge or deploy. Safeguard opens the pull request; a human reviews and merges it, and your pipeline deploys it. Confirming that the running service is on the rebuilt artifact is a check you still make.
- It does not guarantee a version bump is enough. Some fixes need code changes beyond the upgrade: a removed function, a changed default, a configuration you must now set. The breaking change rating helps you spot these; it does not do the migration for you on every codebase.
- Reachability is a priority signal, not proof of safety. Results vary by language, and code that cannot be analysed is marked unknown. A finding marked unreachable today can become reachable after the next feature ships.
- It does not fix what you do not build. If the vulnerable code is in a managed service, a vendor appliance or a host operating system outside your images, the fix has to come from that vendor or your platform team.
- It does not decide your risk. Mitigations, exceptions and expiry dates record a decision. Making that decision, and owning it, stays with your team and your security lead.
Common mistakes
- Fixing the manifest and not the lockfile. The manifest says one thing, the lockfile installs another. Always regenerate and review the lockfile diff.
- Fixing one path and missing another. The package often arrives through two parents. Rerun the trace after the change and confirm every path shows the fixed version.
- Upgrading to "latest" under time pressure. The biggest jump carries the most risk. Take the first fixed version on your line first.
- Overriding to a version the parent cannot use. An override that jumps a major version can compile and still fail at runtime. Test the code paths that use the parent.
- Leaving overrides forever. Every override needs a removal ticket. Otherwise, two years later nobody knows why it is there, and it blocks a future upgrade.
- Rescanning source, not the image. The image is what runs. Cached layers, base image packages and vendored copies only show up in an image scan.
- Treating "not reachable" as "not affected". Record it as a priority call, keep the upgrade on the plan, and recheck when the code changes.
- Marking "resolved" when it is mitigated. If the vulnerable version is still present, call it mitigated or accepted, with an expiry date.
- Not saying anything back. The list owner cannot close the incident until you reply with the link, the rescan and the running version.
FAQ
My service is affected by a dependency CVE. What do I do first?
Trace how the package gets into your build. Run npm explain, mvn dependency:tree -Dincludes=..., ./gradlew dependencyInsight or go mod why -m for the package (for pip, a --dry-run --report resolve plus your lock file), and write down every path. Then read the advisory's affected ranges to find the fixed version for your line.
How do I know if a vulnerable package is a direct or transitive dependency?
If it is declared in your own manifest, it is direct. If the trace command shows it under another package, it is transitive. It can be both, and it can appear at more than one version in the same tree, so check every path.
Should I upgrade to the latest version or the first fixed version?
Under time pressure, take the first fixed version on your current major line, because it is the smallest change. Move to later patch releases if the changelog shows only fixes. Plan big jumps separately with proper testing.
How do I force a transitive dependency to a safe version?
Use your ecosystem's root level mechanism: overrides in npm, a constraints file with -c in pip, dependencyManagement in Maven, dependency constraints in Gradle, or go get of the fixed version (with exclude or replace if needed) in Go. Prefer upgrading the parent when it has a fixed release.
What does it mean if the advisory has no fixed version?
It means no release is known to fix the flaw yet. Check other major lines for a backport, remove the package if you do not need it, disable the affected feature if you can, add compensating controls, and record the decision with a date to check again.
Does a CVE on the CISA KEV list mean I have to patch today?
KEV means the flaw has been exploited in the wild. CISA's directives bind federal agencies, not private companies, but your security team will likely set a short deadline for KEV entries. Take the smallest safe fix first and schedule the cleaner upgrade afterwards.
What is a good EPSS score to worry about?
There is no universal cut off. EPSS is a probability of exploitation in the next 30 days, published daily. Use it alongside KEV, CVSS and whether your code reaches the vulnerable function, and follow your security team's thresholds if they have set them.
How do I prove a dependency vulnerability is fixed?
Show the lockfile diff with the vulnerable version gone, the trace command showing only the fixed version, a rescan of the rebuilt artifact you ship, and the digest of the running version after deploy. Put all four in the pull request or the ticket.
Can a tool open the fix pull request for me?
Yes, several tools can open dependency upgrade pull requests. Safeguard's Griffin can open a fix pull request from a vulnerability, with a breaking change rating and links to the advisory, and autonomous remediation ships switched off with every category set to Manual. You still review, test and merge it.
What should a security fix pull request description include?
The CVE or advisory ID in the title, the version before and after, the path the package took into the tree, what changed and why, test results, the lockfile diff summary, the rescan of the built artifact, and any follow up such as an override removal ticket.
Once the upgrade is merged, the part of the work left is the time before a fix exists, or before it reaches every service that carries the package. The block below is how Safeguard closes those two gaps for teams that do not want to wait on an upstream release.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Griffin, Safeguard's security model, opens a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. It installs the CLI, opens the interactive terminal with safeguard, and asks it to "fix the lodash CVE in this repo". The assistant reads the finding and advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing. It ends with /permissions, /model and /scan .. That is steps 5 to 9 of the checklist above on one machine.
Click through it at your own pace, or open it full screen.
The second demo connects Claude to the Safeguard MCP server with OAuth and shows how you choose which tools Claude can use. If you ask your assistant for help on a fix, this is how it gets read access to the same findings your security team posted, instead of guessing from the package name.
Click through it at your own pace, or open it full screen.
Quick check
You added an npm override for a vulnerable transitive package and the build passed. What is still left before you can call it fixed?
Check the lockfile diff, rerun npm explain to confirm every path now shows the fixed version, rebuild the image without a cached install layer, rescan the built image, confirm the running version after deploy, and open a ticket to remove the override when the parent releases a fix.
The advisory lists introduced and last_affected events but no fixed event. What does that tell you?
No release is known to fix the flaw yet. Look for a fix on another major line, remove the package or disable the affected feature if you can, add compensating controls, and record the finding as mitigated or accepted with a date to check again, not as resolved.
Try the CLI on your own repository: download it at safeguard.sh/download, and read the docs at docs.safeguard.sh.
Sources
- npm Docs: package.json, overrides
- npm Docs: npm ls
- npm Docs: npm explain
- npm Docs: npm audit
- pip documentation: User Guide, constraints files
- pip documentation: pip install
- pip documentation: pip show
- Gradle User Manual: Viewing and debugging dependencies
- Apache Maven: Introduction to the Dependency Mechanism
- Apache Maven Dependency Plugin: dependency:tree
- Gradle User Manual: Dependency constraints
- Go Modules Reference
- Go: Vulnerability Management
- OpenSSF: OSV Schema
- CISA: Known Exploited Vulnerabilities Catalog
- FIRST: Exploit Prediction Scoring System
- FIRST: EPSS model
- Safeguard Docs: Findings
- Safeguard Docs: Explore SBOM
- Safeguard Docs: AI Remediate
- Safeguard Docs: CLI Usage
- Safeguard Docs: Vulnerability Prioritization (Reachability Analysis)
Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.