Fixed findings come back for a few common reasons: an old branch or build still has the old version, another package pulls the old version back in, the lockfile was regenerated, the fix lived in only one place, or a new flaw was published. Find which one applies, then fix the cause, not the symptom.
TL;DR
- A returning finding is not always the same finding. Check the exact package, version, and project.
- Old branches, old images, and extra manifest files are the usual hiding places.
- A parent package can pull the old version back in after an update.
- A lockfile that is regenerated or ignored can undo your fix.
- Rescan on a schedule and make the fix part of the code review, not a side step.
What does it mean when a fixed finding returns?
Definition: A recurring finding is a vulnerability that a scan reports again after someone marked it fixed. It is a sign that the fix did not reach everywhere the package lives, or that the package arrived again by another route.
Think of weeding a garden. You pull a weed and the next week it is back. Sometimes the root stayed in the soil. Sometimes a seed blew in from the neighbor's yard. Sometimes it is a different weed that looks similar. The cure depends on which one it is.
Software works the same way. A package version is just a number written in a file. If any file, branch, or image still says the old number, a scan will find it. So before you assume the tool is wrong, look at where the old number lives.
What are the usual causes?
Here are the seven causes teams run into most.
| Cause | What happened | How to check |
|---|---|---|
| Old branch | The fix is on one branch, but the scan reads another | Check which branch the scan reads |
| Old image or build | A container or package built before the fix is still deployed | Compare build dates to fix dates |
| Parent pulls it back | A package you updated needs the old version of a child | Look at the dependency path |
| Lockfile undone | The lockfile was regenerated or not committed | Compare lockfile in the repository to the one in the build |
| Second manifest | A project has more than one dependency file, and you fixed one | Search the whole folder for dependency files |
| Version range drift | A loose range allowed a different version to install | Check the range and the installed version |
| New advisory | A different flaw was published for the same package | Read the finding id carefully |
Let us take them one at a time.
Old branches and old builds
Most scanners read one branch, usually the default one. If your fix went into a feature branch that was never merged, the default branch still has the old version. Likewise, an application already running in production might have been built last month, so it has last month's packages until it is rebuilt and redeployed.
The parent pulls it back
You upgraded a package. But a different package in your tree needs the old version of the same library. Package managers pick versions by matching ranges, so two packages with different needs can leave the old version in the tree. The fix is often to upgrade the parent that wants the old child, as covered in direct versus transitive vulnerabilities.
The lockfile was undone
The npm docs describe the lockfile as recording the exact tree so that later installs produce identical trees, and say it should be committed to the repository. If someone deletes it, or if a build runs without it, versions can change. The same docs say that when the lockfile is bypassed for the audit command, results may differ every run because the tree is rebuilt each time. If your fix was in the lockfile and the lockfile is not what the build uses, the old version can return. See best practices for npm lockfile security.
A second manifest
Large projects often have several dependency files in different folders, for example one for the front end and one for a background service. If you fixed one and not the other, the scan will keep reporting the one you missed.
Version range drift
A manifest does not always name an exact version. It often names a range. The npm semver documentation explains that a caret range, written with a ^ sign, allows changes that do not modify the left most non zero number, which in practice means patch and minor updates for versions 1.0.0 and above. That means a fresh install can pick a newer version than before. That usually helps, but it can also mean two machines get different versions if the lockfile does not pin them. Our upgrade guide explains the numbering.
A new advisory
Sometimes the finding looks the same but is not. Packages can have several flaws over time. The scan may be reporting a second, different flaw in the same package, which needs a newer fix. Always compare the finding identifier, not just the package name. The what is a known vulnerability post explains the identifiers.
How do I track down which cause I have?
Use this short routine.
- Write down the exact finding. Package name, version found, finding id, and project.
- Check the finding id against your earlier one. If it differs, it is a new flaw, not a return.
- Find the branch and build the scan read. Was it the one you fixed?
- Look at the version in the lockfile. Is it the fixed one?
- Look at the dependency path. Does a parent bring in the old child?
- Search the folder for other dependency files. Fix them all.
- Check what is deployed. If the fixed code has not been released, the running app is still old.
A worked example with invented numbers
The numbers below are invented for teaching.
On Monday a scan of the "billing" project shows 14 high findings. The team upgrades packages and marks 14 as fixed. On Friday the scan shows 5 of those 14 are back.
The team investigates:
- 2 of the 5 are on the default branch. The fix was merged to a release branch only. Cause: wrong branch. Fix: merge to the default branch.
- 1 of the 5 comes from a second manifest in a "tools" folder. Cause: second manifest. Fix: update that file too.
- 1 of the 5 is the same library pulled in by a parent package that wants the old version. Cause: parent pulls it back. Fix: upgrade the parent.
- 1 of the 5 has a different finding id. Cause: new advisory. Fix: a newer version.
So 5 returns became 5 different small actions. None of them was "the scanner is broken." That is typical. Almost every recurrence has a specific, findable cause.
How do I make fixes stay fixed?
- Fix on the default branch first, then copy to others if needed.
- Commit the lockfile and make the build use it.
- Add a gate to pull requests. The OWASP guidance says organizations need an ongoing plan for monitoring, triaging, and applying updates. A gate that flags a known bad version before it merges is part of that plan.
- Rescan on a schedule, not only when code changes, because new flaws appear after you ship.
- Record the fix with the finding id, the version, and the date. If it returns, you can compare.
- Rebuild and redeploy so running systems match the fixed code.
Try it yourself
The demo below shows a vulnerability queue where each finding has a status in a sample workspace. Click through and look for how each finding carries a clear status, which is the record you compare against when a finding seems to return.
Click through it at your own pace, or open it full screen.
Quick check
Does a returning finding mean the scanner is wrong?
Rarely. Usually the old version still lives on another branch, in another file, in a build, or comes back through a parent package. Check the exact package, version, and finding id first.
Why does the lockfile matter for this?
The lockfile records the exact versions installed. If it is not committed or not used by the build, the tree can be rebuilt and old versions can return.
How can I tell a recurrence from a new flaw?
Compare the finding identifier. The same package can have several different flaws over time, and each has its own identifier.
How often should I rescan?
On a regular schedule and whenever dependencies change. New flaws are published after you ship, so a past clean result cannot cover a flaw announced later.
How can Safeguard help with this?
Safeguard's dependency scanning rechecks your projects against fresh vulnerability data, so a flaw announced after your last build can still show up. Policies and gates can block a pull request that brings back a known bad version, which keeps a fix from slipping away. If you turn on autonomous remediation, fix pull requests are opened for review. It is off by default, and every category starts on Manual.
Run a free scan at gold.safeguard.sh/scan.
Sources
- npm Docs: package-lock.json
- npm Docs: npm audit
- node-semver README
- OWASP Top 10 2021, A06 Vulnerable and Outdated Components (source text on GitHub)
Last reviewed: 19 October 2026.