Skip to content

[EventGrid] Fix credential leak on cross-host redirect in legacy publisher (MSRC 126697) - #48239

Merged
Nishanth-MS merged 42 commits into
mainfrom
Nishanth-MS/eventgrid-python-redirect-credential-leak
Jul 29, 2026
Merged

[EventGrid] Fix credential leak on cross-host redirect in legacy publisher (MSRC 126697)#48239
Nishanth-MS merged 42 commits into
mainfrom
Nishanth-MS/eventgrid-python-redirect-credential-leak

Conversation

@Nishanth-MS

Copy link
Copy Markdown
Member

Summary

Fixes a credential-leak vulnerability (MSRC 126697 / ICM 31000000661197) in the legacy EventGridPublisherClient.

When the server returns an HTTP 3xx redirect to a different host, the client re-attached the caller's credential header to the redirected request and sent it to the new host, disclosing the credential.

Root cause: the legacy client hand-rolls its pipeline (_policies()) and omitted azure-core's SensitiveHeaderCleanupPolicy. The stateless auth policies re-add the credential header on every redirect hop; RedirectPolicy flags the cross-domain hop (insecure_domain_change) but nothing consumed that flag to strip the header.

Fix

Add SensitiveHeaderCleanupPolicy after the authentication policy in both the sync (_legacy/_publisher_client.py) and async (_legacy/aio/_publisher_client_async.py) pipelines. The policy's default only covers Authorization, so the Event Grid SAS headers are added explicitly:

SensitiveHeaderCleanupPolicy(blocked_redirect_headers=[
    "Authorization", "x-ms-authorization-auxiliary",
    "aeg-sas-key", "aeg-sas-token",
])

This covers all three credential types:

  • AAD (TokenCredential) -> Authorization
  • Key (AzureKeyCredential) -> aeg-sas-key
  • SAS (AzureSasCredential) -> aeg-sas-token

Tests

tests/test_eg_redirect.py drives the pipeline through a mock transport that issues a cross-host 301 redirect and asserts the credential header is stripped from the redirected request, for all three credential types, sync and async.

Changelog / version

  • Bumped 4.22.0 -> 4.22.1 (backward-compatible security patch).
  • Added a Bugs Fixed CHANGELOG entry.

Notes

  • This is an MSRC security fix; resolving the incident additionally requires MSRC advisory/CVE coordination.
  • The same class of issue was found in the Go SDK (aeg-sas-key/aeg-sas-token leak by default) and as latent hardening gaps in JS/Java/.NET; those are tracked separately.

@Nishanth-MS
Nishanth-MS requested a review from shankarsama as a code owner July 24, 2026 10:18
Copilot AI review requested due to automatic review settings July 24, 2026 10:18
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
9 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

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

Fixes cross-host redirect credential leakage in the legacy Event Grid publisher clients.

Changes:

  • Adds sensitive-header cleanup to sync and async pipelines.
  • Adds redirect regression tests for all credential types.
  • Bumps the patch version and updates the changelog.

Reviewed changes

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

