Key Vault: ChallengeAuthPolicy request-replay fix (#47742) not ported to azure-keyvault-secrets / -certificates, and absent from all stable releases
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 68/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Tranquilo
- Stack tecnológico
- azure, python
- Área
- api, authentication, cloud
Línea de trabajo
Empieza con azure/keyvault/secrets/_shared/challenge_auth_policy.py y azure/keyvault/certificates/_shared/challenge_auth_policy.py, comparándolos con la copia corregida de keys y con la prueba de regresión test_request_body_not_reused_across_requests de #47742. Ejecuta la reproducción con Mock sin red contra ambas policies no portadas; se considera terminado cuando las solicitudes posteriores conservan su método, URL y body, y el fix está incluido en una versión estable.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
The ChallengeAuthPolicy request-replay bug fixed by #47742 ("Fix Challenge Auth replay bug and update tests", merged 2026-07-08) was applied to azure-keyvault-keys and azure-keyvault-administration, but not to azure-keyvault-secrets or azure-keyvault-certificates, which vendor their own copies of _shared/challenge_auth_policy.py.
Separately, the fix is not present in any stable release yet — every current stable Key Vault package predates the merge, including azure-keyvault-keys itself.
To be clear about severity: I am not reporting this as a security vulnerability. The replayed request goes back to the vault that originally received it, and the bearer token is the shared https://vault.azure.net/.default audience that vault was already sent. verify_challenge_resource additionally blocks the cross-resource case. This is a correctness bug — an intended request is silently replaced by a duplicate of an earlier one.
Current state at HEAD
Occurrence count of _request_copy in each package's _shared/challenge_auth_policy.py:
| Package | _request_copy at HEAD |
Status |
|---|---|---|
azure-keyvault-keys |
1 | fixed by #47742 |
azure-keyvault-administration |
0 | fixed |
azure-keyvault-securitydomain |
0 | not affected |
azure-keyvault-secrets |
4 | unported |
azure-keyvault-certificates |
4 | unported |
In the unfixed copies the stash is stored on the policy instance, which is created once per client rather than once per request:
self._request_copy: Optional[HttpRequest] = None— client-level stateself._request_copy = request.http_request— stores the in-flight requestif self._request_copy: request.http_request = self._request_copy— transplants it onto a later request
Stable releases
| Package | Latest stable | Uploaded | Contains fix? |
|---|---|---|---|
azure-keyvault-secrets |
4.11.0 | 2026-04-17 | no |
azure-keyvault-certificates |
4.11.1 | 2026-05-05 | no |
azure-keyvault-keys |
4.11.1 | 2026-05-19 | no — predates the 2026-07-08 merge |
azure-keyvault-administration |
4.7.0 | 2026-05-19 | no — predates the merge |
The fix currently ships only in the pre-release azure-keyvault-keys 4.12.0b3.
Reproduction
This is the regression test added by #47742 (test_request_body_not_reused_across_requests), re-pointed at the unported packages. No Azure account, no network — the transport is a Mock.
pip install azure-keyvault-secrets==4.11.0 azure-keyvault-certificates==4.11.1 azure-keyvault-keys==4.12.0b3
Send a bodied POST to vault-A (elicits a challenge), then a bodiless GET to vault-B through the same client, and inspect the 4th outbound request:
=== azure-keyvault-keys 4.12.0b3 (POSITIVE CONTROL - contains #47742) ===
4th request method : GET (expected GET)
4th request url : https://vault-b.vault.azure.net/secrets/unrelated
4th request body : None (expected None)
RESULT: ok (per-request stash)
=== azure-keyvault-secrets 4.11.0 (UNPORTED) ===
4th request method : POST (expected GET)
4th request url : https://vault-a.vault.azure.net/secrets/db-password
4th request body : b'a duck' (expected None)
RESULT: prior request replayed
=== azure-keyvault-certificates 4.11.1 (UNPORTED) ===
... identical: POST, vault-a URL, body b'a duck'
The positive control discriminates: the fixed package passes under the identical harness, so the result is a property of the code under test rather than of the test.
Full reproduction script
import time
from unittest.mock import Mock
from azure.core.pipeline import Pipeline
from azure.core.rest import HttpRequest
from azure.core.credentials import AccessToken
CHALLENGE = Mock(
status_code=401,
headers={
"WWW-Authenticate": 'Bearer authorization="https://authority.net/tenant", '
"resource=https://vault.azure.net"
},
)
def exercise(policy_cls, label):
first_content = b"a duck"
first_url = "https://vault-a.vault.azure.net/secrets/db-password"
second_url = "https://vault-b.vault.azure.net/secrets/unrelated"
seen = {}
class C:
n = 0
def send(request):
C.n += 1
if C.n == 1:
return CHALLENGE
if C.n == 2:
return Mock(status_code=200)
if C.n == 3:
return CHALLENGE
if C.n == 4:
seen["method"], seen["url"], seen["body"] = request.method, request.url, request.body
return Mock(status_code=200)
raise ValueError("unexpected request")
cred = Mock(spec_set=["get_token"],
get_token=Mock(return_value=AccessToken("token", time.time() + 3600)))
pipeline = Pipeline(policies=[policy_cls(credential=cred)], transport=Mock(send=send))
req = HttpRequest("POST", first_url)
req.set_bytes_body(first_content)
pipeline.run(req)
pipeline.run(HttpRequest("GET", second_url))
replayed = seen.get("body") == first_content or seen.get("url") == first_url
print(f"\n=== {label} ===")
print(f" 4th request method : {seen.get('method')} (expected GET)")
print(f" 4th request url : {seen.get('url')}")
print(f" expected : {second_url}")
print(f" 4th request body : {seen.get('body')!r} (expected None)")
print(" RESULT: " + ("prior request replayed" if replayed else "ok (per-request stash)"))
return replayed
if __name__ == "__main__":
from azure.keyvault.keys._shared.challenge_auth_policy import ChallengeAuthPolicy as KeysPolicy
from azure.keyvault.secrets._shared.challenge_auth_policy import ChallengeAuthPolicy as SecretsPolicy
from azure.keyvault.certificates._shared.challenge_auth_policy import ChallengeAuthPolicy as CertsPolicy
exercise(KeysPolicy, "azure-keyvault-keys (POSITIVE CONTROL - fixed by #47742)")
exercise(SecretsPolicy, "azure-keyvault-secrets (UNPORTED)")
exercise(CertsPolicy, "azure-keyvault-certificates (UNPORTED)")
Impact
A call intended for one vault is emitted as a duplicate of an earlier call — the intended operation does not happen, and the earlier one is repeated. For secrets and certificates the replayed body is secret material being written a second time, so an unintended duplicate write or rotation is possible.
Suggested fix
Port #47742 to azure-keyvault-secrets and azure-keyvault-certificates (make the stash per-request rather than per-policy), and ship it in a stable release — the fix currently exists only in the pre-release azure-keyvault-keys 4.12.0b3.
Root cause of the divergence is the duplicated _shared directories: each package vendors its own copy of challenge_auth_policy.py, so a fix in one does not propagate. The .NET and Java SDKs are unaffected — they use a single shared implementation.
- Lenguaje dominante
- Python
- Estrellas
- 5.6k
- Forks
- 3.4k
- Merge medio
- 1 d 18 h
- PR fusionados (30 d)
- 202
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de Azure/azure-sdk-for-python
-
Evaluation Service Attention
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Azure/azure-sdk-for-python#49190 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
Update CODEOWNERSAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
Azure/azure-sdk-for-python#49183 · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
Evaluation Service Attention
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Azure/azure-sdk-for-python#49153 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
Search Service Attention
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Azure/azure-sdk-for-python#48555 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
Azure.Core customer-reported feature-request needs-team-attention
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Azure/azure-sdk-for-python#47186 ·
Los mantenedores suelen responder en 1 día
Todos los issues de Azure/azure-sdk-for-python
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
PedestrianDynamics/pyFDS-Evac#343 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
theskumar/python-dotenv#708 ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
Los mantenedores suelen responder en 2 días
-
Docs Timedelta
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
pandas-dev/pandas#69919 ·
Los mantenedores suelen responder en 1 día
-
API documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
zephyrproject-rtos/west#1009 · 2 comentarios ·
Los mantenedores suelen responder en 3 días