Skip to content

XS✔ ◾ fix(healthchecks): scope TLS validation bypass to local endpoints - #902

Open
Neil (neilr81) wants to merge 2 commits into
mainfrom
neilr81-healthcheck-ssl-validation-fix
Open

Neil (neilr81) wants to merge 2 commits into
mainfrom
neilr81-healthcheck-ssl-validation-fix

Conversation

@neilr81

@neilr81 Neil (neilr81) commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Why

AddHealthCheckHttpClient unconditionally set ServerCertificateCustomValidationCallback to a callback that always returns true, disabling TLS server certificate validation for every named health check client in every environment, including Production - even though the XML doc claimed the relaxation only applied "in development".

Simply gating that bypass on IsDevelopment() turned out to be too restrictive. Health checks typically target the local node, addressed either by a loopback host or by the raw node IP, and a certificate issued for a DNS name cannot match that address by construction. So the relaxation needs to be scoped by what is being called, not by which environment the service happens to run in.

What changed

src/Diagnostics.HealthChecks/HealthCheckHttpClientServiceCollectionExtensions.cs:

  • AddHealthCheckHttpClient now decides per request. Certificate errors are ignored only when the request targets a loopback host (localhost, 127.0.0.1, [::1]) or a bare IP literal. Requests addressed to a DNS name, meaning any external or FQDN endpoint, go through full validation.
  • Added AddValidatedHealthCheckHttpClient for health checks that should always validate, including loopback and IP endpoints.
  • Both keep a Development escape hatch: in Development all certificate errors are ignored, to support self signed certificates on developer machines. If IHostEnvironment is not registered, the code treats the app as non-Development.
  • Rewrote the XML docs so the documented behavior matches the code and states plainly that validation is relaxed for local endpoints rather than implying it is relaxed only in development.

Routing is done inside the validation callback via Uri.IsLoopback and Uri.HostNameType, so a single client can safely be pointed at a mix of local and remote endpoints.

Tests

tests/Diagnostics.HealthChecks.UnitTests/HealthCheckHttpClientServiceCollectionExtensionsTests.cs runs the registered HttpMessageHandlerBuilder actions against a test builder, then invokes the resulting callback with real request URIs and SslPolicyErrors values:

  • Local and IP endpoints (localhost, 127.0.0.1, [::1], 10.0.0.4, [fd00::1]) in Production -> errors ignored.
  • DNS endpoints in Production -> errors rejected, clean certificates accepted.
  • Development -> errors ignored for both DNS and local hosts.
  • AddValidatedHealthCheckHttpClient in Production, Staging, EDog, Int, and with no registered IHostEnvironment -> errors rejected even for loopback and IP, clean certificates still accepted.
  • AddValidatedHealthCheckHttpClient in Development -> errors ignored.

All 104 tests in Microsoft.Omex.Extensions.Diagnostics.HealthChecks.UnitTests pass.

Notes for reviewers

The main judgement call is treating any IP literal as local, not just loopback and private ranges. That matches how Service Fabric health checks address the local node (node IP, not loopback), but it does mean a health check deliberately pointed at a public IP would skip validation. Narrowing this to loopback plus RFC1918 / ULA ranges is a straightforward follow-up if reviewers prefer the tighter rule - happy to make that change here.

This is still a behavior change for any consumer relying on the old blanket bypass for FQDN addressed endpoints. Those health checks will now fail until the endpoint presents a trusted certificate, which is the intended outcome, and AddValidatedHealthCheckHttpClient gives teams an explicit opt in to the strict behavior everywhere.

IHostEnvironment comes in transitively via the existing Microsoft.Omex.Extensions.Abstractions project reference, so no new package reference was needed.

AddHealthCheckHttpClient set ServerCertificateCustomValidationCallback to
always return true, disabling server certificate validation in every
environment including Production, despite the XML doc claiming the bypass
applied only in development.