Show a summary per file
File Description
tests/test_eg_redirect.py Tests credential stripping after redirects.
CHANGELOG.md Documents the security fix.
_version.py Bumps version to 4.22.1.
_legacy/_publisher_client.py Adds sync header cleanup.
_legacy/aio/_publisher_client_async.py Adds async header cleanup.
Comments suppressed due to low confidence (2)

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/_publisher_client.py:138

  • This remains vulnerable when the redirected host returns a retryable response. The package still permits azure-core>=1.30.0, but SensitiveHeaderCleanupPolicy did not preserve the cross-domain flag across retries until azure-core 1.38.3; on older supported versions the retry auth pass can re-add the credential after cleanup. Require azure-core 1.38.3+ (adjusting Python support metadata as needed), or implement equivalent retry-safe cleanup locally, and cover a 301 -> 500 -> 200 sequence.
            SensitiveHeaderCleanupPolicy(

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/aio/_publisher_client_async.py:132

  • The async pipeline has the same retry gap: with the declared azure-core>=1.30.0, versions before 1.38.3 lose the cross-domain flag after the first redirected send, allowing the async auth policy to restore the credential on a retry to the new host. Require azure-core 1.38.3+ (and reconcile Python support), or implement retry-safe cleanup locally, with a 301 -> 500 -> 200 regression test.
            SensitiveHeaderCleanupPolicy(

Comment thread sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/_publisher_client.py Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 10:32

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 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread sdk/eventgrid/azure-eventgrid/tests/test_eg_redirect.py Outdated
Comment thread sdk/eventgrid/azure-eventgrid/setup.py
@github-actions

This comment has been minimized.

Copilot AI review requested due to automatic review settings July 24, 2026 14:18
@Nishanth-MS

Copy link
Copy Markdown
Member Author

Thanks for the review — addressed all comments:

1. Inaccurate policy comment (sync + async): Reworded to note that SensitiveHeaderCleanupPolicy already covers Authorization and x-ms-authorization-auxiliary by default, and only the Event Grid SAS headers (aeg-sas-key/aeg-sas-token) are added here.

2. Retry gap before azure-core 1.38.3: Raised the minimum dependency to azure-core>=1.38.3 (verified: 1.38.3 is where insecure_domain_change persistence across retries was fixed) and added a 301 -> 500 -> 200 regression test asserting the credential stays stripped across the retry, for all three credential types.

3. Async AAD test used a sync credential: Added an AsyncFakeTokenCredential (async def get_token) and a separate ASYNC_CREDENTIAL_CASES so the async AAD path is exercised correctly (no TypeError).

4. Python 3.8 vs azure-core 1.38.3: azure-core dropped 3.8 in 1.33.0, so bumped python_requires to >=3.9 and removed the 3.8 classifier.

Note on the CI failures (pylint in _model_base.py/_serialization.py and test_cncf_events/test_cncf_events_async): these are in auto-generated files and the CloudEvents conversion path, none of which are touched by this PR (which only changes the 6 files above). They appear pre-existing/environmental (the pylint items are in generated code; the cncf failures track the cloudevents<=2.0.0 pin) rather than introduced by this change.

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 6 out of 6 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 24, 2026 14:26
@Nishanth-MS

Copy link
Copy Markdown
Member Author

Addressed all three review comments:

1 & 2. Duplicate blocked_redirect_headers kwarg (sync + async): Fixed. _policies now builds the list once by merging the mandatory Event Grid headers with any caller-supplied blocked_redirect_headers and pops the key from kwargs before forwarding, so passing that pipeline option no longer raises TypeError:

blocked_redirect_headers = [
    "Authorization", "x-ms-authorization-auxiliary", "aeg-sas-key", "aeg-sas-token",
    *kwargs.pop("blocked_redirect_headers", []),
]
...
SensitiveHeaderCleanupPolicy(blocked_redirect_headers=blocked_redirect_headers, **kwargs)

(The pop is scoped to _policies — its **kwargs is a fresh dict, so __init__'s kwargs are unaffected.)

  1. Full-file churn in _model_base.py / _serialization.py: You were right — my earlier local edits rewrote the files with CRLF, producing whole-file diffs. Regenerated the edits from the pristine main files preserving LF, so the diffs are now exactly 3 lines (_model_base.py) and 6 lines (_serialization.py) — only the inline # pylint: disable additions, no line-ending/blame churn.

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_model_base.py:780

  • This added pragma pushes the line past the 120-character pylint limit configured at pylintrc:25. Reflow it or include the resulting line-too-long diagnostic in the targeted suppression.
                    next(a for a in annotation.__args__ if a != type(None)), module, rf  # pyright: ignore # pylint: disable=unidiomatic-typecheck

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_model_base.py:786

  • The appended suppression takes this line beyond the repository's 120-character maximum (pylintrc:25), which can make pylint fail with line-too-long. Please wrap the expression or suppress that diagnostic explicitly.
            annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a != type(None)]  # pyright: ignore # pylint: disable=unidiomatic-typecheck

Comment thread sdk/eventgrid/azure-eventgrid/azure/eventgrid/_model_base.py
@Nishanth-MS

Copy link
Copy Markdown
Member Author

/azp run python - pullrequest

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@Nishanth-MS

Copy link
Copy Markdown
Member Author

Re: the line-too-long concern on _model_base.py (lines 777/780/786) — verified this does not fire:

  • Ran pylint --rcfile=<repo>/pylintrc --disable=all --enable=line-too-long on the committed file → 10.00/10, no C0301. pylint exempts lines whose length comes from a trailing # pylint: pragma, so the inline # pyright: ignore # pylint: disable=unidiomatic-typecheck does not trigger line-too-long.
  • These lines are byte-identical to the sanctioned generator convention: azure-batch/azure/batch/_model_base.py (on main, shipping, CI-green) has the exact same three lines at the same lengths (125/146/154) with the same header (# pylint: disable=too-many-lines, no line-too-long disable).
  • CI Build Analyze (pylint 4.0.4) on this PR is green.

No change needed; resolving.

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 13 out of 13 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/_publisher_client.py:133

  • An explicit blocked_redirect_headers=None is valid for SensitiveHeaderCleanupPolicy, but unpacking it here raises TypeError during client construction. Normalize the optional value before merging so the policy's documented default form remains usable.
            *kwargs.pop("blocked_redirect_headers", []),

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/aio/_publisher_client_async.py:127

  • An explicit blocked_redirect_headers=None is valid for SensitiveHeaderCleanupPolicy, but unpacking it here raises TypeError during async client construction. Normalize the optional value before merging so the policy's documented default form remains usable.
            *kwargs.pop("blocked_redirect_headers", []),

Copilot AI review requested due to automatic review settings July 28, 2026 11:41

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 13 out of 13 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/_publisher_client.py:133

  • SensitiveHeaderCleanupPolicy accepts blocked_redirect_headers=None, but explicitly forwarding that value through the client now raises TypeError: Value after * must be an iterable before the pipeline is created. Treat None as “no additional headers” while retaining the mandatory Event Grid headers.
            *kwargs.pop("blocked_redirect_headers", []),

sdk/eventgrid/azure-eventgrid/azure/eventgrid/_legacy/aio/_publisher_client_async.py:127

  • The async client has the same regression: explicitly passing blocked_redirect_headers=None, which is valid for SensitiveHeaderCleanupPolicy, attempts to unpack None and fails during construction. Normalize None to an empty list before merging the mandatory headers.
            *kwargs.pop("blocked_redirect_headers", []),

Comment thread sdk/eventgrid/azure-eventgrid/README.md Outdated
Comment thread sdk/eventgrid/azure-eventgrid/setup.py Outdated
Comment thread sdk/eventgrid/azure-eventgrid/setup.py
Comment thread sdk/eventgrid/azure-eventgrid/setup.py Outdated
Copilot AI review requested due to automatic review settings July 28, 2026 13:37
@Nishanth-MS

Copy link
Copy Markdown
Member Author

Thanks @kashifkhan — updated the baseline to Python 3.10 per your feedback:

  • setup.py: python_requires=">=3.10"; removed the 3.9 classifier and extended classifiers to 3.10, 3.11, 3.12, 3.13, 3.14.
  • README.md: Prerequisites now say "Python 3.10 or later".
  • CHANGELOG.md: note updated to "Increased the minimum supported Python version to 3.10. Python 3.9 and earlier are no longer supported."

(The bump was originally driven by azure-core>=1.38.3; 3.10 is a safe floor above that.)

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 13 out of 13 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

sdk/eventgrid/azure-eventgrid/setup.py:69

  • The PR describes 4.22.1 as a backward-compatible security patch, but this change makes the patch release uninstallable on Python 3.8 and 3.9, which 4.22.0 supported. Please either retain the existing Python floor for this patch release, or use an appropriate non-patch release and remove the backward-compatible claim; the README, classifiers, changelog, and version must remain aligned with that choice.
    python_requires=">=3.10",

@Nishanth-MS
Nishanth-MS merged commit 40b8c4c into main Jul 29, 2026
26 checks passed
@Nishanth-MS
Nishanth-MS deleted the Nishanth-MS/eventgrid-python-redirect-credential-leak branch July 29, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants