Repository navigation
[azure-core] In-place set operators on HttpLoggingPolicy.allowed_header_names are silently ignored #49607
Description
Library: azure-core 1.41.0 (also current main)
What happens
HttpLoggingPolicy.allowed_header_names and the allowed_query_params sets of HttpLoggingPolicy and DistributedTracingPolicy are now CaseInsensitiveSet instances. CaseInsensitiveSet subclasses the built-in set and keeps a separate lowercase cache that __contains__ reads. Only add, discard, remove, update, clear and pop maintain that cache. The inherited in-place operators (|=, -=, &=, ^=) and difference_update, intersection_update and symmetric_difference_update change the underlying set but not the cache, so membership checks no longer match the contents.
In practice:
policy.allowed_header_names |= {"x-custom-header"}has no effect: the header is still logged asREDACTED, even thoughlen()and iteration show it.policy.allowed_header_names -= {"x-ms-request-id"}has no effect: the header is still logged in clear text.
Both statements worked when these attributes were plain set objects.
What should happen
Every mutating set operation should keep the set consistent, so the allowlists behave like a normal set with case-insensitive lookups.
Minimal repro
from azure.core.pipeline.policies import HttpLoggingPolicy
policy = HttpLoggingPolicy()
policy.allowed_header_names |= {"x-custom-header"}
print("x-custom-header" in policy.allowed_header_names) # False, expected True
print(policy._redact_header("x-custom-header", "value")) # REDACTED, expected value
policy.allowed_header_names -= {"x-ms-request-id"}
print("x-ms-request-id" in policy.allowed_header_names) # True, expected False
print(policy._redact_header("x-ms-request-id", "value")) # value, expected REDACTEDRoot cause
azure/core/utils/_utils.py: CaseInsensitiveSet(set) overrides __contains__ to look only at _lower_to_original, but does not override the in-place operators or the *_update methods, whose C implementations write to the base set directly. The equivalent class in corehttp derives from MutableSet and does not have this problem, because MutableSet implements those operations on top of add and discard.
Activity
github-actions commentedon Oct 19, 2026
Thank you for your feedback. Tagging and routing to the team member(s) best able to assist.
github-actions commentedon Oct 19, 2026
🎯 Agentic Issue Triage
Summary:
CaseInsensitiveSetinazure-coredoesn't override the inherited in-place set operators (|=,-=,&=,^=) or the*_updatemethods, so these mutate the underlying set without updating the internal lowercase cache, causingHttpLoggingPolicy.allowed_header_names/allowed_query_paramsandDistributedTracingPolicy.allowed_query_paramsmembership checks to silently diverge from the set's actual contents.📋 Issue Details
Package:
azure-core 1.41.0(also reproduces onmain)Affected API:
azure.core.utils._utils.CaseInsensitiveSet, used byHttpLoggingPolicy.allowed_header_names,HttpLoggingPolicy.allowed_query_params, andDistributedTracingPolicy.allowed_query_paramsScenarios:
policy.allowed_header_names |= {"x-custom-header"}adds the item to the underlying set (visible vialen()/iteration) but"x-custom-header" in policy.allowed_header_namesstill returnsFalse, so the header remains redacted.policy.allowed_header_names -= {"x-ms-request-id"}removes the item from the underlying set, but"x-ms-request-id" in policy.allowed_header_namesstill returnsTrue, so the header is not redacted when it should be.Root ask: Make
CaseInsensitiveSetkeep its_lower_to_originalcache consistent across all mutating operations (in-place operators anddifference_update/intersection_update/symmetric_difference_update), matching the behavior of a plainsetprior to this change, or match thecorehttpequivalent which derives fromMutableSet.🔎 Debugging / Reproduction Notes
The reporter already did root-cause analysis:
CaseInsensitiveSetsubclasses the built-insetand overrides onlyadd,discard,remove,update,clear, andpopto maintain a_lower_to_originallowercase cache used by__contains__. The C-implemented in-place operators (|=,-=,&=,^=) and the*_updatemethods bypass these overrides and mutate the basesetstorage directly, leaving the cache stale. The reporter notes thecorehttpequivalent avoids this bug because it derives fromcollections.abc.MutableSet, which builds all set operations on top ofadd/discard.A fix PR (#49608) has already been opened by the reporter: "[azure-core] Keep CaseInsensitiveSet consistent for in-place set operations".
Suggested investigation steps:
|=,-=,&=,^=,difference_update,intersection_update,symmetric_difference_update).HttpLoggingPolicyandDistributedTracingPolicybehavior is correctly restored for both header and query-param allowlists.CaseInsensitiveSetto aMutableSet-based implementation (likecorehttp) would be a more robust long-term fix.🏷️ Label Confidence
Client— azure-core is a shared data-plane library, not underazure-mgmt-*.Azure.Core— issue explicitly namesazure-coreandazure/core/utils/_utils.py.👥 Owner Routing
# ServiceLabel: %Azure.Core(line 19) — exact match for predicted service label, found scanning bottom-to-top.@kashifkhan@kashifkhan; addedneeds-team-attentionsince issue is customer-reported.%Core.Httpentry (also owned by@kashifkhan) was scanned first but does not match the predicted "Azure.Core" label, so it was skipped.Note: a fix PR (#49608) is already linked to this issue.
kashifkhan commentedon Oct 19, 2026
Hi Sebastian Turner (@auraseb), thank you for the issue and PR. Can you point to an example when this happened while using an SDK scenario please?
github-actions commentedon Oct 19, 2026
Hi Sebastian Turner (@auraseb). Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue.
auraseb commentedon Oct 19, 2026
Thanks for looking. I found it reading the policy code after the allowlists became
CaseInsensitiveSet, not from a production incident, so I do not have a customer trace to point to.The scenario is customizing logging on a client, for example
policy.allowed_header_names |= {"x-ms-client-request-id"}to stop a header from being redacted, orpolicy.allowed_header_names -= {"x-ms-request-id"}to keep one out of logs. Both worked while these were plain sets, and code written that way now silently does nothing after upgrading. The second case is the one that matters more, since a header someone meant to hide keeps being logged in clear text.I also pushed a fix for the mypy errors in the PR.