Applying a security patch is only half of the job.
The other half is proving that the patched application still behaves correctly.
This becomes especially important for teams supporting multiple .NET versions. Microsoft regularly ships servicing updates containing security and non-security fixes. For example, the July 2026 servicing release included security fixes across .NET 8, .NET 9, and .NET 10, with corresponding releases 8.0.29, 9.0.18, and 10.0.10.
A security update can therefore create an operational question:
Security fix available
↓
Application patched
↓
Does everything still work?
A good deployment pipeline should answer that question automatically.
The objective is not to avoid security updates because they might cause regressions. The objective is to make regression detection part of the patching process.
What Is a Security Regression?
A security regression occurs when a security-related change fixes one problem but unintentionally causes another problem in application behavior, compatibility, performance, or deployment.
Consider an API application:
Before patch
↓
Authentication works
↓
API requests succeed
↓
Background jobs run
After patch
↓
Security issue fixed
↓
Authentication still works?
↓
API requests still succeed?
↓
Background jobs still run?
The security fix itself may be correct while an application dependency, configuration, or runtime interaction behaves differently after the update.
This is why simply checking:
dotnet build
is not sufficient.
A successful compilation proves that the code builds. It does not prove that the production workload is still secure and functional.
Start With a Known-Good Baseline
Before changing a runtime or dependency, capture the current state.
For example:
Application: Orders API
Target Framework: net8.0
Runtime: 8.0.x
SDK: 8.0.x
Container: production image
Tests: 1,248 passed
API smoke tests: Passed
Startup time: 820 ms
This baseline gives the pipeline something to compare against.
Without a baseline, detecting a regression becomes subjective.
A useful baseline can include:
Build status
Unit test results
Integration test results
API smoke tests
Startup behavior
Health checks
Authentication flows
Database connectivity
Important performance indicators
Security scan results
Do Not Confuse SDK Version With Runtime Version
One of the easiest mistakes in .NET patch validation is checking the wrong version.
The SDK controls development and build tooling, while the application runtime is a separate concern. Microsoft explicitly documents that global.json controls SDK selection independently of the runtime targeted by the project.
For example:
{
"sdk": {
"version": "10.0.100"
}
}
does not automatically mean that the production application is running the same runtime patch.
Your pipeline should therefore inspect both.
Useful diagnostic commands include:
dotnet --info
dotnet --list-sdks
dotnet --list-runtimes
This distinction is particularly important when investigating a security patch that affects the runtime.
Verify the Runtime Used by the Application
The most useful validation is to check what the application actually reports at runtime.
For example:
using System.Runtime.InteropServices;
Console.WriteLine(RuntimeInformation.FrameworkDescription);
Console.WriteLine(RuntimeInformation.OSDescription);
This provides runtime information from the running process rather than relying only on the build machine configuration.
For a production deployment, expose this information through controlled diagnostics or telemetry rather than a public endpoint.
For example:
Application
↓
Deployment
↓
Runtime diagnostics
↓
Expected runtime?
If the pipeline expects a patched runtime but the application reports an older version, the deployment should fail validation.
Build a Security Regression Gate
A practical CI pipeline can introduce a dedicated security regression stage:
Build
↓
Unit Tests
↓
Integration Tests
↓
Security Scan
↓
Runtime Verification
↓
Smoke Tests
↓
Performance Checks
↓
Security Regression Gate
The gate should answer several questions:
Did the application build?
Did tests pass?
Is the intended runtime being used?
Are vulnerable packages still present?
Did important application behavior change?
Did startup or request performance degrade unexpectedly?
Did deployment health checks pass?
Only then should the patched build continue toward production.
Scan NuGet Dependencies Automatically
Runtime security is only one part of the problem.
Application dependencies should also be checked.
Modern .NET SDK tooling supports vulnerability reporting through the dotnet package list command. The --vulnerable option identifies packages with known vulnerabilities, and JSON output can be used to feed automated security systems.
For example:
dotnet package list \
--include-transitive \
--vulnerable \
--format json
The important part is --include-transitive.
A vulnerable dependency may not appear as a direct PackageReference.
For example:
Application
|
+-- Package A
|
+-- Package B
|
+-- Vulnerable Package C
If the pipeline only checks direct dependencies, Package C can be missed.
Convert Security Results Into a Machine-Readable Gate
JSON output makes it easier to automate policy.
Conceptually:
{
"securityGate": {
"critical": 0,
"high": 0,
"medium": 2
}
}
The deployment policy might then be:
Critical > 0 → Block
High > 0 → Block
Medium > 0 → Review
Low > 0 → Track
The exact thresholds should be defined by the organization's security policy.
The important principle is consistency.
Every repository should not invent its own definition of an acceptable security state.
Test the Application, Not Just the Package
Suppose a security patch changes behavior around HTTP processing.
A package scan can tell you:
Vulnerability fixed: Yes
It cannot tell you:
Authentication flow: Still works
API serialization: Still works
File upload: Still works
Database queries: Still work
That requires application-level testing.
For an ASP.NET Core API, a regression suite might cover:
GET /health
POST /login
GET /orders
POST /orders
PUT /orders/{id}
DELETE /orders/{id}
The test suite should focus on business-critical paths rather than attempting to test every possible request.
Use Smoke Tests After Deployment
Smoke tests are particularly valuable immediately after deployment.
A basic workflow:
Deploy to staging
↓
Wait for startup
↓
Health check
↓
Authentication test
↓
Critical API test
↓
Database connectivity test
↓
Pass?
For example:
[Fact]
public async Task HealthEndpoint_ShouldReturnSuccess()
{
using var client = factory.CreateClient();
var response = await client.GetAsync("/health");
response.EnsureSuccessStatusCode();
}
The exact tests will depend on the application.
The important part is that the same critical paths are tested before and after the security update.
Compare Before and After Behavior
A stronger regression gate compares results.
For example:
Join the conversation! Your thoughts help the community grow.