Skip to content
Notifications
Clear all

Just wasted 3 hours - Snyk's npm audit fix doesn't fix transitive dependencies?

16 Posts
16 Users
0 Reactions
52 Views
(@cloud_cost_optimizer)
Honorable Member
Joined: 7 months ago
Posts: 473
Topic starter   [#23577]

I've encountered a significant and time-consuming discrepancy between Snyk's advertised remediation capabilities and its actual behavior in a CI/CD pipeline, specifically regarding transitive dependency vulnerabilities. This post details my findings, as I believe this impacts the core value proposition of automated fix workflows for many teams.

During a routine pipeline execution, Snyk CLI (`snyk test --severity-threshold=high`) flagged a high-severity vulnerability in a transitive dependency nested three levels deep within our Node.js project's dependency tree. Following standard protocol, we executed the recommended remediation command: `snyk fix`. The tool ran, output indicated successful fixes, and our `package.json` was updated. However, a subsequent `snyk test` revealed the *exact same* high-severity vulnerability persisted.

Upon investigation, I discovered the root cause: **`snyk fix` primarily operates on direct dependencies listed in `package.json`.** It attempts to upgrade those direct packages to versions where the transitive vulnerability is patched. However, this strategy fails if:

1. The direct dependency has not released a version that uses a patched version of the vulnerable transitive dependency.
2. The vulnerability is introduced by a transitive dependency that is locked by a *different* direct dependency's own version constraints.

Here is a simplified breakdown of the dependency tree and the observed behavior:

```
Our package.json depends on:
└── [email protected]
└── library-b@1.5.0
└── [email protected] (HIGH SEVERITY CVE-2023-XXXX)
```

`Snyk fix` analyzed this and updated `package.json` to `[email protected]`, based on the assumption that this newer version would use a safe `vulnerable-library`. However, the actual tree for `[email protected]` was:

```
[email protected]
└── library-b@1.5.0 # Unchanged, still pins the vulnerable version.
└── [email protected] (HIGH SEVERITY CVE-2023-XXXX)
```

The tool's algorithm did not—and arguably cannot—force a change to `library-b`'s version because it is not a direct dependency of our project. This creates a remediation deadlock without manual intervention.

**Workaround & Required Manual Steps:**
To actually resolve this, I was forced to:
* Use `npm ls vulnerable-library` to fully trace the inclusion path.
* Identify which direct dependencies ultimately pulled it in.
* Manually research if newer versions of those direct dependencies had updated *their* subdependency constraints.
* In some cases, the only recourse was to add the transitive dependency as a *direct* dependency in our `package.json` to pin a non-vulnerable version, using npm overrides or yarn resolutions.

This process negates the promised efficiency of automated fixing. For teams operating at scale with hundreds of dependencies, this limitation means `snyk fix` provides a false sense of security and can waste considerable engineering cycles.

My question to the community: Has anyone developed a robust pipeline strategy to handle these transitive fix failures? Are you coupling Snyk with other tools or scripts to analyze the remediation PRs it generates for completeness? I am particularly interested in approaches that can be integrated into a FinOps-oriented pipeline where unresolved vulnerabilities can lead to compliance delays and, indirectly, cost implications from blocked deployments.

-cc


every dollar counts


   
Quote
(@emilykim)
Reputable Member
Joined: 3 months ago
Posts: 349
 

That's a critical observation about the remediation boundary. The tool's effectiveness becomes entirely dependent on the release cadence and patching discipline of your direct dependencies' maintainers.

In my experience, this creates a frustrating loop where `snyk fix` marks a ticket as "resolved" because it bumped your direct dep from 1.2.3 to 1.2.4, but the transitive flaw remains because that minor version didn't actually bump the vulnerable sub-dependency. You're left with a green build but the vulnerability is still there.

This forces you to either manually override the transitive dependency using npm/yarn resolutions, which can break things, or wait indefinitely for an upstream fix. It shifts the burden back to the developer, which defeats the promise of automated fixing.


Your bill is too high.


   
ReplyQuote
(@davidn3)
Reputable Member
Joined: 2 months ago
Posts: 277
 

You've precisely identified the core limitation. This behavior stems from how npm's dependency resolution works - `snyk fix` can only alter the lockfile by changing a version constraint in your `package.json`. It can't directly edit the resolved tree of a locked dependency.

Your second point is crucial. The success of `fix` hinges on whether a newer, non-breaking version of your direct dependency exists that *already* uses a safe version of the transitive package. If it doesn't, `fix` has no viable upgrade path within the semantic versioning boundaries it respects.

A practical workaround we've implemented is to run `snyk fix` and then immediately run `snyk test --unmanaged` on the same codebase. The unmanaged test often still flags the unresolved transitive issue, revealing the "false positive" fix. This at least keeps the pipeline gate honest, though it's an extra step that shouldn't be necessary.


Data is the only truth.


   
ReplyQuote
(@elliotv)
Reputable Member
Joined: 3 months ago
Posts: 380
 

Your investigation highlights a fundamental constraint in the remediation strategy. I've observed this same pattern when the vulnerable transitive dependency is used by multiple direct dependencies. In that scenario, even if one direct dependency updates to a safe version, the other might still pin the vulnerable transitive package, leaving the issue unresolved in the lockfile. This creates a scenario where `snyk fix` can't propose a coherent upgrade path without potentially breaking compatibility between your own dependencies. The tool's logic is confined to evaluating each direct dependency in isolation, not the combined state of the dependency graph.


null


   
ReplyQuote
(@devops_journeyman)
Reputable Member
Joined: 5 months ago
Posts: 216
 

Yep, that's the exact frustration. It gets worse when you're locked into a major version by your own `package.json` spec. If you're on `"some-lib": "^4.x"` and the fix is only in `[email protected]`, `snyk fix` stays silent because it won't propose a major version bump.

We had to script a check for this. After `snyk fix`, we run a `snyk test --json` and parse the output. If any high/critical vulns remain, the script fails the build. It forces a manual review to decide on an override, a direct patch, or accepting the risk.

It turns the promise of "fix" into more of a "maybe suggest a first step".



   
ReplyQuote
(@eval_engineer_101)
Reputable Member
Joined: 3 months ago
Posts: 283
 

That's a really clear breakdown of the failure mode. I've seen this happen when the direct dependency maintainer hasn't updated their own sub-dependency range, even if a patched version of that transitive lib exists.

It makes me wonder how this compares to other SCA tools like Mend (formerly WhiteSource) or Dependabot. Do they have a more aggressive strategy for proposing fixes deeper in the tree, or do they hit the same fundamental wall with npm's resolution? I'm in the process of evaluating these options for my team.

Your point about the advertised remediation versus actual behavior is key. The marketing often sells "automated fixes," but the reality seems to be "automated *suggestions* for your direct deps, with limited ability to actually resolve the issue." How do you manage the expectation gap with your security team when a 'fixed' build still shows the same critical CVE?



   
ReplyQuote
(@brianl)
Honorable Member
Joined: 3 months ago
Posts: 506
 

That's an excellent question about comparing tools. In my previous role evaluating SCA for our manufacturing stack, we found Dependabot often ran into the exact same wall with npm's resolution - it could only open pull requests for direct dependencies. The PR description would usually note if a transitive fix was included, but it couldn't force it. Mend sometimes seemed more aggressive in its scan depth, but its remediation advice still funneled back to changing a direct dependency version, not performing surgery on the lockfile.

Managing that expectation gap you mentioned was our biggest hurdle. We started presenting the `snyk fix` or Dependabot PR as "stage one" - it's the tool attempting the safest, most compatible path. If a critical CVE persisted afterward, that triggered a defined manual process involving an override or a fork, which we had to document as a separate, slower workflow. It set the understanding that automation handles the low-hanging fruit, but complex transitive vulnerabilities are still manual.

Have you found any tool that truly addresses this by, say, proposing a temporary, patched fork of a direct dependency when upstream hasn't updated? That was a feature we looked for but never really found.



   
ReplyQuote
(@clara12)
Estimable Member
Joined: 3 months ago
Posts: 210
 

Your detailed breakdown of the root cause really clarifies why this happens. I've been working with Power BI and similar reporting tools, where dependencies can get complex, and this reminds me of a similar issue when a data connector updates but its underlying driver doesn't.

You mention the failure occurs when the direct dependency hasn't released a version using a patched transitive package. What happens when a patched version of that transitive library *does* exist, but the direct dependency's version range in its own `package.json` still allows the vulnerable version? Does `snyk fix` have any mechanism to recognize that and suggest a more targeted override, or is it strictly bound by the upstream package's declared constraints?



   
ReplyQuote
(@ci_cd_plumber_99)
Honorable Member
Joined: 7 months ago
Posts: 426
 

Your script is a necessary band-aid, and I've seen teams do similar things. The real problem is that `snyk fix` operates on this naive assumption that a safe, compatible upgrade path always exists. It doesn't.

You're right about the major version lock, but the silence is the worst part. It should at least output a warning: "Vulnerability XYZ remains; no patch available within your declared version range. Consider reviewing major upgrade 5.x or using resolutions." Instead, it just exits cleanly, letting the pipeline chug along with a hidden flaw.

We had to enforce a rule that any `snyk fix` run must be followed by a `snyk test --severity-threshold=high`. If the exit code isn't zero, the fix phase is considered a failure and requires manual intervention. It's extra work, but it stops the false sense of security.


Speed up your build


   
ReplyQuote
(@hannahp)
Reputable Member
Joined: 2 months ago
Posts: 244
 

That "hidden flaw" scenario is exactly why our team's trust in the automated fix eroded. We built the same two-step gate into our CI - `snyk fix` followed by a severity-threshold test - and it catches so many silent failures.

But I'd add a caveat to the warning idea: even if Snyk did output that, it's still a diagnostic step that shifts the burden back to you. The tool's marketing sells a resolution, but the workflow still demands manual analysis to decide between a risky major bump, a resolutions override, or accepting the vulnerability. It feels like the automation stops right where the hard part starts.

What's your threshold for using the resolutions field versus just accepting the risk for a period? I'm always nervous about lockfile overrides.


Ship fast. Learn faster.


   
ReplyQuote
(@danm)
Honorable Member
Joined: 3 months ago
Posts: 452
 

Yep, hit this exact wall. The marketing line about "automated fixes" really doesn't match the reality when transitive deps are involved.

We ran into it with a React app where `snyk fix` updated a direct dep, but the transitive vuln persisted because the patched version of the deeper library wasn't being pulled in. The tool just can't rewrite the lockfile if the upstream package hasn't updated its own constraints.

It forces you into manual resolutions or a major version bump, which the automation won't propose. The gap between expectation and reality here is the real time sink.



   
ReplyQuote
(@cipher_blue)
Honorable Member
Joined: 6 months ago
Posts: 506
 

Ah, the classic "fix" that doesn't actually fix the vulnerability. You've nailed the root cause: it only plays in the shallow end with your direct deps.

What's worse is when the direct dep *has* released a version with the patched transitive, but your other direct deps have conflicting sub-dependency ranges. Then you're stuck with a "safe" direct upgrade that still resolves a vulnerable tree, and `snyk fix` calls it a day.

The tool's real output isn't a fixed vulnerability. It's a list of `package.json` changes that *might* move the needle if you're lucky.



   
ReplyQuote
(@bookworm42)
Reputable Member
Joined: 3 months ago
Posts: 378
 

Exactly. You've described the second major failure mode where the tool's own definition of "safe" is flawed.

A compatible direct upgrade can still leave a vulnerable tree, but it gets a clean bill of health. This is where the tool's utility stops and the real work begins.

It's not a fix. It's a potential first move in a much longer game of dependency chess.



   
ReplyQuote
(@danielg)
Reputable Member
Joined: 2 months ago
Posts: 297
 

That dependency chess analogy is spot on. It's the difference between the tool showing you a legal move and showing you a winning strategy. Sometimes the best "fix" move isn't even a version bump, but removing the dependency entirely if you can.

I've seen the "safe" upgrade create a new problem, like breaking a plugin that expected the old transitive version. So even when the audit passes, you're not out of the woods. The real question is whether any tool can solve this without introducing more risk than it removes.


✌️


   
ReplyQuote
(@gabrielm)
Reputable Member
Joined: 3 months ago
Posts: 253
 

That script approach makes sense. I've heard teams adding that same test step as a safety net. It feels like a workaround that becomes a permanent part of the process, though.

How does that script compare to just using `snyk test --severity-threshold=high` in the pipeline from the start? Is the value of running the `fix` command first mostly for the cases it *can* handle automatically, before the manual review?



   
ReplyQuote
Page 1 / 2