Turning Complex Problems Into Confident Technology
One vulnerable package, four repositories, four different fixes
Security and secrets · Production · Inside the Workshop
The problem
On 13 October 2026, security advisories were published against two .NET cryptography packages, System.Security.Cryptography.Pkcs and System.Security.Cryptography.Xml. The two on Pkcs (GHSA-846f-f764-76mx, rated high, and GHSA-2fmv-rv2c-59x7, rated moderate) are both denial-of-service issues, fixed on the 9.x line in 9.0.21.
None of my code used these packages directly. They arrived underneath other libraries: the SQL Server client, the Microsoft identity library, a third-party UI component suite. All four of my .NET repositories had them somewhere in the dependency tree.
Three of those repositories treat NuGet's audit warnings as errors. In my internal dashboard app, a build passed at 23:48 and the next run failed, with nothing in the repository changed. Every branch, including main, broke at once. The fourth repository, this site's own API, reported the advisories as build warnings.
The problem was the same everywhere. The right fix was different in each.
This site's API: one pin in one place
The API is four projects, and the audit found Pkcs 9.0.13 in all four. I took every available minor and patch update first, 25 of them, hoping an upstream update would clear it. It did not. The newest SQL Server client, 7.1.1, only moved its own dependency to Pkcs 9.0.18, which is still inside the vulnerable range.
The SQL client was the only package bringing Pkcs in, and every project inherits from one shared project. So the narrowest fix was a single direct reference to Pkcs 9.0.21 in that shared project. I chose 9.0.21 over 10.0.13, which is also patched, to stay on the major line the SQL client was built against. A comment beside the reference says to remove it once a SQL client release depends on 9.0.21 or later. The test suites ran the same before and after: 820 and 1,627 passed, none failed.
The mailbox digest: the same pin, a different file
The mailbox digest function pulls Pkcs in through the same SQL client, so the reasoning matched. Upgrading the SQL client was the fix I wanted, but no release of it fixes this. The difference was where the pin lives. That repository has no central package management, and every version sits in the project file, so the pin went there too, with the same "remove when" comment.
The dashboard app: a direct reference was refused
The dashboard app brings both packages in through the Microsoft identity library, and it was hit by five advisories: the two on Pkcs and three on Xml. My first attempt was the same direct reference. It failed outright. System.Security.Cryptography.Xml ships as part of the .NET 10 shared framework, and NuGet rejects a direct reference to it with error NU1510.
The mechanism built for this case is central transitive pinning: a setting in the repository's central package file that lifts the version of a package nothing references directly. It covered both packages, and the test project, in one place.
That is a broader change than a version bump, and the commit says so. With the setting on, every version in the central file now pins transitive dependencies as well as direct ones. It is the intended use of the setting, and it is what makes this kind of advisory fixable, but anyone reading the change later should know it changed how the whole repository resolves packages.
I then updated all 17 outdated packages in that repository, and checked that the pins were still needed instead of assuming the updates had made them redundant. With the pins removed, the newest identity library still resolved the vulnerable Pkcs. The audit was clean only with them in place.
The tool server: pins in the three projects that need them
My internal tool server is split into several projects, and three of them pull the packages in by three different routes. The pins went into exactly those three, and the test projects inherit them through their references.
What every fix had in common
None of them switched the audit off. Suppressing NuGet's audit warning would have turned every build green in a minute. It would also have silenced the next advisory as well as this one, which is the opposite of what the audit is for. Each fix lifts one package to its first patched version, at the narrowest point the repository's layout allows, and records when it can be removed.
A JavaScript footnote
After the .NET work, npm audit on the website still reported a high finding in compression, reached through the local web server package serve, which pins compression to exactly the vulnerable version.
npm's offered fix was npm audit fix --force, which would install serve 10.0.2. That is a downgrade. The project was already on serve 14.2.6, the latest release, and every version from 10.1.0 to 14.2.6 carries the same pin. Version 10.0.2 is from the 2018 line. Taking it would have swapped one advisory for years-old dependencies and an untested change to how the site is served during its Lighthouse and Playwright checks.
Instead, a scoped override tells npm to use the patched compression 1.8.2 for serve only. Nothing else in the tree is forced, and npm audit now reports no vulnerabilities.
A side lesson about test runners
One of the dashboard updates was a major version of the test runner. Its failure mode is silence: a runner that stops discovering tests reports success, having run nothing. The only way to know it still works is to check that the count of tests that ran has not changed. It still reported 13 passed, the same as before.
What to take from this
- A build can break overnight with no change of yours. If audit warnings are errors, a published advisory is enough. That is the setting doing its job, so plan for it.
- Fix where the dependency enters. Find which package brings the vulnerable one in, and pin at that point, in the place your repository keeps versions.
- Read the suggested fix before running it. A package manager's "fix" can be a downgrade.
- Keep the audit on. Suppression clears today's error and every future one with it.
- Leave a note saying when the pin can go, or it will outlive its reason.
Related
If this is your problem too
Dependency drift is one of the quieter costs of keeping a system healthy, and it is part of what I look at under maintainability. How a build pipeline can report success that means less than it seems is in a green health check is not a deployment.
Got a problem like this?
A free 20-minute call: you’ll know whether I can help, what is probably wrong, and what the next step involves. No obligation.