Validate Azure region format to prevent region poisoning (fixes #6060)#6061
Merged
Conversation
Region short names are always alphanumeric single words. Previously a
region read from WithAzureRegion, the MSAL_FORCE_REGION env variable, the
REGION_NAME env variable, or IMDS was only loosely checked (or, for
MSAL_FORCE_REGION, not checked at all), so a value such as 'attacker.com/x'
could be prefixed onto '{region}.login.microsoft.com' and redirect the
token endpoint host.
- Add RegionManager.IsValidRegionName enforcing letters/digits only
- WithAzureRegion and MSAL_FORCE_REGION now throw MsalError.InvalidRegion on invalid input
- Tighten RegionManager.ValidateRegion (REGION_NAME + IMDS) to reject special characters
- Add MsalError.InvalidRegion + message and PublicAPI entries
- Tests for builder, env-variable, and helper validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…check - Normalize WithAzureRegion and MSAL_FORCE_REGION values (strip spaces, lower-case) consistently with the REGION_NAME auto-detection path, so 'East Us' is accepted everywhere instead of throwing on some paths. - Extract the duplicated validate-or-throw logic into a single private NormalizeAndValidateRegion helper; sentinels are passed through unchanged. - Defense-in-depth: re-validate the region at the single point where the regional authority host is constructed (RegionAndMtlsDiscoveryProvider), ignoring any malformed value and falling back to global. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request addresses a security issue where a malformed/attacker-controlled Azure region value could be used to “poison” the computed token endpoint host. It introduces strict (ASCII) alphanumeric validation for region short names and applies it consistently across configuration, environment-variable handling, auto-detection, and the final host construction point.
Changes:
- Introduces
RegionManager.IsValidRegionName(ASCII letters/digits only) and uses it for region validation during region discovery. - Updates
ConfidentialClientApplicationBuilderto normalize (strip spaces + lowercase) and fail fast withMsalClientException(MsalError.InvalidRegion) for invalid region formats fromWithAzureRegion(...)andMSAL_FORCE_REGION. - Adds defense-in-depth validation at the point where the regional host is constructed, plus unit tests and public API surface tracking for the new error code.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithRegionTests.cs | Adds builder/env-var validation + normalization test coverage and verifies new invalid_region error code. |
| tests/Microsoft.Identity.Test.Unit/CoreTests/RegionDiscoveryProviderTests.cs | Adds unit tests for IsValidRegionName and ensures poisoned REGION_NAME is ignored (fallback to global). |
| src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt | Tracks new public API constant MsalError.InvalidRegion. |
| src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt | Tracks new public API constant MsalError.InvalidRegion. |
| src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt | Tracks new public API constant MsalError.InvalidRegion. |
| src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt | Tracks new public API constant MsalError.InvalidRegion. |
| src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt | Tracks new public API constant MsalError.InvalidRegion. |
| src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt | Tracks new public API constant MsalError.InvalidRegion. |
| src/client/Microsoft.Identity.Client/MsalErrorMessage.cs | Adds an internal error message template describing the invalid region format requirement. |
| src/client/Microsoft.Identity.Client/MsalError.cs | Adds public MsalError.InvalidRegion with XML docs describing cause/mitigation. |
| src/client/Microsoft.Identity.Client/Instance/Region/RegionManager.cs | Replaces URI-based validation with strict ASCII alphanumeric validation via IsValidRegionName. |
| src/client/Microsoft.Identity.Client/Instance/Discovery/RegionAndMtlsDiscoveryProvider.cs | Re-validates region before constructing the regional host (defense-in-depth) and falls back to global on invalid input. |
| src/client/Microsoft.Identity.Client/AppConfig/ConfidentialClientApplicationBuilder.cs | Centralizes normalization + validation into NormalizeAndValidateRegion and throws invalid_region for invalid user/env inputs. |
Per review feedback, validate the user-provided/forced Azure region format and
fall back to the global (non-regional) endpoint when invalid, rather than
throwing. This keeps the behavior non-breaking while still ensuring a poisoned
region (e.g. one containing host/path/special characters) is never prefixed
onto the trusted '{region}.login.microsoft.com' suffix. Removes the now-unused
MsalError.InvalidRegion / MsalErrorMessage.InvalidRegionFormat constants and
reworks tests to assert global routing. Renames test keyword 'attacker' to 'fake'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the per-character loop in RegionManager.IsValidRegionName with a compiled, anchored regex. Uses an explicit [a-zA-Z0-9] class (not \w/\d, which are Unicode-aware) and \A...\z anchors (not ^...$, which would let a trailing newline through) to preserve the ASCII-only security guarantee. Adds tests for trailing/embedded newlines and Unicode digits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
neha-bhargava
approved these changes
Jun 11, 2026
gladjohn
approved these changes
Jun 12, 2026
This was referenced Jun 19, 2026
Closed
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validates that an Azure region string contains only letters and digits (a single alphanumeric word) before it is used to build a token endpoint host. If the value is malformed, MSAL falls back to the global (non-regional) endpoint instead of using it. Fixes #6060.
Why this is a security fix
The configured region is prefixed onto the trusted
{region}.login.microsoft.comsuffix. A malformed or poisoned value (e.g.MSAL_FORCE_REGION="fake.com/x") would yieldhttps://fake.com/x.login.microsoft.com, whose authority isfake.com— redirecting the token/credential request to an unintended host. Validating the region and falling back to global ensures the request never targets such a host.Behavior: fall back to global (not fail-fast)
A malformed region is not a build-time error. Consistent with how MSAL already handles regions elsewhere (an unusable region falls back to global rather than failing the call), an invalid value is logged and ignored, and the request proceeds against the global endpoint. This keeps the change non-breaking while still closing the host-redirection vector — the request is never sent to the tampered URL described in #6060.
Changes
RegionManager.IsValidRegionName— single source of truth. A compiled, anchored regex (\A[a-zA-Z0-9]+\z) that is ASCII-only by design: it uses an explicit[a-zA-Z0-9]class (not\w/\d, which are Unicode-aware and would allow Unicode digits/homoglyphs) and anchors with\A...\zrather than^...$so a trailing newline cannot slip through.RegionManager.GetAzureRegionAsync— for a user-provided /MSAL_FORCE_REGIONregion, validate the format before returning it; an invalid value logs an error and returnsnull(→ global endpoint). The auto-detect sentinel is handled before this check, so auto-discovery is unaffected.RegionManager.ValidateRegion— used by theREGION_NAMEenv var and IMDS body paths — delegates toIsValidRegionNameand rejects malformed values (falls back to global).RegionAndMtlsDiscoveryProvider(the single point where the regional host is actually constructed) re-validates the region; a malformed value is logged and ignored so it can never alter the authority host.Telemetry note
For a user-provided region, region auto-discovery still runs for telemetry before the fallback decision, so
ApiEvent.RegionUsedrecords the provided string andRegionOutcomeis set toUserProvidedValid/UserProvidedInvalidas usual. The actual routing for a malformed value is global — that is the security guarantee. Fallback tests therefore assert on the resolved token endpoint (global URL), not onRegionUsed.Tests
WithAzureRegion(...)andMSAL_FORCE_REGIONwith malformed values (fake.com/x,fake.com?x,fake.com#x,east@us,east.us,east us) fall back to the global token endpoint.REGION_NAME/env-var special-character rejection inRegionDiscoveryProviderTests.WithAzureRegion(null/empty)still throwsArgumentNullException(pre-existing).TreatWarningsAsErrors).