Key Vault: ChallengeAuthPolicy request-replay fix (#47742) not ported to azure-keyvault-secrets / -certificates, and absent from all stable releases
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 68/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- azure, python
- Lĩnh vực
- api, authentication, cloud
Hướng nghiên cứu
Bắt đầu với azure/keyvault/secrets/_shared/challenge_auth_policy.py và azure/keyvault/certificates/_shared/challenge_auth_policy.py, so sánh chúng với bản sao keys đã được sửa và regression test test_request_body_not_reused_across_requests từ #47742. Chạy reproduction Mock không có mạng trên cả hai policy chưa được port; được xem là hoàn tất khi các request sau đó vẫn giữ nguyên method, URL và body, và fix được đưa vào một bản phát hành ổn định.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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.
- Ngôn ngữ chính
- Python
- Star
- 5.6k
- Fork
- 3.4k
- Merge trung bình
- 2 ngày 7 giờ
- Pull request đã merge (30 ngày)
- 206
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Azure/azure-sdk-for-python
-
EngSys
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Azure/azure-sdk-for-python#49614 · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Teams Chatbot]: Clarify Python dev-feed publication from a branch versus PyPI releaseCó thể đã có người làm @jianingwang123 đã nhận 5 ngày trước. Đang mởcustomer-reported question
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Azure/azure-sdk-for-python#49548 · 1 bình luận · 2 reaction · 2 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Client customer-reported Hosted Agents needs-team-attention question Service Attention
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Azure/azure-sdk-for-python#49477 · 4 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
azure-ai-evaluation: a content-harm score of 0 (no harm) is parsed as NaN in _parse_content_harm_response (numeric branch uses 0 < x instead of 0 <= x)Có thể đã có người làm @pujitha24 đã nhận 21 ngày trước. Đang mởEvaluation Service Attention
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Azure/azure-sdk-for-python#49190 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Update CODEOWNERSĐang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Azure/azure-sdk-for-python#49183 · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của Azure/azure-sdk-for-python
Issue tương tự
-
wr.dynamodb.read_items returns [{}] for a single partition value that does not existCó thể đã có người làm @auraseb đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
aws/aws-sdk-pandas#3520 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
requires triage typing
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
sqlalchemy/sqlalchemy#13704 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Edit tool converts CRLF files to LFCó thể đã có người làm @RaphaelFakhri đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
huggingface/tau#774 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
show ignores the row limit when a column or table name contains "limit"Có thể đã có người làm @RaphaelFakhri đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
What happened to `check.py`?Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
linebender/resvg#1179 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày