Library name and version
azure-core 1.55.4 (behavior also present on main)
Describe the bug
Filed as defense-in-depth hardening, not an exploitable vulnerability report — reachability is addressed honestly below.
On a redirect, RedirectPolicy.createRedirectRequest removes exactly one header before following the Location:
// RedirectPolicy.createRedirectRequest(...)
redirectResponse.getRequest().getHeaders().remove(HttpHeaderName.AUTHORIZATION);
A credential placed in any other header — for example a caller-named key header set by
new AzureKeyCredentialPolicy("api-key" | "Ocp-Apim-Subscription-Key" | "aeg-sas-key", credential) — is not
in that removal set. attemptRedirectSync copies the request and re-runs the downstream policies
(next.clone().processSync()), so the credential header is re-materialized on the request sent to the
redirect target. When the redirect is cross-domain, that target is a different origin than the one the
credential was scoped to. The protection is bound to a single header name (Authorization), not to the trust
of the destination.
Reproduction (offline, executed)
Pipeline [RedirectPolicy, AzureKeyCredentialPolicy("api-key", <canary>)] over the OkHttp transport with
followRedirects(false) (so RedirectPolicy is the layer under test). Two loopback HTTPS servers
(self-signed cert, SAN localhost + 127.0.0.1); the trusted host 127.0.0.1 returns 302 to the attacker
host addressed as localhost (a different host string → cross-domain). Authorization is set as a control.
Result on the cross-domain hop:
api-key: "CANARY-JAVA-APIKEY" <- forwarded to the cross-domain host
Authorization: "" <- stripped (control valid)
HTTPS is required because KeyCredentialPolicy.processSync enforces it (IllegalStateException: Key credentials require HTTPS ...); the redirect-forwarding step is scheme-independent, so an HTTPS→HTTPS
cross-domain redirect forwards the key the same way. A minimal runnable Maven project can be shared on request.
Reachability (honest — why this is hardening, not a vulnerability)
RedirectPolicy is not in the azure-core default pipeline; individual client builders add it opt-in.
On current evidence the two relevant client populations are disjoint:
- Clients that add
RedirectPolicy (azure-communication-callautomation, azure-communication-callingserver,
azure-containers-containerregistry) authenticate with HMAC or token credentials placed in Authorization
(which RedirectPolicy strips).
- Clients that use a custom-header
AzureKeyCredentialPolicy (Azure Maps, Event Grid aeg-sas-key,
Form Recognizer / Text Analytics Ocp-Apim-Subscription-Key) do not add RedirectPolicy.
So no shipped client is currently known to combine the two; the leaking configuration requires a consumer to
assemble a pipeline containing both. Hence: hardening, not a shipped-default exposure.
Why it is still worth addressing
Sibling Azure SDKs already treat this class as a destination-trust boundary:
- Azure.Core for .NET added authority-change detection (1.59.0).
- azure-core (Python) ships a
SensitiveHeaderCleanupPolicy on cross-domain redirects.
- azure-sdk-for-js blocks cross-origin redirects by default.
Java's RedirectPolicy currently protects only Authorization, leaving other credential headers that shipped
clients do use (api-key, Ocp-Apim-Subscription-Key, aeg-sas-key) unprotected if such a client ever
follows a cross-domain redirect.
Suggested direction (non-prescriptive)
Bind the redirect protection to the destination's trust rather than to a single header name:
- On a cross-origin redirect, do not follow while credentials are present, or require explicit opt-in (the JS approach).
- Authority-change detection: credential policies skip re-injection when the redirect authority differs (the .NET approach).
- A cross-domain sensitive-header cleanup (as in Python) where credential policies can register their header name, so the cleanup is not limited to
Authorization.
Expected behavior
A request-scoped credential is not carried to a different origin than the one it was scoped to, regardless of which header carries it.
Responsible disclosure note
Reported publicly as defense-in-depth because no reachable shipped-client configuration was found. If a shipped
or widely-used client is identified that installs RedirectPolicy and authenticates via a custom-header
AzureKeyCredentialPolicy, this becomes a reachable credential-disclosure issue and should be routed through
MSRC (https://aka.ms/opensource/security) rather than a public issue.
Library name and version
azure-core1.55.4 (behavior also present onmain)Describe the bug
Filed as defense-in-depth hardening, not an exploitable vulnerability report — reachability is addressed honestly below.
On a redirect,
RedirectPolicy.createRedirectRequestremoves exactly one header before following theLocation:A credential placed in any other header — for example a caller-named key header set by
new AzureKeyCredentialPolicy("api-key" | "Ocp-Apim-Subscription-Key" | "aeg-sas-key", credential)— is notin that removal set.
attemptRedirectSynccopies the request and re-runs the downstream policies(
next.clone().processSync()), so the credential header is re-materialized on the request sent to theredirect target. When the redirect is cross-domain, that target is a different origin than the one the
credential was scoped to. The protection is bound to a single header name (
Authorization), not to the trustof the destination.
Reproduction (offline, executed)
Pipeline
[RedirectPolicy, AzureKeyCredentialPolicy("api-key", <canary>)]over the OkHttp transport withfollowRedirects(false)(soRedirectPolicyis the layer under test). Two loopback HTTPS servers(self-signed cert, SAN
localhost+127.0.0.1); the trusted host127.0.0.1returns302to the attackerhost addressed as
localhost(a different host string → cross-domain).Authorizationis set as a control.Result on the cross-domain hop:
HTTPS is required because
KeyCredentialPolicy.processSyncenforces it (IllegalStateException: Key credentials require HTTPS ...); the redirect-forwarding step is scheme-independent, so an HTTPS→HTTPScross-domain redirect forwards the key the same way. A minimal runnable Maven project can be shared on request.
Reachability (honest — why this is hardening, not a vulnerability)
RedirectPolicyis not in theazure-coredefault pipeline; individual client builders add it opt-in.On current evidence the two relevant client populations are disjoint:
RedirectPolicy(azure-communication-callautomation,azure-communication-callingserver,azure-containers-containerregistry) authenticate with HMAC or token credentials placed inAuthorization(which
RedirectPolicystrips).AzureKeyCredentialPolicy(Azure Maps, Event Gridaeg-sas-key,Form Recognizer / Text Analytics
Ocp-Apim-Subscription-Key) do not addRedirectPolicy.So no shipped client is currently known to combine the two; the leaking configuration requires a consumer to
assemble a pipeline containing both. Hence: hardening, not a shipped-default exposure.
Why it is still worth addressing
Sibling Azure SDKs already treat this class as a destination-trust boundary:
SensitiveHeaderCleanupPolicyon cross-domain redirects.Java's
RedirectPolicycurrently protects onlyAuthorization, leaving other credential headers that shippedclients do use (
api-key,Ocp-Apim-Subscription-Key,aeg-sas-key) unprotected if such a client everfollows a cross-domain redirect.
Suggested direction (non-prescriptive)
Bind the redirect protection to the destination's trust rather than to a single header name:
Authorization.Expected behavior
A request-scoped credential is not carried to a different origin than the one it was scoped to, regardless of which header carries it.
Responsible disclosure note
Reported publicly as defense-in-depth because no reachable shipped-client configuration was found. If a shipped
or widely-used client is identified that installs
RedirectPolicyand authenticates via a custom-headerAzureKeyCredentialPolicy, this becomes a reachable credential-disclosure issue and should be routed throughMSRC (https://aka.ms/opensource/security) rather than a public issue.