Resolve IHostEnvironment from the service provider and apply the bypass
only when IsDevelopment() is true. When IHostEnvironment is not registered
the secure default of validating certificates is preserved.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: db9ceb21-549a-40e3-aba5-9b587fa6d8ed
@neilr81
Neil (neilr81) requested a review from a team as a code owner October 14, 2026 13:46
Copilot AI balanced review requested due to automatic review settings October 14, 2026 13:46
@neilr81
Neil (neilr81) deployed to github-app-main October 14, 2026 13:46 — with GitHub Actions Active
@microsoft-omex

microsoft-omex Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

PR Metrics

✔ Thanks for keeping your pull request small.
✔ Thanks for adding tests.

Lines
Product Code 65
Test Code 156
Subtotal 221
Ignored Code -
Total 221

Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!

@microsoft-omex microsoft-omex Bot changed the title fix(healthchecks): only bypass TLS certificate validation in Development XS✔ ◾ fix(healthchecks): only bypass TLS certificate validation in Development Oct 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation and tests correctly enforce the documented secure behavior.

0 open findings

What changed in this PR

Restricts health-check TLS certificate bypasses to Development, preserving validation elsewhere.

Changes:

  • Adds environment-aware handler configuration with secure fallback.
  • Adds tests for Development, deployed environments, and missing environment registration.
  • Updates XML documentation.
File Description
src/​Diagnostics.HealthChecks/​HealthCheckHttpClientServiceCollectionExtensions.cs Applies the TLS bypass only in Development.
tests/​Diagnostics.HealthChecks.UnitTests/​HealthCheckHttpClientServiceCollectionExtensionsTests.cs Verifies environment-specific certificate validation behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

/// <param name="services">The services.</param>
/// <param name="name">The logical name of the <see cref="HttpClient"/> to configure.</param>
/// <returns>The <see cref="IHttpClientBuilder"/>.</returns>
public static IHttpClientBuilder AddHealthCheckHttpClient(this IServiceCollection services, string name) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this validation reduction may have been deliberate to support localhost loopback explicitly - where HTTPS validation won't work due to IP-based routing, rather than domain based routing.
If so, this change will break all localhost loopback health checks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if someone has control of the service endpoints on localhost, HTTPS validation is the least of the security concerns.

@K-Cully Keith Cully (K-Cully) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this will break every service and that the detection is actually a false-positive.
Suggest just updating the comments to make it clear of the execution context and localhost only use, and possibly making code edits to restrict the validation to only be skipped for localhost requests)

…ment

Health checks commonly target the local node by loopback host or by raw
node IP, where a certificate issued for a DNS name cannot match the
address being called. Gating the relaxation purely on the Development
environment broke those checks everywhere else.

AddHealthCheckHttpClient now ignores certificate errors only for loopback
hosts and bare IP literals. Requests addressed to a DNS name, meaning
external or FQDN endpoints, are fully validated. Development continues to
ignore all certificate errors for self signed developer certificates.

Add AddValidatedHealthCheckHttpClient for health checks that should always
validate, including for loopback and IP addressed endpoints, with the same
Development exception.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: db9ceb21-549a-40e3-aba5-9b587fa6d8ed
Copilot AI balanced review requested due to automatic review settings October 15, 2026 10:55
@neilr81
Neil (neilr81) deployed to github-app-main October 15, 2026 10:55 — with GitHub Actions Active
@neilr81 Neil (neilr81) changed the title XS✔ ◾ fix(healthchecks): only bypass TLS certificate validation in Development fix(healthchecks): scope TLS validation bypass to local endpoints Oct 15, 2026
@microsoft-omex microsoft-omex Bot changed the title fix(healthchecks): scope TLS validation bypass to local endpoints XS✔ ◾ fix(healthchecks): scope TLS validation bypass to local endpoints Oct 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Remote IP-addressed endpoints still bypass TLS certificate validation in Production.

1 open finding

🧠 Review effort: Balanced

This branch was successfully deployed

1 active deployment
github-app-main — dd67478e Deployed Oct 15, 2026 by neilr81 via PR Metrics #2488
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants