Hangfire
Hangfire copied to clipboard
Build warning for dependency Newtonsoft.Json 11.0.1
.NET 9 now generates build warnings for known vulnerabilities in transitive dependencies. This causes build failures if you treat warnings as errors.
Warning NU1903 : Package 'Newtonsoft.Json' 11.0.1 has a known high severity vulnerability, https://github.com/advisories/GHSA-5crp-9r3c-p9vr
I have no reason to believe this vulnerability can be exploited via Hangfire.
dotnet nuget why output:
[net9.0]
│
└─ MyProject (v1.0.0)
├─ Hangfire.AspNetCore (v1.8.15)
│ └─ Hangfire.NetCore (v1.8.15)
│ └─ Hangfire.Core (v1.8.15)
│ └─ Newtonsoft.Json (v11.0.1)
├─ Hangfire.PostgreSql (v1.20.10)
│ └─ Hangfire.Core (v1.8.15)
│ └─ Newtonsoft.Json (v11.0.1)
├─ Hangfire.Pro (v3.0.4)
│ └─ Hangfire.Core (v1.8.15)
│ └─ Newtonsoft.Json (v11.0.1)
└─ Hangfire.Pro.Redis (v3.0.10)
└─ Hangfire.Core (v1.8.15)
└─ Newtonsoft.Json (v11.0.1)
Repro steps:
- Install .NET 9 SDK.
- Build a
net9.0ornet8.0project containing a reference to Hangfire 1.8.15.
This is a general issue with NuGet itself described in this issue on GitHub: https://github.com/NuGet/Home/issues/5553, feel free to upvote it. The problem is that NuGet doesn't allow developers or package authors to specify the dependency resolution behavior when installing packages. The best possible solution here currently is to install the Newtonsoft.Json or other package explicitly in your application project:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Hangfire specifies minimum required version, and all the future versions are still compatible, there are even unit tests that point to Newtonsoft.Json 13.0.3 package. Every bump of a dependency package is a breaking change and completely unnecessary, since it will force to a never-ending race with version bumps.
Duplicates #2202.
I totally agree with you concerning the sentence: "Every bump of a dependency package is a breaking change...", and also the fact that "...it will force to a never-ending race with version bumps." At the same time, I think that when a security issue is found on a specific version of a referenced library (I assume that this is the case with Newtonsoft.Json 11.0.1), all the libraries which use that unsecure version should be updated to the next secure version, if the latest is not an option, resolving the potential breaking changes issues caused by the update; or at least, this is how I do in my projects that of course, are not so widely used as your, hence I got the complexity of applying this approach.
I understand that including that:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
will force the build process to use that version, but I'd prefer avoid that workaround because actually I don't use directly Newtonsoft.Json in any of my projects, since I switched them to use the native System.Text.Json a long time ago and I would remain like that.
I think that between a never-ending race with version bumps and leaving the versions of external references as is forever, there is a "middle way", that in my opinion is: update them when it is necessary; and in this way, especially because there should be no breaking changes issues for this specific case: you continue to guarantee a safe solution without introducing issues for your users.
I agree with the above. While bumping too often can be annoying if someone also has a direct dependency on a transitive library from your project (and thus needs to bump when you bump), there is a happy middle-ground where you bump every so often to a more recent version when tangible benefits present themselves in your dependencies (performance, security, features, etc). I don't want to have to worry about what transitive versions of dependencies are referenced if it's not something I am using directly - the libs I am referencing should pick minimum versions that work well for them without causing any issues.
While I agree that bumping all the dependencies whenever new versions are available for them will make it easier for some to stay on the edge and have no automated security-related warnings, for others it can end in a dependency hell, when a new version of Hangfire.Core or other package can't be used together with other packages like in the following case:
Hangfire.Core: Newtonsoft.Json ≥ 13.0.3 Application.Library: 11.0.0 ≥ Newtonsoft.Json < 12.0.0
And if every dependency should be installed with its latest version, I honestly don't understand why we need to specify versions when authoring NuGet packages.
Previously
Previously, with the packages.config-based implementation that existed before .NET Core days, it was possible to specify the dependency resolution behavior when installing a package, and with its "Highest" option we always received the highest supported versions for dependencies. Nowadays, we can't specify this when using PackageReference, and there's a long-standing issue in this in the NuGet repository – https://github.com/NuGet/Home/issues/5553, and I would suggest to actively vote for it to restore the same semantics.
Paket manager
There's another package manager available in the .NET world, Paket. And while I don't encourage everyone switch to it, its behavior can also prove my point of view. Today I installed it and specified the following manifest file:
source https://api.nuget.org/v3/index.json
nuget Hangfire.Core
nuget Hangfire.InMemory
So when I'm installing packages, I get Newtonsoft.Json 13.0.3 version as expected. And if I specify Newtonsoft.Json 12.0.2 (for example) explicitly in the manifest (for some reason), it's installing that version instead, and Hangfire.Core still can work with it, since it supports all the preceding version.
PS C:\Users\SergeyOdinokov\RiderProjects\ConsoleApp3> dotnet paket install
Paket version 9.0.2+a9b12aaeb8d8d5e47a415a3442b7920ed04e98e0
Resolving dependency graph...
Updated packages:
Group: Main
- Hangfire.Core: 1.8.15 (added)
- Hangfire.InMemory: 1.0.0 (added)
- Microsoft.CSharp: 4.7.0 (added)
- Microsoft.NETCore.Platforms: 7.0.4 (added)
- Microsoft.NETCore.Targets: 5.0.0 (added)
- Microsoft.Win32.Primitives: 4.3.0 (added)
- NETStandard.Library: 2.0.3 (added)
- Newtonsoft.Json: 13.0.3 (added)
- Owin: 1.0.0 (added)
- runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl: 4.3.3 (added)
What to do
Libraries don't have "specified" versions of their dependencies, they have "supported" versions. And they don't and shouldn't control them, since target applications may already have these dependencies for other reasons. And without full support from package managers that are responsible for resolving these dependencies, we'll always end with workarounds – either by installing highest versions manually in specific applications (causing local issues) or globally in library packages themselves (causing global issues).
I encourage everyone to vote for the referenced issue in the NuGet repository, putting all the energy there to solve the problem, instead of implementing more and more workarounds.
Otherwise we'll end with every library that specifies highest dependencies, and we'll be forced to update the whole project every time we just bump a patch version of a specific library.
Hi @odinserj, I'm totally agree with your previous post, that is crystal clear and it is valid in general. What I would like to point out here, that is I think the reason why this thread has been opened, is a very specific issue: that is that the minimal version of Newton.Json (11.0.1), referenced by Hangfire.Core and only for who is using it with netstandard2.0 projects, has been deprecated because it contains known high severity vulnerability (https://github.com/advisories/GHSA-5crp-9r3c-p9vr).
As far as I can see from the Hangfire.Core.csproj file:
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'"> <PackageReference Include="Microsoft.CSharp" Version="4.4.0" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.1" NoWarn="NU1903" /> </ItemGroup>
We could just update the version to 13.0.1, that should be the first version without known vulnerabilities:
What you suggest makes perfectly sense, but at the same time, imho a public library should at least declare its own references ensuring their security when possible. Of course, maybe someone that is referencing directly Newton.Json with an old version, after upgrading Hangfire to the new version with inside an updated Newton.Json version will break, but that is caused not because we just trying to pursuit the latest version released, but to ensure the safety of all the libraries involved.
I hope I can be understood, what I mean is that there are situations where upgrading a library, potentially causing breaking changes to the users which use it, is somehow more acceptable than leaving everything as is, but keeping unsecure references that at least personally I try to avoid whenever possible. Even with a more suitable packet manager, which could "automagically" fix this issue, I would still prefer to have the library I use referencing exclusively not vulnerable versions.
But what to do if there's a company that has an application that's using Newtonsoft.Json of a specific outdated version, and upgrading to the latest version will cause compile-time (that's even ok) or run-time errors (more difficult to detect) for the application itself? So they updated Hangfire.Core and get an unwanted side-effect of changing their application behavior.
For example, a similar problem was with the Microsoft.Data.SqlClient (it's a dependency of Hangfire.SqlServer) package that introduced a breaking change in version 4.0.0 that ended with connectivity errors for many. When we update such package explicitly, we at least can track the source of the problem, but with a side-effect update it's not so clear. Also, some newest versions of packages simply have bugs, and it sometimes takes time to fix them.
The problem is that it's not rare for packages to fix security vulnerabilities only in their latest versions, leaving all the previous versions unfixed. And it's not clear when to bump such dependencies if we decide to have always non-vulnerable dependencies. Is it allowed in patch versions? No one expects big changes in patch versions. Is it allowed only in minor or major versions? It will take time, and workaround is still to manage transitive dependencies, until the new version is released.
Hangfire also have some internal dependencies that are merged and internalized, like Microsoft.Owin (for .NET Framework packages) or Dapper for Hangfire.SqlServer. They all have non-vulnerable versions, and updated under the scenes, since they are used internally and don't affect the target application.
But for external dependencies that can be already installed in a target application, I'm not sure that a framework can dictate package updates. Transitive dependencies management is required anyway to stay secure, and it perfectly solves the problem of outdated and unsecure dependencies, and is fully available. And at the same time we are sure that we can solve bugs in one package, leaving others untouched.
And with something like the following we can have advantages of both worlds, when our package manager supports this:
<PackageReference Include="Hangfire.Core" Version="1.8.15" DependencyBehavior="Highest" />
Well, I think that when a developer decides to update its projects and find something that is changed in the references, it should have all the tools to update and/or fix what could happen then; maybe I'm too optimistic, but in my experience if you are not able to update a project because of an updated version of a referenced library (and in this case we are talking about "nothing"), maybe that project has several issues and it should remain untouched at all.
Anyway, while I can agree with you if we are talking about "major systems", in this specific case we are dealing with an update that fixes known vulnerabilities, which I'm confident that won't introduce breaking changes, since as you wrote previously: "there are even unit tests that point to Newtonsoft.Json 13.0.3 package". That's said, in my project I have just this references:
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.15" />
<PackageReference Include="Hangfire.SqlServer" Version="1.8.15" />
Again, I wouldn't like to force my csproj to include:
<PackageReference Include="Hangfire.Core" Version="1.8.15" DependencyBehavior="Highest" />
I could accept to have the "DependencyBehavior" property in my direct references, but still I think it's like "cheating" in this scenario because yes: I eventually fix my vulnerability issue, but the real thing is that a library that I use is declaring to accept a vulnerable library instead of update it.
From my point of view (and of course, it is just my thought), with this approach you worry more about maintaining dated systems, rather than supporting projects that are always up to date and maintained to the state of the art. I understand this is only my issue (maybe someone else is in a situation like mine), but after upgrading to net core 9, I was forced to set to false the "TreatWarningsAsErrors" property in a solution of 76 projects only for a project that is referencing Hangfire, due to that Newtonsoft.Json vulnerability...and this is really frustrating for me, especially because I really can't see a drawback updating that reference; that's it.
I think that when a developer decides to update its projects and find something that is changed in the references, it should have all the tools to update and/or fix what could happen then; maybe I'm too optimistic, but in my experience if you are not able to update a project because of an updated version of a referenced library (and in this case we are talking about "nothing"), maybe that project has several issues and it should remain untouched at all.
Yes, but there are legacy projects (with almost no time budget) that you prefer to leave untouched, except one library that solves 1 bug that started to appear after running this project smoothly after 10 years. Unfortunately this happens sometimes.
Anyway, while I can agree with you if we are talking about "major systems", in this specific case we are dealing with an update that fixes known vulnerabilities, which I'm confident that won't introduce breaking changes, since as you wrote previously: "there are even unit tests that point to Newtonsoft.Json 13.0.3 package".
There are no breaking changes for Hangfire itself, and there are no breaking changes to your project, but this doesn't mean there are no breaking changes for someone else's project.
From my point of view (and of course, it is just my thought), with this approach you worry more about maintaining dated systems, rather than supporting projects that are always up to date and maintained to the state of the art. I understand this is only my issue (maybe someone else is in a situation like mine), but after upgrading to net core 9, I was forced to set to false the "TreatWarningsAsErrors" property in a solution of 76 projects only for a project that is referencing Hangfire, due to that Newtonsoft.Json vulnerability...and this is really frustrating for me, especially because I really can't see a drawback updating that reference; that's it.
I understand, my friend. But what if include the Newtonsoft.Json 13.0.3 package to your project instead of disabling the TreatWarningsAsErrors (that's completely unacceptable, I agree), until there are changes in the NuGet client itself that install/restore highest dependency versions by default when you have no transitive dependency installed explicitly in your project, or at least gives some way to specify this behavior manually?
To be honest, I'd be happy to reference only the newest package versions and stop supporting older frameworks, but there are still companies and projects that use them. Once that legacy prevents from moving forward, I'd drop such support, but this problem itself is bigger than Hangfire, and relate to other packages as well.
So I'm still suggesting to upvote the referenced feature for NuGet. Once we understand it will never be implemented, I would be happy to join the bumping race, but in my opinion it would be irresponsibly towards the NuGet ecosystem.
https://github.com/NuGet/Home/issues/5553
We are talking about a 6 year old package with security vulnerabilities here. There is no reason for new hangfire packages to support that. If they really need to use 6 year old vulnerable packages they can use 6 year old hangfire packages too 😆
First of all, thank you @odinserj for your patience and support. Concerning your suggestion:
I understand, my friend. But what if include the Newtonsoft.Json 13.0.3 package to your project instead of disabling the TreatWarningsAsErrors (that's completely unacceptable, I agree), until there are changes in the NuGet client itself that install/restore highest dependency versions by default when you have no transitive dependency installed explicitly in your project, or at least gives some way to specify this behavior manually?
Of course, it will be my less worse option to workaround my issue and, for sure I'll upvote the referenced feature for NuGet (https://github.com/NuGet/Home/issues/5553), because I'm convinced that is a good thing. Nevertheless, as I've already written and I hope to be crystal clear and understood: even with the new package manager behavior, imho having a reference to a vulnerable library is a mistake, especially if we consider that (as @mikernet pointed out), we are talking about a 6 years old library.
Finally and again, between "reference only the newest package versions and stop supporting older frameworks" and leave all the references as is forever and ever, there should be a middle way where vulnerable libraries should be deprecated and updated, for a greater good.
We are talking about a 6 year old package with security vulnerabilities here. There is no reason for new hangfire packages to support that. If they really need to use 6 year old vulnerable packages they can use 6 year old hangfire packages too 😆
Yes, it seems a very niche case that someone would update project A to latest Hangfire version but then never update project B's Newtonsoft.Json in 6 years when project B depends on project A, but I guess it's possible
Also, there are not many breaking changes between 11.x and 13.x https://github.com/JamesNK/Newtonsoft.Json/releases
I understand the rationale behind wanting to wait until Hangfire 2.x, myself I would not be surprised by if going from Hangfire 1.8 to 1.9 it increased a very stable dependency like Newtonsoft.Json from 11.x to 13.x
tldr; Hangfire 2.x lets go
I have just committed a change to Hangfire.Core itself to set the MaxDepth property to its JsonSeriarlizerOptions objects, so it's safe to use whatever version of Newtonsoft.Json we have. This "unsafe defaults" is the only vulnerability in Newtonsoft.Json, and it's related to StackOverflowException exception when deeply nested structures are serialized, please see https://github.com/advisories/GHSA-5crp-9r3c-p9vr for details.
I will also bump Newtonsoft.Json to version 13.0.1 in the upcoming Hangfire 1.9.0 release (which will be started soon) as a dependency for a new platform, either net6.0 or net8.0 as other bumps already performed with this library. So both backward compatibility + safe defaults will be persisted, and we'll get rid of transient package warning on the .NET 9.0 platform.
We are talking about a 6 year old package with security vulnerabilities here. There is no reason for new hangfire packages to support that. If they really need to use 6 year old vulnerable packages they can use 6 year old hangfire packages too 😆
I agree with this. One of the great things about NuGet is that packages remain available, so pushing updates doesn’t inherently or automatically break existing applications. Of course, if someone updates packages after years of inactivity, they should expect some issues.
It’s the same situation with migrating to System.Text.Json—just rip the Band-Aid off and move forward. Yes, there will be challenges, but that’s part of the process. Updates always come with potential downsides and trade-offs, which is exactly why semantic versioning exists. If we aimed for perfection with every change, nothing would ever get done (which seems to have been the case for years now regarding these two items).
I have also posted a comment in the NuGet repository regarding the possible solution for this problem. It is still supported by the NuGet Client itself, but currently it is not possible to use this feature when dealing with PackageReference tags in csproj files. The proposed solution is to enable support for the DependencyVersion attribute when referencing a package, and pass it to the NuGet client.
https://github.com/NuGet/Home/issues/5553#issuecomment-2700436516
Please upvote the referenced comment, as it will improve the NuGet ecosystem as a whole, since not only Hangfire packages suffer from this. Otherwise package authors will be required to bump dependencies every time a new vulnerability is discovered, and application developers will need to do the same, sometimes even for a single bug fix.
@odinserj I appreciate the effort and agree that improving the NuGet ecosystem is a worthy goal—but I think it's important to clarify that NuGet’s dependency resolution model is fundamentally different from many other package managers. It prioritizes project-wide compatibility over strict "latest version" resolution, which is actually a big reason why https://github.com/NuGet/Home/issues/5553 has been stale for years (likely because the decision makers are aware of this, but not so much the consumers).
The reality is, even if Hangfire bumps a dependency version, NuGet may still not choose that newer version in a consuming project—especially if other referenced packages impose stricter constraints, or if the APIs across versions remain compatible. Version bumps don’t force updates unless there’s a conflict or an explicit upgrade path.
There is a viable solution already: Hangfire can safely bump dependencies without breaking anything. Thanks to NuGet's design, this doesn’t cause consumer chaos. Developers still have full control over the resolved versions, and projects with older constraints will simply continue using what works for them.
Expecting package authors to hold back progress for the sake of decade-old dependencies isn’t sustainable. Encouraging ecosystem health means moving forward—deliberately and responsibly—not staying frozen in time because some projects never updated.
The reality is, even if Hangfire bumps a dependency version, NuGet may still not choose that newer version in a consuming project—especially if other referenced packages impose stricter constraints, or if the APIs across versions remain compatible. Version bumps don’t force updates unless there’s a conflict or an explicit upgrade path.
Sorry, it seems to me I don't understand this paragraph. So, for example, if Microsoft.EntityFrameworkCore.SqlServer references Microsoft.Data.SqlClient of version 6.0.0, and my application project references Microsoft.Data.SqlClient of version 3.0.0 (and I don't want it to be changed, because I know there are breaking changes in 4.0.0 and don't have time to fix them), will the resulting version of Microsoft.Data.SqlClient remain 3.0.0 in the application project after adding that EF package?
There’s a document that explains this more: https://learn.microsoft.com/en-us/nuget/concepts/dependency-resolution
Transitive restore applies four main rules to resolve dependencies: lowest applicable version, floating versions, direct-dependency-wins, and cousin dependencies.
For example, if Microsoft.EntityFrameworkCore.SqlServer references Microsoft.Data.SqlClient version >= 6.0.0, and your application project explicitly references version 3.0.0, NuGet will issue a warning and fail the restore unless an override is used—because 3.0.0 does not satisfy the >= 6.0.0 constraint.
Let’s use real versions. Microsoft.EntityFrameworkCore.SqlServer version 3.1.32 depends on Microsoft.Data.SqlClient >= 1.1.3. If you install 3.1.32, NuGet will retrieve Microsoft.Data.SqlClient >= 1.1.3, but it won't automatically select a newer version unless another dependency or direct package reference requests it. If you explicitly reference Microsoft.Data.SqlClient version 2.1.4, that version would be selected, assuming it satisfies all constraints.
Now, regarding Newtonsoft.Json in Hangfire:
- With the current package references, Newtonsoft.Json version 11 will be used unless another package or project explicitly requests a newer version. That means consumers may miss out on bug fixes and improvements.
- If Hangfire bumps its minimum version to 13, and a consumer project references version 11, NuGet will resolve to 13. However, if a transitive dependency has a constraint that disallows versions >12 (which is rare), the newer Hangfire package wouldn’t be installable without resolving that conflict. But in that case, the constraint issue lies with the other package, not with Hangfire.
The main point is: we shouldn’t be afraid to update dependencies out of fear of breaking consumers. NuGet’s model protects consumers, and older versions are always available unless they deliberately opt into newer ones—usually by upgrading their own dependencies.
For example, if Microsoft.EntityFrameworkCore.SqlServer references Microsoft.Data.SqlClient version >= 6.0.0, and your application project explicitly references version 3.0.0, NuGet will issue a warning and fail the restore unless an override is used—because 3.0.0 does not satisfy the >= 6.0.0 constraint.
Correct, NuGet will issue an error and will disallow this installation. But I don't understand how this can be overridden. Without this understanding, the paragraph above contradicts with the following one.
The reality is, even if Hangfire bumps a dependency version, NuGet may still not choose that newer version in a consuming project—especially if other referenced packages impose stricter constraints, or if the APIs across versions remain compatible. Version bumps don’t force updates unless there’s a conflict or an explicit upgrade path.
Let’s use real versions. Microsoft.EntityFrameworkCore.SqlServer version 3.1.32 depends on Microsoft.Data.SqlClient >= 1.1.3. If you install 3.1.32, NuGet will retrieve Microsoft.Data.SqlClient >= 1.1.3, but it won't automatically select a newer version unless another dependency or direct package reference requests it. If you explicitly reference Microsoft.Data.SqlClient version 2.1.4, that version would be selected, assuming it satisfies all constraints.
In this example, the application project uses a higher version of Microsoft.Data.SqlClient package than the referenced package. This example is expected and obvious, but turns my example inside out.
With the current package references, Newtonsoft.Json version 11 will be used unless another package or project explicitly requests a newer version. That means consumers may miss out on bug fixes and improvements.
Yes, and this behavior should be possible to override by package consumers on NuGet's side as it was possible previously. This problem doesn't relate only to Hangfire packages, and there are other evidences in the referenced topic. And a lot of users upvoted for that issue.
If Hangfire bumps its minimum version to 13, and a consumer project references version 11, NuGet will resolve to 13. However, if a transitive dependency has a constraint that disallows versions >12 (which is rare), the newer Hangfire package wouldn’t be installable without resolving that conflict. But in that case, the constraint issue lies with the other package, not with Hangfire.
Newtonsoft.Json doesn't depend on other projects and doesn't contain breaking changes even between major versions, which is very rare. And I'm trying to understand what to do with other dependencies -- Hangfire.SqlServer might (it's now using reflection for other reasons, to be compatible with both older System.Data.SqlClient) depend on Microsoft.Data.SqlClient, which had breaking changes in the past.
The main point is: we shouldn’t be afraid to update dependencies out of fear of breaking consumers. NuGet’s model protects consumers, and older versions are always available unless they deliberately opt into newer ones—usually by upgrading their own dependencies.
I see a lot of community-based packages for Hangfire just reference the most recent Hangfire.Core version and bump it every time a new version of Hangfire.Core is released. This is not an expected version management strategy, and while it can work for a small subset of extensions, it will turn release management into nightmare.
it's important to clarify that NuGet’s dependency resolution model is fundamentally different from many other package managers
Updating a single package just to fix a few bugs should make cascade changes in the whole application. Newtonsoft.Json is an exception, but the problem started to exist in the ecosystem itself after switching to PackageReference-based configuration, and amplified by an increased security vulnerability awareness with the appearance of new tools.
Bumping dependency versions is fighting with a symptom, not with a real problem.
Correct, NuGet will issue an error and will disallow this installation. But I don't understand how this can be overridden. Without this understanding, the paragraph above contradicts with the following one.
https://devblogs.microsoft.com/dotnet/introducing-central-package-management
I see a lot of community-based packages for Hangfire just reference the most recent Hangfire.Core version and bump it every time a new version of Hangfire.Core is released. This is not an expected version management strategy, and while it can work for a small subset of extensions, it will turn release management into nightmare.
It's not a nightmare, though. I use automated dependency updating (across multiple Hangfire libraries), and I've never encountered versioning conflicts. In fact, it’s quite nice from both an authoring and consumption standpoint. Everything runs on the latest versions, and tests ensure it all compiles and works before publishing.
Can you give a concrete example of how this becomes a nightmare?
Either consumers want the new features and are willing to upgrade, or they don’t and stick with their current version. That’s a healthy, flexible system.
NuGet does the heavy lifting to resolve transitives. If something pulls the solution forward, it's not a surprise—it's expected. That’s where semantic versioning comes in. If a breaking change is introduced, bump the major version to signal this, and let automated tools manage it accordingly.
Updating a single package just to fix a few bugs should make cascade changes in the whole application. Newtonsoft.Json is an exception, but the problem started to exist in the ecosystem itself after switching to PackageReference-based configuration, and amplified by an increased security vulnerability awareness with the appearance of new tools.
Isn’t that awareness a good thing? Package management is better than ever. Do you remember the pain of binding redirects? That was genuinely awful.
Look—I get that I may not change your mind, but I hope you’ll consider this question openly:
What are the actual, non-theoretical downsides to upgrading your packages to the latest versions?
You pull the rest of the solution forward, and they have to make changes to use the new library? That can be looked at as a positive, not a negative.
I’d bet that in most real-world scenarios, it's rarely a problem.
If someone doesn’t want to upgrade, they don’t have to—that version will still be available. Security fixes are a separate concern, and I absolutely understand the need to backport those. But when it comes to feature development, why hold the project back?
It’s my opinion that the lowest-possible package versioning methodology is holding the entire software ecosystem back—and I’d wager it’s also a major source of future (currently unknown) security vulnerabilities.
Wrapping up the thread.
The only reported vulnerability of Newtonsoft.Json that's recorded and flagged by analyzers is https://github.com/advisories/GHSA-5crp-9r3c-p9vr, which is about throwing StackOverflowException when serializing highly nested recursive objects that can bring the process down.
However, only specific use case was fixed in Newtonsoft.Json 13.0.1, involving serialization of JObject type instances. For cases with serializing of other types, including custom recursive types that can be passed as arguments in Hangfire, Newtonsoft.Json of any version, including the newest ones, still can throw StackOverflowException.
The problem above is reported in https://github.com/JamesNK/Newtonsoft.Json/issues/3010, but not fixed and will not be fixed, according to this comment:
Somebody reported this issue to the .NET security team. Our official stance is that this issue does not constitute a security vulnerability because no trust boundary has been crossed. The shape of the object to be serialized originates entirely within the trusted caller (the web app itself). While it's true that the web app might take influences from external sources, the fact remains that the web app itself is fully accountable for controlling the shape of the input object, and the web app bears ultimate responsibility for validating the shape of that object before using it if it has been influenced by external sources. That makes this a caller (web app business logic) concern, not a callee (Newtonsoft) concern.
This is distinct from https://github.com/JamesNK/Newtonsoft.Json/pull/2462 (deserialization), where the input is expected to have come from the other side of a trust boundary. This critical distinction means that these two issues shouldn't really be compared with one another.
So, there's no version currently available that will save us from StackOverflowException, and no such updates are planned as for now, because such serialization (according to .NET security team) does not cross the trust boundaries, which is also the case for Hangfire and its background jobs.
So, the commit with bumping Newtonsoft.Json for .NET 6.0 platform is all about workaround to avoid false alerts from security tools. This is a potential breaking change, and should be handled by NuGet Package Manager itself to give developers control what version to use and make decisions based on their requirements, as I told above, and is landed only as a compromise from my side.
.NET 9 enabled transitive dependency checks, but then rolled back this decision. .NET 10 will re-enable transitive dependency checks again, so I'm expecting a lot of interest over this issue. So NuGet installs transient dependencies with the Lowest Applicable Version rule (it's own rule) first, and then expects that the highest versions are installed, when running NuGet Audit.
And here are the real roots of the problem, with a lot of cases and evidences:
- https://github.com/NuGet/Home/issues/5553 issue to give developers control over transitive dependencies.
- https://github.com/JamesNK/Newtonsoft.Json/issues/3010 to avoid having
StackOverflowExceptionwhen serializing (very) deeply nested data structures.
This is not an issue on Hangfire's side, regardless of the referenced Newtonsoft.Json versions age – there are other transient packages, and I can't bump them once a new version is available of any transient package. All internalized packages (which are safe to upgrade) are on their non-vulnerable versions, like Microsoft.Owin. NuGet packages are not NPM packages.
@odinserj I’m disappointed that you’re not addressing this or answering my questions - especially when contributors are ready to step in, yet the concern over a “breaking change” seems to block any progress.
How much longer will you spend defending the decision not to update? Even if you’re confident there’s no vulnerability now, what about future edge cases? No one has asked you to ignore updates; yet there are dozens of issues calling this out, ready pull requests, and Hangfire is now blocked in environments by policies that require non-vulnerable libraries.
We’re moving to https://github.com/Arcenox-co/TickerQ. This isn’t due to any confirmed vulnerability; rather, we need a project whose maintainers proactively review and merge community contributions, stay ahead of potential security risks, and respect the growing compliance requirements of enterprise environments. Continuing to dismiss valid concerns and ignore pull requests feels like moving backwards, so we can’t justify staying where updates are treated as optional and feedback goes unanswered.