Skip to content

Validate Managed Identity endpoint host to prevent credential redirection#8682

Closed
Robbie-Microsoft wants to merge 4 commits into
devfrom
rginsburg/06_25_26
Closed

Validate Managed Identity endpoint host to prevent credential redirection#8682
Robbie-Microsoft wants to merge 4 commits into
devfrom
rginsburg/06_25_26

Conversation

@Robbie-Microsoft

@Robbie-Microsoft Robbie-Microsoft commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Managed Identity (MI) credential sources in @azure/msal-node read their endpoint URLs from environment variables (IDENTITY_ENDPOINT, MSI_ENDPOINT, IMDS_ENDPOINT, AZURE_POD_IDENTITY_AUTHORITY_HOST) and use them together with credential material — the X-IDENTITY-HEADER secret and the Azure Arc HIMDS key. The shared validator getValidatedEnvVariableUrlString only canonicalized the URL (lowercase + trailing slash) and never checked the host. As a result, an in-process actor (e.g. a malicious transitive dependency) that can influence process.env could repoint an MI endpoint at an arbitrary host and have the SDK deliver the credential to it.

These endpoints are always served by a node-local agent (IMDS on link-local 169.254.169.254; Azure Arc, App Service, Cloud Shell, Machine Learning, and Service Fabric on loopback), so the endpoint host can be safely pinned.

Fix

Centralized host pinning in the single shared helper every MI source already calls before constructing a request:

  • Parse the endpoint with the WHATWG URL parser so userinfo such as http://127.0.0.1@evil.com correctly resolves to host evil.com and is rejected.
  • Allow only loopback / link-local hosts: localhost, 127.0.0.0/8, [::1], and 169.254.0.0/16 (covers the IMDS endpoint). Host matching uses an explicit bounded octet parser — no regex, so there is no ReDoS surface. Alternate IPv4 encodings (decimal, hex, short form) are normalized by the URL parser before the check, and look-alike domains such as 169.254.169.254.evil.com are rejected.
  • Two failure modes: an unparseable value yields the existing per-variable "malformed" error; a parseable value with a disallowed host yields the new invalid_managed_identity_endpoint error.
  • Scheme is intentionally not restricted — MI legitimately uses http on loopback, and host pinning already rejects hostless schemes like file:.
  • Removed dead trailing-slash handling in AzureArc (a no-op ternary whose result was discarded).

Testing

  • New ManagedIdentityEndpointValidation.spec.ts (16 cases): disallowed hosts (off-box, userinfo confusion, look-alike suffix, private/non-loopback, wildcard), malformed input, accepted loopback/link-local (incl. IPv6 and decimal-encoded), and two end-to-end tests asserting acquireToken now refuses to send the credential to an attacker host. Each accepted case runs through the env var and source name its real source uses (e.g. IMDS via AZURE_POD_IDENTITY_AUTHORITY_HOST).
  • Updated the six existing MI specs to use realistic loopback endpoint URLs (they previously used non-URL placeholder strings that host validation now correctly rejects), centralized in a shared MANAGED_IDENTITY_TEST_ENDPOINTS constant that reuses the real Azure Arc / IMDS source values.
  • Full Managed Identity suite green (89/89), tsc build, eslint, and Prettier all pass.

Notes for reviewers

  • The fix is contained entirely in the shared helper, so all six MI sources are protected without changing the value each passes to its constructor.
  • No public API change; this is additive (one new error code) and behavior-preserving for legitimate node-local endpoints.
  • localhost is allowed by name on purpose. The threat being mitigated is redirection to an off-box attacker server. A co-located process that can both bind localhost and set these environment variables already has local code execution, which is outside this control's scope. IPv6 link-local (fe80::/10) and IPv4-mapped IPv6 ([::ffff:127.0.0.1]) are intentionally rejected (fail-closed) since real MI endpoints never use those forms.
  • The host-validation cases use a small data-driven validate() helper that calls the static validator directly, rather than the per-source acquireToken harness — intentional for exercising a pure function. The end-to-end refusal tests still follow the existing MI spec conventions.

@Robbie-Microsoft Robbie-Microsoft requested a review from a team as a code owner June 25, 2026 22:21
Copilot AI review requested due to automatic review settings June 25, 2026 22:21
@Robbie-Microsoft Robbie-Microsoft requested a review from a team as a code owner June 25, 2026 22:21
…tion

