-
Notifications
You must be signed in to change notification settings - Fork 16.5k
Fix recursion depth error in _redact_exception_with_context #61776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix recursion depth error in _redact_exception_with_context #61776
Conversation
42caffe to
5326a05
Compare
shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
Outdated
Show resolved
Hide resolved
shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
Show resolved
Hide resolved
Fix recursion depth check in _redact_exception_with_context_or_cause. There are some obscure cases where exception might point to itself in cause/context - this PR protects against it. Changed name to include cause as well. Initially implemented as a fix to v2-11-test in apache#61254 - enhanced with the case of removal of too-deep exceptions rather than not-redacting it (and replacing it with sentinel exception explaining that reminder of the stack trace has been removed. Co-authored-by: Anton Nitochkin <[email protected]>
5326a05 to
cc6b24c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR updates the SecretsMasker exception redaction logic to safely handle circular __context__/__cause__ chains and to cap traversal using a recursion/visit limit, preventing RecursionError during log masking.
Changes:
- Replace
_redact_exception_with_contextwith_redact_exception_with_context_or_cause, adding cycle detection and a max-visit limit with a sentinel exception when truncating. - Update the log filter to call the new redaction helper.
- Add extensive unit tests covering context/cause chaining, cycles, branching, and depth-limit behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py |
Introduces cycle/limit-aware exception redaction and switches the filter to use it. |
shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py |
Adds new tests for the updated exception redaction behavior across multiple chaining scenarios. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _redact_exception_with_context_or_cause(self, exception, visited=None): | ||
| # Exception class may not be modifiable (e.g. declared by an | ||
| # extension module such as JDBC). | ||
| with contextlib.suppress(AttributeError): | ||
| exception.args = (self.redact(v) for v in exception.args) | ||
| if exception.__context__: | ||
| self._redact_exception_with_context(exception.__context__) | ||
| if exception.__cause__ and exception.__cause__ is not exception.__context__: | ||
| self._redact_exception_with_context(exception.__cause__) | ||
| if visited is None: | ||
| visited = set() |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contextlib.suppress(AttributeError) currently wraps the entire traversal, not just the exception.args assignment. If an exception type has non-settable args (the comment mentions extension-module exceptions), the AttributeError will cause the function to skip redacting/truncating __context__/__cause__, potentially leaving sensitive values unredacted. Narrow the suppress(AttributeError) scope to only the exception.args = ... line (and optionally to assignments to __context__/__cause__ if needed), while keeping visited/recursion-limit logic outside the suppress block.
|
|
||
| def test_redact_exception_with_context_simple(self): | ||
| """ | ||
| Test _redact_exception_with_context with a simple exception without context. |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This docstring/test text still refers to _redact_exception_with_context, but the method under test is now _redact_exception_with_context_or_cause. Please rename the wording here to match the new method name (and mention both context/cause if that’s what’s being exercised).
| Test _redact_exception_with_context with a simple exception without context. | |
| Test _redact_exception_with_context_or_cause with a simple exception without context or cause. |
| def test_redact_exception_with_max_context_recursion_depth(self): | ||
| """ | ||
| Test _redact_exception_with_context respects MAX_RECURSION_DEPTH. | ||
| Exceptions beyond the depth limit should be skipped (not redacted). |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring here says exceptions beyond the depth limit are "skipped (not redacted)", but the implementation replaces the truncated tail with a sentinel exception (and drops further chaining). Update the docstring to reflect the sentinel/truncation behavior so the test intent is accurate.
| Exceptions beyond the depth limit should be skipped (not redacted). | |
| Once the depth limit is reached, the remaining exception chain is not traversed; | |
| instead, it is truncated and replaced with a sentinel exception indicating the | |
| recursion limit was hit, and further chaining is dropped. |
| def test_redact_exception_with_immutable_args(self): | ||
| """ | ||
| Test _redact_exception_with_context handles exceptions with immutable args gracefully. | ||
| """ | ||
| masker = SecretsMasker() |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_redact_exception_with_immutable_args only asserts the method doesn’t crash. Since the production logic should still traverse/redact __context__/__cause__ even when args can’t be set, this test won’t catch regressions where an AttributeError stops redaction of chained exceptions. Consider adding a __context__/__cause__ containing a secret and asserting it still gets redacted/truncated.
jscheffl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wanted to have AI used again. Not too bad as review. But approved anyway.
Fix recursion depth check in _redact_exception_with_context_or_cause. There are some obscure cases where exception might point to itself in cause/context - this PR protects against it.
Changed name to include cause as well.
Initially implemented as a fix to v2-11-test in #61254 - enhanced with the case of removal of too-deep exceptions rather than not-redacting it (and replacing it with sentinel exception explaining that reminder of the stack trace has been removed.
Was generative AI tooling used to co-author this PR?
Generated-by: Copilot + Claude Sonnet 4.5 following the guidelines
{pr_number}.significant.rstor{issue_number}.significant.rst, in airflow-core/newsfragments.