This check flags Azure App Services running an outdated .NET Framework runtime, which means missing security patches and unsupported features. Pin your app to a current, supported framework version in the App Service runtime stack settings and rebuild your deployment pipeline around supported runtimes.
Runtime versions age out faster than most teams expect. An App Service that was deployed on a fully patched stack two years ago can quietly drift into "unsupported" territory while nobody touches it. The appservice_olddotnet check exists to catch exactly this kind of silent decay before it turns into a vulnerability you read about in an incident report.
This post walks through what the check looks at, why an outdated .NET Framework is a real problem and not just a compliance checkbox, and how to fix and prevent it across the console, CLI, and infrastructure as code.
What this check detects
The appservice_olddotnet check inspects each Azure App Service in your subscription and reads the configured .NET runtime version. If the app is pinned to a .NET Framework version that Microsoft no longer supports, or that has been superseded by a newer maintained release, the check raises a finding.
There are two distinct things people mean when they say ".NET" on App Service, and the distinction matters here:
- .NET Framework is the older Windows-only runtime (versions like 3.5 and 4.x). On App Service this is exposed through the
netFrameworkVersionsetting, with values such asv4.0orv4.8. - .NET (Core) is the modern cross-platform runtime (.NET 6, 8, 9 and so on), configured through the linux/windows FX version stack settings.
Note: On Azure App Service, netFrameworkVersion set to a value like v4.0 can actually mean any 4.x release, because the 4.x family shares an in-place runtime. Modern .NET (Core) versions are tracked separately. This check focuses on apps still tied to the legacy Framework or to a Framework build that has fallen out of support.
Practically, the check is asking: is this app running on a runtime that Microsoft will still ship security fixes for? If the answer is no, you have a problem regardless of how stable the app feels.
Why it matters
An outdated runtime is not a cosmetic issue. It changes your exposure in a few concrete ways.
You stop getting security patches
When a .NET Framework version reaches end of support, Microsoft stops shipping fixes for it. New CVEs discovered in deserialization, request parsing, cryptography, or the underlying class libraries simply never get patched on that version. Your app keeps running, which is the dangerous part, because there is no crash or error to tell you that you are now sitting on known-exploitable code.
The .NET Framework has had a steady stream of serious vulnerabilities over the years, including remote code execution issues in how it handles certain inputs. On a supported version those get patched automatically through the platform. On an unsupported one, you carry the risk indefinitely.
Warning: "It still works" is not the same as "it is safe." Unsupported runtimes fail quietly. The application stays up while its attack surface grows with every CVE published after the support cutoff.
Compliance and audit failures
Most security frameworks (PCI DSS, SOC 2, ISO 27001, HIPAA) expect you to run supported, patched software. An auditor who finds production workloads on an end-of-life runtime will write it up, and "the app is old but stable" is not an answer that satisfies them. This single finding can hold up an attestation.
Real attack scenarios
- Deserialization RCE: Several older .NET Framework versions are vulnerable to gadget-chain deserialization attacks. An attacker who can get crafted data into a vulnerable deserializer can execute code on your App Service worker.
- TLS and crypto weaknesses: Older runtimes default to weaker TLS behavior and outdated cipher handling, which can expose data in transit or fail modern security scans.
- Dependency rot: An app stuck on an old framework usually means old NuGet packages too, compounding the number of unpatched libraries in the deployment.
How to fix it
Remediation comes down to moving the app to a supported runtime. The right path depends on whether you are staying on .NET Framework or migrating to modern .NET.
Step 1: Find out what you are actually running
List the runtime configuration for the app before changing anything.
az webapp config show \
--name my-app \
--resource-group my-rg \
--query "{netFramework:netFrameworkVersion, windowsFx:windowsFxVersion, linuxFx:linuxFxVersion}" \
-o table
To sweep an entire subscription and find apps still on the legacy Framework:
az webapp list --query "[].{name:name, rg:resourceGroup}" -o tsv | \
while read name rg; do
ver=$(az webapp config show --name "$name" --resource-group "$rg" \
--query netFrameworkVersion -o tsv)
echo "$rg/$name -> $ver"
done
Step 2: Update the runtime (staying on .NET Framework)
If you have a Windows app that genuinely needs the .NET Framework, move it to the latest supported in-place version (4.8 at time of writing).
Warning: Changing the runtime version recycles the app and can cause a brief restart. Do this in a deployment slot first, test, then swap into production to avoid user-facing downtime.
az webapp config set \
--name my-app \
--resource-group my-rg \
--net-framework-version v4.8
Step 3: Migrate to modern .NET (recommended)
The longer-term fix is to move off the legacy Framework entirely and onto a supported .NET release with active long-term support. This requires rebuilding your project against the new SDK, but it removes the whole class of "the runtime is dead" findings going forward.
Once your code targets a current version, set the FX stack on the App Service. For a Linux app:
az webapp config set \
--name my-app \
--resource-group my-rg \
--linux-fx-version "DOTNETCORE|8.0"
For a Windows app running modern .NET:
az webapp config set \
--name my-app \
--resource-group my-rg \
--windows-fx-version "DOTNET|8.0"
Tip: Use the .NET Upgrade Assistant (dotnet tool install -g upgrade-assistant) to scaffold the migration from .NET Framework to modern .NET. It flags incompatible APIs and rewrites project files so you are not doing the port by hand.
Step 4: Verify
Confirm the new runtime is live and the app still serves traffic.
az webapp config show \
--name my-app \
--resource-group my-rg \
--query "{net:netFrameworkVersion, linux:linuxFxVersion, windows:windowsFxVersion}" \
-o table
curl -I https://my-app.azurewebsites.net
Fixing it in infrastructure as code
If your App Service is managed with Terraform or Bicep, change it there rather than in the portal, so the next apply does not revert your fix.
Terraform
resource "azurerm_linux_web_app" "app" {
name = "my-app"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
service_plan_id = azurerm_service_plan.plan.id
site_config {
application_stack {
dotnet_version = "8.0"
}
}
}
For a Windows app that must stay on the Framework, pin it explicitly to the supported version:
site_config {
application_stack {
dotnet_version = "v4.0"
current_stack = "dotnet"
}
}
Bicep
resource site 'Microsoft.Web/sites@2023-12-01' = {
name: 'my-app'
location: location
properties: {
serverFarmId: plan.id
siteConfig: {
linuxFxVersion: 'DOTNETCORE|8.0'
}
}
}
How to prevent it from happening again
The root cause of this finding is almost never a bad decision at deploy time. It is the absence of a process that revisits runtime versions as they age out. Build that process.
Gate it in CI/CD
Add a check to your pipeline that fails the build if an App Service definition references an unsupported runtime. A simple grep against your IaC works as a starting point:
# Fail the pipeline if any legacy Framework version is committed
if grep -rEn 'dotnet_version\s*=\s*"v[123]' ./infra; then
echo "Outdated .NET Framework version found in IaC"
exit 1
fi
Enforce with Azure Policy
Azure Policy can audit or deny App Services configured with a disallowed runtime. Use a policy definition that inspects netFrameworkVersion and flags anything below your minimum.
{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Web/sites/config" },
{
"field": "Microsoft.Web/sites/config/netFrameworkVersion",
"in": [ "v2.0", "v3.5", "v4.0" ]
}
]
},
"then": { "effect": "audit" }
}
Tip: Start the policy in audit mode to see how many existing apps would fail, fix those, then switch the effect to deny so non-compliant runtimes can never be deployed again.
Schedule recurring scans
Runtime support dates are a moving target, so a one-time fix is not enough. Run appservice_olddotnet on a schedule in Lensix so an app that was compliant last quarter gets re-flagged the moment its runtime crosses an end-of-support date.
Best practices
- Track support timelines, not just versions. Subscribe to the .NET support policy calendar and treat an upcoming end-of-support date as a planned work item, not a surprise.
- Prefer LTS releases. For modern .NET, target Long Term Support versions so you get the longest patch window per upgrade and reduce how often you have to migrate.
- Use deployment slots for runtime changes. Always test a runtime bump in a staging slot and swap, so a breaking change in the new version never hits production cold.
- Keep dependencies current alongside the runtime. An up-to-date runtime with stale NuGet packages still carries risk. Update both together.
- Centralize runtime config in IaC. Manual portal edits drift. When the runtime version lives in Terraform or Bicep, it is reviewable, auditable, and consistent across environments.
- Plan the move off .NET Framework. If you can, treat any Framework app as a migration candidate. Modern .NET gets faster releases, better performance, and a longer support runway.
Outdated runtimes are one of the easiest findings to ignore because nothing breaks. That is exactly why they belong in an automated check. Catch the drift early, pin to a supported version, and put a policy in place so the next stale runtime never reaches production.

