Repository navigation
Fix IMDSv2 SCHANNEL failures bypassing certificate re-mint retry - #6173
Conversation
PR #6070 moved the IMDSv2 mTLS-PoP token leg to the shared TokenClient path. The retry logic survived that move, but AbstractManagedIdentity. HandleException did not - and that method manufactured both halves of the catch filter the retry depends on (the exception type and the error code). A local SCHANNEL handshake failure therefore surfaced as a raw HttpRequestException instead of triggering the binding re-mint. Restore the normalization in DelegateImdsV2TokenLegAsync so callers keep receiving MsalServiceException, and widen the re-mint filter so it no longer depends on a distant wrapper for its type match. Cancellation is never retried and exceptions MSAL already owns pass through untouched. Also renames IsSchanelFailure to IsSchannelFailure and generalizes its parameter to Exception; detection logic is unchanged. Both members are internal, so there is no public API change. Fixes #6172 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression in IMDSv2 mTLS-PoP token acquisition where SCHANNEL/transport failures in the delegated TokenClient path were no longer normalized into MSAL exceptions, causing the existing “re-mint binding cert and retry once” recovery logic to never trigger.
Changes:
- Restores transport-failure normalization in the delegated IMDSv2 token-leg call so callers see
MsalServiceException(with original exception preserved asInnerException). - Hardens the re-mint-and-retry filter to handle both MSAL-shaped and raw transport exception chains (while excluding cancellation).
- Adds regression/unit tests covering raw SCHANNEL transport chains, bounded single retry behavior, and non-SCHANNEL failure classification.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/PersistentCertificateStoreUnitTests.cs | Updates SCHANNEL detector tests to call the internal helper directly (no reflection) and adds coverage for raw transport exception chains. |
| tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs | Adds end-to-end regression tests proving re-mint+retry behavior for raw SCHANNEL failures and ensuring unrelated failures are not retried and are normalized. |
| src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs | Renames/generalizes SCHANNEL detection helper to accept any Exception chain, supporting both wrapped and raw transport failures. |
| src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs | Wraps unowned delegated token-leg exceptions into managed identity MsalServiceException and broadens the retry filter to operate on any exception chain. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Adds the three unit tests needed to close the acceptance criteria the first commit left unproven: - BearerOverMtls_TokenLeg_SchannelFailure_ReMintsBindingAndRetriesOnce proves the re-mint path is identical for bearer-over-mTLS (WithRequestOverMtls) as for mTLS PoP. WithRequestOverMtls had no coverage in Test.Unit before this. - mTLSPop_Unattested_TokenLeg_SchannelFailure_ReMintsBindingAndRetriesOnce proves the path is attestation-independent (KeyGuard key, no WithAttestationSupport). - mTLSPop_TokenLeg_Cancellation_IsNotRetriedAndIsNotWrapped guards the new transport normalization: OperationCanceledException must still propagate un-wrapped and must not consume a re-mint. Both Schannel tests were verified to fail against the pre-fix behavior. The cancellation test passes both before and after by design; it is a guard against the fix over-reaching, not a regression test. No product code changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs:285
- The comment says SCHANNEL-shaped failures from the mint step are "recoverable by re-minting", but the mint flow (/getplatformmetadata + /issuecredential) does not present the binding certificate in a client mTLS handshake. A SCHANNEL/AuthenticationException during mint is more likely a generic TLS/transport issue, so the rationale here is misleading. Consider rewording to reflect that this is a bounded retry rather than a cert-specific recovery.
// Failures from the mint step itself are intentionally in scope: minting talks to the IMDS endpoint over
// TLS, so a handshake failure there is just as recoverable by re-minting. The retry is bounded to a
// single additional attempt either way. Cancellation is never retried.
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
The cancellation regression test injected a bare OperationCanceledException without ever canceling the caller's token. HttpManager only rethrows a cancellation when the token is genuinely canceled; otherwise it classifies it as a timeout and converts it into MsalServiceException(request_timeout). Whether a bare OperationCanceledException reaches that catch as a TaskCanceledException depends on async state-machine behavior, which differs between net8.0 and net48. The test therefore passed on net8.0 and failed on net48 with MsalServiceException instead of OperationCanceledException. Cancel the caller's token from the token-leg mock's request-validation hook instead. The mock validates the request, then calls ThrowIfCancellationRequested, so the token leg is provably reached and cancellation is raised with the token genuinely canceled. Both the "rethrown by HttpManager" and "never caught by HttpManager" paths then propagate cancellation, so the assertion holds on both target frameworks. Also switches to the assignability-based assertion pattern already used by MtlsPop_AttestationProviderCancelled_PropagatesCancellation, since the runtime may surface cancellation as the derived TaskCanceledException. Verified: net48 (vstest.console) ImdsV2Tests 117/117 pass; net8.0 ManagedIdentity suite 461 passed / 1 skipped / 0 failed. No product code change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ken leg AbstractManagedIdentity.HandleException mapped FormatException onto invalid_managed_identity_endpoint, but the normalization wrapper added in this PR collapsed everything that was not an HttpRequestException onto the generic managed_identity_request_failed. That is the same class of error-contract loss this PR exists to fix, so restore the mapping. The arm is reachable: the token endpoint is built from binding.Endpoint, a value IMDS supplies, so a malformed value surfaces as UriFormatException (a FormatException) instead of a generic failure. Also correct two comments: - ShouldRemintImdsV2Binding claimed a mint-step failure was "recoverable by re-minting" because minting talks to IMDS "over TLS". The mint endpoints are plain HTTP (http://169.254.169.254) and present no binding certificate, so a SCHANNEL failure cannot originate there. The behavior is unchanged and harmless; only the stated rationale was wrong. - Document that IsUnwrappedNonMsalFailure gates on MsalException rather than HandleException's narrower MsalServiceException. That divergence is intentional: it stops MsalClientException from being rewrapped as managed_identity_request_failed, preserving the more specific error code. Adds mTLSPop_TokenLeg_FormatFailure_IsWrappedAsInvalidEndpointAsync, verified to fail against the pre-fix mapping. Validated on both target frameworks: net8.0 and net48 each report 462 passed, 1 skipped, 0 failed for FullyQualifiedName~ManagedIdentity. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
looks good Robbie-Microsoft, just two minor comments |
Replaces the injection-based test with one that exercises the actual path
Gladwin asked about: IMDS returns a malformed mtls_authentication_endpoint,
which is only null/empty-checked during the mint step, flows into
binding.Endpoint, and throws UriFormatException when the token leg parses it.
Asserts both halves of the contract: the error code stays
invalid_managed_identity_endpoint (not the generic request-failed code), and
no re-mint is attempted.
Verified the test fails without the FormatException arm and passes with it.
Endpoint value chosen to survive TrimEnd('/'); confirmed UriBuilder throws
for it on both net48 and net8.0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The change restores a prior exception contract and retry behavior with targeted, deterministic regression tests covering the reported failure mode and key non-regression cases (cancellation/unrelated failures).
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
Updated [Microsoft.Extensions.Configuration](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.Abstractions](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.Abstractions's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.Binder](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.Binder's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.EnvironmentVariables](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.EnvironmentVariables's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.Json](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.Json's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.UserSecrets](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.UserSecrets's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Logging.Abstractions](https://github.com/dotnet/dotnet) from 10.0.11 to 10.0.13. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Logging.Abstractions's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Identity.Client](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) from 4.88.0 to 4.91.0. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Identity.Client's releases](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/releases)._ ## 4.91.0 ### New Features - Made the dynamic certificate provider and confidential-client callbacks (`OnMsalServiceFailure`, `OnCompletion`, and `OnBackgroundTokenRefreshCompleted`) generally available, removing the experimental feature opt-in requirement. [#6216](AzureAD/microsoft-authentication-library-for-dotnet#6216) ### Bug Fixes - Fixed built-in mTLS `HttpClient` caching to use certificate object identity, ensuring a new certificate instance with a reacquired private-key handle receives a new handler even when its thumbprint is unchanged. [#6217](AzureAD/microsoft-authentication-library-for-dotnet#6217) - Fixed managed identity `WithForceRefresh(true)` to re-mint the MSI v2 binding certificate before acquiring a new token, while reusing a valid cached attestation token for the current key. [#6215](AzureAD/microsoft-authentication-library-for-dotnet#6215) - Fixed system-browser authentication callbacks in Linux containers by adding opt-in IPv4 port-forwarding support through `MSAL_ALLOW_SYSTEM_BROWSER_PORT_FORWARDING`, disabled by default. [#6186](AzureAD/microsoft-authentication-library-for-dotnet#6186) ## 4.90.1 ### New Features - Added `IMsalWsTrustHttpClientFactory`, allowing custom HTTP client factories to provide redirect-disabled, credential-policy-aware clients for federation metadata (MEX) and WS-Trust requests. Added `MsalError.TooManyRedirects` and `MsalError.WsTrustCrossOriginRedirectNotSupported` for redirect failures. [#6165](AzureAD/microsoft-authentication-library-for-dotnet#6165) ### Bug Fixes - Hardened federation metadata and WS-Trust requests by requiring HTTPS, securely validating redirects, rejecting credential-bearing cross-origin redirects, and limiting redirect chains. [#6165](AzureAD/microsoft-authentication-library-for-dotnet#6165) - Fixed instance discovery so a custom authority port is not forwarded to the global discovery host, while preserving the port when discovery uses the authority host. [#6155](AzureAD/microsoft-authentication-library-for-dotnet#6155) - Fixed KeyGuard attestation to send the tenant ID as the MAA `client_id` metadata value without changing managed-identity client-ID handling or attestation-cache partitioning. [#6200](AzureAD/microsoft-authentication-library-for-dotnet#6200) ### Changes - Updated `Microsoft.Azure.Security.KeyGuardAttestation` from version 1.1.7 to 1.1.8. [#6202](AzureAD/microsoft-authentication-library-for-dotnet#6202) ## 4.90.0 ### Changes - Updated the KeyGuardAttestation package version to 1.1.7. [#6189](AzureAD/microsoft-authentication-library-for-dotnet#6189) ## 4.89.0 ## What's Changed * Add repository guidance for AI agents by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6158 * Add Linux Attested Managed Identity Flow documentation by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6149 * Remove internal authority example from API docs by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6170 * Update SNI MTLS PoP Token Design documentation by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6171 * Add SNI mTLS E2E coverage for a non-exportable VBS-protected certificate by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6167 * Revise mTLS PoP acceptance-test matrix structure by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6161 * Add ICloudConfiguration for cross-cloud metadata resolution by @Avery-Dunn in AzureAD/microsoft-authentication-library-for-dotnet#6104 * Fix IMDSv2 SCHANNEL failures bypassing certificate re-mint retry by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6173 * Always record RawStsErrorCode on MsalFailure metric by @ssmelov in AzureAD/microsoft-authentication-library-for-dotnet#6175 * Add MSAL_MI_DISABLE_IMDS_V2 kill switch for managed identity by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6178 * Add timeout for managed identity capability discovery by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6181 * Pin GitHub Actions to full-length commit SHAs by @danfiedler-msft in AzureAD/microsoft-authentication-library-for-dotnet#6182 ## New Contributors * @danfiedler-msft made their first contribution in AzureAD/microsoft-authentication-library-for-dotnet#6182 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.88.0...4.89.0 Commits viewable in [compare view](AzureAD/microsoft-authentication-library-for-dotnet@4.88.0...4.91.0). </details> Updated [Microsoft.Identity.Client.Extensions.Msal](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) from 4.88.0 to 4.91.0. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Identity.Client.Extensions.Msal's releases](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/releases)._ ## 4.91.0 ### New Features - Made the dynamic certificate provider and confidential-client callbacks (`OnMsalServiceFailure`, `OnCompletion`, and `OnBackgroundTokenRefreshCompleted`) generally available, removing the experimental feature opt-in requirement. [#6216](AzureAD/microsoft-authentication-library-for-dotnet#6216) ### Bug Fixes - Fixed built-in mTLS `HttpClient` caching to use certificate object identity, ensuring a new certificate instance with a reacquired private-key handle receives a new handler even when its thumbprint is unchanged. [#6217](AzureAD/microsoft-authentication-library-for-dotnet#6217) - Fixed managed identity `WithForceRefresh(true)` to re-mint the MSI v2 binding certificate before acquiring a new token, while reusing a valid cached attestation token for the current key. [#6215](AzureAD/microsoft-authentication-library-for-dotnet#6215) - Fixed system-browser authentication callbacks in Linux containers by adding opt-in IPv4 port-forwarding support through `MSAL_ALLOW_SYSTEM_BROWSER_PORT_FORWARDING`, disabled by default. [#6186](AzureAD/microsoft-authentication-library-for-dotnet#6186) ## 4.90.1 ### New Features - Added `IMsalWsTrustHttpClientFactory`, allowing custom HTTP client factories to provide redirect-disabled, credential-policy-aware clients for federation metadata (MEX) and WS-Trust requests. Added `MsalError.TooManyRedirects` and `MsalError.WsTrustCrossOriginRedirectNotSupported` for redirect failures. [#6165](AzureAD/microsoft-authentication-library-for-dotnet#6165) ### Bug Fixes - Hardened federation metadata and WS-Trust requests by requiring HTTPS, securely validating redirects, rejecting credential-bearing cross-origin redirects, and limiting redirect chains. [#6165](AzureAD/microsoft-authentication-library-for-dotnet#6165) - Fixed instance discovery so a custom authority port is not forwarded to the global discovery host, while preserving the port when discovery uses the authority host. [#6155](AzureAD/microsoft-authentication-library-for-dotnet#6155) - Fixed KeyGuard attestation to send the tenant ID as the MAA `client_id` metadata value without changing managed-identity client-ID handling or attestation-cache partitioning. [#6200](AzureAD/microsoft-authentication-library-for-dotnet#6200) ### Changes - Updated `Microsoft.Azure.Security.KeyGuardAttestation` from version 1.1.7 to 1.1.8. [#6202](AzureAD/microsoft-authentication-library-for-dotnet#6202) ## 4.90.0 ### Changes - Updated the KeyGuardAttestation package version to 1.1.7. [#6189](AzureAD/microsoft-authentication-library-for-dotnet#6189) ## 4.89.0 ## What's Changed * Add repository guidance for AI agents by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6158 * Add Linux Attested Managed Identity Flow documentation by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6149 * Remove internal authority example from API docs by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6170 * Update SNI MTLS PoP Token Design documentation by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6171 * Add SNI mTLS E2E coverage for a non-exportable VBS-protected certificate by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6167 * Revise mTLS PoP acceptance-test matrix structure by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6161 * Add ICloudConfiguration for cross-cloud metadata resolution by @Avery-Dunn in AzureAD/microsoft-authentication-library-for-dotnet#6104 * Fix IMDSv2 SCHANNEL failures bypassing certificate re-mint retry by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6173 * Always record RawStsErrorCode on MsalFailure metric by @ssmelov in AzureAD/microsoft-authentication-library-for-dotnet#6175 * Add MSAL_MI_DISABLE_IMDS_V2 kill switch for managed identity by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6178 * Add timeout for managed identity capability discovery by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6181 * Pin GitHub Actions to full-length commit SHAs by @danfiedler-msft in AzureAD/microsoft-authentication-library-for-dotnet#6182 ## New Contributors * @danfiedler-msft made their first contribution in AzureAD/microsoft-authentication-library-for-dotnet#6182 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.88.0...4.89.0 Commits viewable in [compare view](AzureAD/microsoft-authentication-library-for-dotnet@4.88.0...4.91.0). </details> Updated [Microsoft.Kiota.Abstractions](https://github.com/microsoft/kiota-dotnet) from 2.1.1 to 2.1.2. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Kiota.Abstractions's releases](https://github.com/microsoft/kiota-dotnet/releases)._ ## 2.1.2 ## [2.1.2](microsoft/kiota-dotnet@v2.1.1...v2.1.2) (2026-09-08) ### Bug Fixes * **serialization-json:** honor custom TimeSpan converters ([#795](microsoft/kiota-dotnet#795)) ([e22c5ba](microsoft/kiota-dotnet@e22c5ba)) * **serialization-json:** honor DateTimeOffset converters ([802e20a](microsoft/kiota-dotnet@802e20a)) * **serialization-json:** honor guid converters before fallback parsing ([#794](microsoft/kiota-dotnet#794)) ([22cc1d3](microsoft/kiota-dotnet@22cc1d3)) Commits viewable in [compare view](microsoft/kiota-dotnet@v2.1.1...v2.1.2). </details> Updated [Microsoft.Kiota.Http.HttpClientLibrary](https://github.com/microsoft/kiota-dotnet) from 2.1.1 to 2.1.2. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Kiota.Http.HttpClientLibrary's releases](https://github.com/microsoft/kiota-dotnet/releases)._ ## 2.1.2 ## [2.1.2](microsoft/kiota-dotnet@v2.1.1...v2.1.2) (2026-09-08) ### Bug Fixes * **serialization-json:** honor custom TimeSpan converters ([#795](microsoft/kiota-dotnet#795)) ([e22c5ba](microsoft/kiota-dotnet@e22c5ba)) * **serialization-json:** honor DateTimeOffset converters ([802e20a](microsoft/kiota-dotnet@802e20a)) * **serialization-json:** honor guid converters before fallback parsing ([#794](microsoft/kiota-dotnet#794)) ([22cc1d3](microsoft/kiota-dotnet@22cc1d3)) Commits viewable in [compare view](microsoft/kiota-dotnet@v2.1.1...v2.1.2). </details> Updated [Microsoft.Kiota.Serialization.Form](https://github.com/microsoft/kiota-dotnet) from 2.1.1 to 2.1.2. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Kiota.Serialization.Form's releases](https://github.com/microsoft/kiota-dotnet/releases)._ ## 2.1.2 ## [2.1.2](microsoft/kiota-dotnet@v2.1.1...v2.1.2) (2026-09-08) ### Bug Fixes * **serialization-json:** honor custom TimeSpan converters ([#795](microsoft/kiota-dotnet#795)) ([e22c5ba](microsoft/kiota-dotnet@e22c5ba)) * **serialization-json:** honor DateTimeOffset converters ([802e20a](microsoft/kiota-dotnet@802e20a)) * **serialization-json:** honor guid converters before fallback parsing ([#794](microsoft/kiota-dotnet#794)) ([22cc1d3](microsoft/kiota-dotnet@22cc1d3)) Commits viewable in [compare view](microsoft/kiota-dotnet@v2.1.1...v2.1.2). </details> Updated [Microsoft.Kiota.Serialization.Json](https://github.com/microsoft/kiota-dotnet) from 2.1.1 to 2.1.2. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Kiota.Serialization.Json's releases](https://github.com/microsoft/kiota-dotnet/releases)._ ## 2.1.2 ## [2.1.2](microsoft/kiota-dotnet@v2.1.1...v2.1.2) (2026-09-08) ### Bug Fixes * **serialization-json:** honor custom TimeSpan converters ([#795](microsoft/kiota-dotnet#795)) ([e22c5ba](microsoft/kiota-dotnet@e22c5ba)) * **serialization-json:** honor DateTimeOffset converters ([802e20a](microsoft/kiota-dotnet@802e20a)) * **serialization-json:** honor guid converters before fallback parsing ([#794](microsoft/kiota-dotnet#794)) ([22cc1d3](microsoft/kiota-dotnet@22cc1d3)) Commits viewable in [compare view](microsoft/kiota-dotnet@v2.1.1...v2.1.2). </details> Updated [Microsoft.Kiota.Serialization.Multipart](https://github.com/microsoft/kiota-dotnet) from 2.1.1 to 2.1.2. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Kiota.Serialization.Multipart's releases](https://github.com/microsoft/kiota-dotnet/releases)._ ## 2.1.2 ## [2.1.2](microsoft/kiota-dotnet@v2.1.1...v2.1.2) (2026-09-08) ### Bug Fixes * **serialization-json:** honor custom TimeSpan converters ([#795](microsoft/kiota-dotnet#795)) ([e22c5ba](microsoft/kiota-dotnet@e22c5ba)) * **serialization-json:** honor DateTimeOffset converters ([802e20a](microsoft/kiota-dotnet@802e20a)) * **serialization-json:** honor guid converters before fallback parsing ([#794](microsoft/kiota-dotnet#794)) ([22cc1d3](microsoft/kiota-dotnet@22cc1d3)) Commits viewable in [compare view](microsoft/kiota-dotnet@v2.1.1...v2.1.2). </details> Updated [Microsoft.Kiota.Serialization.Text](https://github.com/microsoft/kiota-dotnet) from 2.1.1 to 2.1.2. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Kiota.Serialization.Text's releases](https://github.com/microsoft/kiota-dotnet/releases)._ ## 2.1.2 ## [2.1.2](microsoft/kiota-dotnet@v2.1.1...v2.1.2) (2026-09-08) ### Bug Fixes * **serialization-json:** honor custom TimeSpan converters ([#795](microsoft/kiota-dotnet#795)) ([e22c5ba](microsoft/kiota-dotnet@e22c5ba)) * **serialization-json:** honor DateTimeOffset converters ([802e20a](microsoft/kiota-dotnet@802e20a)) * **serialization-json:** honor guid converters before fallback parsing ([#794](microsoft/kiota-dotnet#794)) ([22cc1d3](microsoft/kiota-dotnet@22cc1d3)) Commits viewable in [compare view](microsoft/kiota-dotnet@v2.1.1...v2.1.2). </details> Updated [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 18.9.0 to 18.11.0. <details> <summary>Release notes</summary> _Sourced from [Microsoft.NET.Test.Sdk's releases](https://github.com/microsoft/vstest/releases)._ ## 18.11.0 <!-- Release notes generated using configuration in .github/release.yml at rel/18.11 --> ## What's Changed * Fix TRX logger mangling astral-plane (non-BMP) characters by @azat-msft in microsoft/vstest#16318 * Disable the MTP testhost by default by @nohwnd in microsoft/vstest#16337 * Fix OS-blind apphost probe in MTP bridge on Unix (regression from #16201) by @azat-msft in microsoft/vstest#16336 * Retarget the MTP client onto Microsoft.Testing.Platform.ServerMode.Client.Sources by @nohwnd in microsoft/vstest#16300 * Pass arguments to PowerShell with -File instead of -command by @nohwnd in microsoft/vstest#16363 * Respect console logger verbosity from a settings file by @nohwnd in microsoft/vstest#16044 * Fix backslash normalization in VSTestCLIRunSettings on Unix by @nohwnd in microsoft/vstest#15795 * Docs: fix typo explicitely -> explicitly by @vaibhav8a in microsoft/vstest#16397 * Copy acceptance test diagnostic logs into the test results directory by @nohwnd in microsoft/vstest#16403 ## New Contributors * @vaibhav8a made their first contribution in microsoft/vstest#16397 **Full Changelog**: microsoft/vstest@v18.10.1...v18.11.0 ## 18.10.1 ## What's Changed * Isolate code coverage files in published output by @azat-msft in microsoft/vstest#16476 **Full Changelog**: microsoft/vstest@v18.10.0...v18.10.1 ## 18.10.0 ## What's Changed * Drop Mono fallback, run .NET Framework tests on Windows only by @nohwnd in microsoft/vstest#16158 * Run Microsoft.Testing.Platform test apps under `vstest.console` and datacollector by @nohwnd in microsoft/vstest#16201 * Fix test output eaten by MSBuild terminal logger by @nohwnd in microsoft/vstest#16223 * Remove the experimental test session feature by @nohwnd in microsoft/vstest#16231 * Skip a single bad executor instead of failing all executor loading by @nohwnd in microsoft/vstest#16239 * Assert apartment state instead of using Clipboard in UI tests by @nohwnd in microsoft/vstest#16270 * Surface test host crashes during protocol negotiation by @nohwnd in microsoft/vstest#16285 * Pass the inferred target platform to the host in run settings by @nohwnd in microsoft/vstest#16271 * Report raw invalid IsTargetPlatformInferred value, cover host x64 forcing by @nohwnd in microsoft/vstest#16295 * Disable the MTP testhost by default (#16337) by @nohwnd in microsoft/vstest#16341 **Full Changelog**: microsoft/vstest@v18.9.0...v18.10.0 Commits viewable in [compare view](microsoft/vstest@v18.9.0...v18.11.0). </details> Updated [microsoft.openapi.kiota](https://github.com/microsoft/kiota) from 1.35.0 to 1.36.1. <details> <summary>Release notes</summary> _Sourced from [microsoft.openapi.kiota's releases](https://github.com/microsoft/kiota/releases)._ ## 1.36.1 ## Added ### Changed - Fixed small issues with release pipelines ## 1.36.0 ## Added - Ruby: an error's `message` returns the property the description marks with `x-ms-primary-error-message`, as the other languages do. An error property named `message` is renamed `message_escaped`, like `MessageEscaped` in C#; when it is a string, `message` still returns it. [kiota-ruby#63](microsoft/kiota-ruby#63) ### Changed - Fixed relative description paths when opening or selecting clients in the VS Code Kiota workspace. - Removed the retired lock-search command from the VS Code command palette. Lock-file migration remains available. - Preserve inherited properties when a single `oneOf` or `anyOf` member defines its properties only through `allOf`. Fixes [#6778](microsoft/kiota#6778). - Python: preserve false, zero, empty strings, and empty collections when parsing and serializing union type wrappers. - All language refiners accept a generation logger consistently, retaining configuration-only compatibility constructors hidden from IntelliSense. - Reject plugin-manifest file references whose path components become parent-directory traversal after Windows trims trailing spaces or dots. - Fixed a stack overflow when generating API plugins from descriptions with cyclic `allOf` schemas. - Dart: omit unsupported schema defaults with a generation warning, preserve supported literal and enum property and parameter defaults (including empty strings), initialize query parameters from schema defaults, and keep constructor initialization valid with or without backing stores. - Dart: preserve nullable optional request-builder constructor parameters, including UUIDs and collections, so cloning accepts null arguments without overwriting existing path parameters. - Dart: escape URL templates once at final emission to preserve literal dollar signs and backslashes in constructor defaults and method overrides. - Dart: preserve schema string defaults named `null`, including enum wire values, without treating them as JSON-null sentinels. - Dart: preserve query-parameter defaults inside single-member `allOf` wrappers, retaining outer-default precedence and explicit JSON-null defaults. - Dart: validate parser-backed date, time, and UUID defaults before emission, omitting unsupported values with a generation warning. - Ruby: a union of object types that no discriminator decides, such as a oneOf without a discriminator, now reads the payload into every object member, as TypeScript does, instead of returning an empty wrapper. A union whose discriminator matched no member does the same, and its array member is only read when no other member was picked. Requires the runtime gems at 0.24.0. - Ruby: enum members hold their wire value, so a value such as `incoming-payment` or `read` is sent and read as the description declares it instead of as `IncomingPayment` or `Read`. `Enum[:Name]` now returns the wire value as a string. Requires the runtime gems at 0.24.0. - Go: keep escaped namespace segments lowercase so imports match generated package directories on case-sensitive filesystems. Fixes [#7828](microsoft/kiota#7828). - TypeScript: import response enum objects for request builders reached through path parameters. [#6884](microsoft/kiota#6884) - Resolve enum query parameters wrapped in single-member `allOf` schemas, including enum array items. - Go: cast scalar enum values in union factories and avoid reading an empty discriminator name for undiscriminated unions. Addresses [#7808](microsoft/kiota#7808). - Preserve models for multipart properties with default content types when other properties specify an encoding. [#7342](microsoft/kiota#7342) - Dart: escape enum options named value or values to avoid conflicts with generated and built-in enum members. [#7807](microsoft/kiota#7807) - Ruby: the client's root module has an `eager_load!` method that loads every autoloaded model, for servers that preload before forking. - Ruby: generated files start with `# frozen_string_literal: true`, so string default values handed to user code are frozen. - Ruby: generated string literals are double quoted, the StandardRB convention. - Ruby: namespaces with nothing to autoload no longer get an empty barrel file, and generated files no longer require them (561 fewer files in a GitHub client). - Ruby: a client generated with a multi-segment `--namespace-name`, such as `integration_test.client`, failed to load because its root file was written one directory too deep. - Ruby: namespace barrels register their models and enums with `autoload` instead of requiring them, so a subclass no longer fails to load when its base class file is required first. Other files require the barrel rather than an individual model file, and models now load on first use. A type no barrel autoloads, such as a model named after its namespace, is now required directly: 1,315 constants referenced by a Meraki client never loaded, and a client generated from the current GitHub description failed to load because it required a file that is never written. [#7956](microsoft/kiota#7956) - Ruby: model properties are declared with `attr_accessor`, which removes the thousands of "possibly useless use of a variable" warnings `ruby -w` printed while loading a client. - Ruby: enum hashes are frozen. [kiota-ruby#68](microsoft/kiota-ruby#68) - C#: escape Unicode line and paragraph separators in generated string literals so generated clients remain valid C# source. - External reference allowlist wildcards no longer cross the URI authority boundary, so a wildcard in the scheme or host cannot match text in the path, the user information or the port. - Initialize inherited Dart model fields in constructor initializer lists. - Replace only unresolved binary types with a native stream type, and translate a resolved model by its definition rather than its name, so a model whose schema is named `binary`, `base64` or `base64url` keeps its own name across every language instead of being rendered as the native binary type. Generation no longer fails for C# and Java. - Avoid redundant parsing branches for equivalent binary alternatives in Dart and PHP composed models. - Preserve imports for distinct types from the same namespace when inlining error model inheritance. - Dart: qualify model fields named `node` or `deserializerMap` during deserialization so local variables do not shadow them; escape `override` model members to preserve Dart annotations. Fixes [#7822](microsoft/kiota#7822). - PHP: disambiguate enum constants that normalize to the same name while preserving wire values and existing constant names. - Java: omit unused discriminator-mapping imports for union/intersection wrappers, avoiding imports of nonexistent models while retaining member and inherited-factory imports. - Dart: omit unused discriminator-mapping imports for composed wrappers while preserving member and inherited-factory imports. Addresses [#7821](microsoft/kiota#7821). - Preserve endpoints whose static path segments collide after name sanitization, such as /v1.1 and /v11. [#7143](microsoft/kiota#7143) - Ruby: a path with an escaped suffix, such as `/domains/{domainName}!quote`, generated a request builder method that passed the raw path parameter name where the signature had snake cased it, and referenced the builder class without its namespace. Both raised `NameError` on first use. [#7956](microsoft/kiota#7956) ... (truncated) Commits viewable in [compare view](microsoft/kiota@v1.35.0...v1.36.1). </details> Updated [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 4.0.0 to 4.0.1. <details> <summary>Release notes</summary> _Sourced from [xunit.runner.visualstudio's releases](https://github.com/xunit/visualstudio.xunit/releases)._ ## 4.0.1 Release notes: https://xunit.net/releases/visualstudio/4.0.1 Commits viewable in [compare view](xunit/visualstudio.xunit@4.0.0...4.0.1). </details> Updated [xunit.v3](https://github.com/xunit/xunit) from 4.0.0 to 4.0.2. <details> <summary>Release notes</summary> _Sourced from [xunit.v3's releases](https://github.com/xunit/xunit/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/xunit/xunit/commits). </details> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steinar Hjellvik <38071413+shjellvik@users.noreply.github.com>
Fixes #6172
What broke
#6070 (
3680c2fe2) moved the IMDSv2 mTLS-PoP token leg to the sharedTokenClient→OAuth2Client→HttpManagerpath. The retry logic survived that move — it was re-implemented inManagedIdentityAuthRequest. What did not survive wasAbstractManagedIdentity.HandleException, which normalized raw transport failures intoMsalServiceException(ManagedIdentityUnreachableNetwork).That method manufactured both halves of the catch filter the retry depends on — the exception type and the error code. Once it left the path, the filter could never match:
MsalServiceException→HttpRequestException→AuthenticationException— matchedHttpRequestException→AuthenticationException→Win32Exception— type mismatchNet effect: a KeyGuard/CNG-bound cert Schannel can no longer use (
SEC_E_UNKNOWN_CREDENTIALS, 0x8009030D) surfaced as a bareHttpRequestExceptioninstead of triggering the re-mint-and-retry that exists precisely for it. No packet leaves the machine — this is not a transient network condition, it's a stale binding cert that only a re-mint fixes.Unintended exception-contract change, not a behavior change anyone signed off on.
Fix
DelegateImdsV2TokenLegAsync— restore normalization. WraptokenClient.SendTokenRequestAsyncand re-normalize failures MSAL doesn't already own intoMsalServiceException, mirroringHandleException. Scoped to the token call only — the mint step already throws MSAL-shaped exceptions, so wrapping it would double-wrap. The error-code mapping mirrorsHandleException:HttpRequestException→managed_identity_unreachable_network,FormatException→invalid_managed_identity_endpoint(reachable because the token endpoint is built frombinding.Endpoint, an IMDS-supplied value), everything else →managed_identity_request_failed. Guard isex is not MsalException, stricter than the oldMsalServiceExceptioncheck, soMsalClientExceptionis preserved rather than masked.OperationCanceledExceptionpasses through untouched.ShouldRemintImdsV2Binding— defense in depth. Widen the catch fromcatch (MsalServiceException ex)tocatch (Exception ex) when (...). Being explicit: this layer is not required to fix the bug — the normalization change alone makes every new test pass, and I isolated the layers to confirm that. It's here because the original filter only worked by accident of a wrapper three call-frames away. That coupling is what let this regress silently. Cancellation is explicitly excluded.IsSchanelFailure→IsSchannelFailure(Exception). Typo fix, parameter generalized fromMsalServiceException.internal static— no public API surface change, noPublicAPI.Unshipped.txtentry. Detection logic byte-for-byte unchanged: it still keys onAuthenticationExceptionbroadly, which also catches server-cert validation failures. That over-trigger is bounded (one wasted re-mint, then the real error propagates) and narrowing it toSEC_E_UNKNOWN_CREDENTIALS/SEC_E_NO_CREDENTIALSis a semantic change I'm keeping out of a regression fix. Happy to pull it forward if a reviewer wants it here.Compatibility
The regression shipped in 4.85.2 through 4.88.0. On those versions this failure surfaces as a raw
HttpRequestException; after this fix it surfaces asMsalServiceExceptionagain, with the original exception preserved asInnerException. That restores the documented managed identity contract, but anyone who added acatch (HttpRequestException)workaround on 4.85.2+ will find it no longer fires and should revert it. No public API added, removed, or annotated.Tests
Eight end-to-end regression tests in
ImdsV2Tests.cs. Attempt counts are asserted viaMockHttpManager.QueueSize, so "retries exactly once" is proven rather than inferred from a successful return.mTLSPop_TokenLeg_RawSchannelFailure_...mTLSPop_TokenLeg_RepeatedSchannelFailure_...mTLSPop_TokenLeg_UnrelatedNetworkFailure_...mTLSPop_TokenLeg_NonHttpFailure_...HttpRequestExceptionnormalized ontomanaged_identity_request_failedmTLSPop_TokenLeg_FormatFailure_...FormatExceptionkeepsinvalid_managed_identity_endpoint, not the generic codeBearerOverMtls_TokenLeg_SchannelFailure_...WithRequestOverMtlsas for PoPmTLSPop_Unattested_TokenLeg_SchannelFailure_...WithAttestationSupportmTLSPop_TokenLeg_Cancellation_...WithRequestOverMtlshad zero coverage inTest.Unitbefore this.Three detector tests in
PersistentCertificateStoreUnitTests.cs. These also drop a reflection-based lookup of the old method name — the rename would have broken it, and reflection-coupled tests are how a rename becomes a silent hole.Reverted the fix in place and re-ran. All six Schannel/normalization tests fail against the pre-fix behavior with exactly the reported symptom (
Actual exception type:<System.Net.Http.HttpRequestException>) and pass against the new.mTLSPop_TokenLeg_FormatFailure_...was likewise verified to fail against the pre-fix error-code mapping. The cancellation test passes both before and after by design — it guards the fix against over-reaching, it does not reproduce the bug. Calling that out rather than claiming eight regression tests.Full ManagedIdentity suite on both target frameworks — net8.0 and net48 each report 462 passed, 1 skipped, 0 failed. Clean build under
TreatWarningsAsErrors.Acceptance criteria (#6172)
All five criteria are verbatim from the issue. Unit evidence is from
ImdsV2Tests.cs; E2E evidence is from CI run20260901.8on the MI-enabled Azure VM pool (20/20 passed), which exercises real IMDSv2 with Credential Guard.HttpRequestExceptionfailures trigger one certificate re-mint and retrymTLSPop_TokenLeg_RawSchannelFailure_...(re-mints + retries) and..._RepeatedSchannelFailure_...(retries once, then propagates). Attempt counts asserted viaQueueSize; both fail against pre-fix codeBearerOverMtls_TokenLeg_SchannelFailure_...mirrors the PoP test — both paths shareSendDelegatedImdsV2TokenRequestAsync. E2E:AcquireToken_OnImdsV2_WithRequestOverMtls_ReturnsBearerpassed for SAMI + UAMIAcquireToken_OnImdsV2_MtlsPoP_WithAttestation_Succeeds(SAMI + UAMI-ClientId) andAcquireTokenAndCallAKV_.... Unattested:AcquireToken_OnImdsV2_MtlsPoP_GracefulDegradation_WhenCredentialGuardUnavailableManagedIdentityImdsV2FicTests.FicTwoLeg_MtlsPop_SAMI/_UAMI-ClientIdpassed E2E (plus bothFicTwoLeg_Bearer_*). This is the same code path Identity.Web'sManagedIdentityClientAssertiondrives. Identity.Web's own suite was not run against this branch — its test is[OnlyOnAzureDevopsFact]; static review found no risk (below)mTLSPop_TokenLeg_Cancellation_...(propagates un-wrapped, consumes no re-mint) and..._UnrelatedNetworkFailure_...(neither retried nor swallowed). Both assertQueueSizeBeing precise on #4: MSAL's own FIC two-leg mTLS PoP E2E tests pass against live Azure MI, which covers the underlying behavior. Executing Identity.Web's suite against an unreleased MSAL build is the one gap, and it's called out rather than papered over.
Downstream: Microsoft.Identity.Web
Checked against a local
microsoft-identity-webcheckout. No risk found:MsiFic_TwoLeg_FinalMtlsPop_SystemAssignedManagedIdentityAsyncexists (tests/E2E Tests/TokenAcquirerTests/ManagedIdentityFicTests.cs). It is[OnlyOnAzureDevopsFact]and needs live Azure MI + Credential Guard, so it can only be validated in their CI.4.88.0(Directory.Build.props) — inside the affected range.Exception, logs, and rethrows unfiltered; it does not discriminate on exception type.ManagedIdentityClientAssertionalready documents that bind failures "surface as anMsalServiceExceptionfromExecuteAsync", which is the contract this PR restores.invalid_client+ specific AADSTS codes, somanaged_identity_*error codes cannot trigger a spurious retry.HttpRequestExceptionfilters inDownstreamApi.HttpMethods.csare on downstream API calls, not token acquisition.