Managed Identity sources read endpoint URLs from environment variables and use them together with credential material (the X-IDENTITY-HEADER secret and the Azure Arc HIMDS key). The shared endpoint validator only canonicalized the URL without checking the host, so an in-process actor could repoint an MI endpoint environment variable at an arbitrary host and capture the credential.

Pin the endpoint host inside the shared getValidatedEnvVariableUrlString helper to loopback / link-local addresses (localhost, 127.0.0.0/8, [::1], 169.254.0.0/16), parsing with the WHATWG URL parser so userinfo tricks resolve to the real host. Add the invalid_managed_identity_endpoint error and remove dead trailing-slash handling in AzureArc.

Adds ManagedIdentityEndpointValidation.spec.ts (including two end-to-end redirect-refusal tests) and updates the existing MI specs to use loopback endpoints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Pull request overview

This PR hardens @azure/msal-node Managed Identity (MI) by validating that MI endpoint environment variables resolve to node-local hosts (loopback/link-local), preventing credential material from being redirected to an attacker-controlled host via process.env tampering.

Changes:

  • Added host pinning to BaseManagedIdentitySource.getValidatedEnvVariableUrlString using the WHATWG URL parser plus explicit IPv4 octet parsing.
  • Introduced new MI error code/message invalid_managed_identity_endpoint and added regression + end-to-end tests covering redirected endpoints and malformed/disallowed URLs.
  • Updated existing MI tests to use realistic loopback/link-local endpoint URL values; removed dead trailing-slash code in AzureArc.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts Adds centralized host validation for MI endpoint env vars before requests are constructed.
lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts Removes dead trailing-slash handling during env var validation.
lib/msal-node/src/error/ManagedIdentityErrorCodes.ts Adds new MI error code for disallowed endpoint hosts.
lib/msal-node/src/error/ManagedIdentityError.ts Adds error message text for the new MI endpoint validation failure mode.
lib/msal-node/test/client/ManagedIdentitySources/ManagedIdentityEndpointValidation.spec.ts New regression + E2E coverage for host validation and credential redirection prevention.
lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts Updates test env var endpoint to a valid loopback URL.
lib/msal-node/test/client/ManagedIdentitySources/AzureArc.spec.ts Updates test env var endpoints to valid loopback URLs.
lib/msal-node/test/client/ManagedIdentitySources/CloudShell.spec.ts Updates test env var endpoint to a valid loopback URL.
lib/msal-node/test/client/ManagedIdentitySources/DefaultManagedIdentityRetryPolicy.spec.ts Updates test env var endpoint to a valid loopback URL.
lib/msal-node/test/client/ManagedIdentitySources/MachineLearning.spec.ts Updates test env var endpoint to a valid loopback URL.
lib/msal-node/test/client/ManagedIdentitySources/ServiceFabric.spec.ts Updates test env var endpoint to a valid localhost URL.
change/@azure-msal-node-c30af408-852d-4a8c-a236-70697d5e7d78.json Beachball changefile for the security hardening behavior change.

Comment thread lib/msal-node/src/error/ManagedIdentityError.ts
Robbie-Microsoft and others added 2 commits June 25, 2026 18:31
…docs

Adds the new error code to the 'Potential errors' section of managed-identity.md, describing when it occurs (endpoint env var resolves to a non-loopback/link-local host) and how to resolve it. Addresses PR review feedback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the ad-hoc (and partly made-up) endpoint URLs scattered across the MI source specs with a single MANAGED_IDENTITY_TEST_ENDPOINTS constant in StringConstants.ts. Azure Arc reuses the real DEFAULT_AZURE_ARC_IDENTITY_ENDPOINT source value and IMDS uses the real link-local host; the platform-assigned sources keep representative loopback values. Drops the misleading Service Fabric :2377 (Docker Swarm) port in favor of a dynamic/ephemeral-range port. Behavior-neutral: the network layer is mocked and only the host is validated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

…ng scope

Address review nits on the MI endpoint-validation change:

- Thread each accepted endpoint through the env var and source name its real source uses, so the validate() helper no longer always reports AppService while the case label names a different source (and IMDS now uses AZURE_POD_IDENTITY_AUTHORITY_HOST, matching Imds.tryCreate).
- Document why localhost is allowed by name (off-box redirection is the threat; a co-located attacker already has code execution), and that IPv6 link-local / IPv4-mapped IPv6 are intentionally rejected (fail-closed).
- Note that the final UrlString re-parse is intentionally outside the try/catch (it only throws on empty, already rejected by new URL()).
- Fix copy-paste describe titles in CloudShell/ServiceFabric specs that said 'App Service'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

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.

2 participants