This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new bfa23909190 Mask registered secrets at any nesting depth (#73793)
bfa23909190 is described below
commit bfa2390919046ddcee5e568362adff7725672886
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Oct 19 16:56:10 2026 +0200
Mask registered secrets at any nesting depth (#73793)
* Mask registered secrets at any nesting depth
Pattern-based masking of registered secrets stopped at max_depth: a
string nested deeper than that was returned without the replacer being
applied, while key-name masking already worked at any depth. Apply the
replacer to string leaves wherever they sit. max_depth still bounds the
fail-closed walk of a value already known to be sensitive.
Generated-by: Claude Opus 5
* Add newsfragment
Generated-by: Claude Opus 5
---
airflow-core/newsfragments/73793.improvement.rst | 1 +
.../src/airflow_shared/secrets_masker/secrets_masker.py | 15 ++++++---------
.../tests/secrets_masker/test_secrets_masker.py | 11 ++++++++---
3 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/airflow-core/newsfragments/73793.improvement.rst
b/airflow-core/newsfragments/73793.improvement.rst
new file mode 100644
index 00000000000..fa43fd229c7
--- /dev/null
+++ b/airflow-core/newsfragments/73793.improvement.rst
@@ -0,0 +1 @@
+Mask registered secrets at any nesting depth. Secret values registered with
the masker are now replaced wherever they appear in a nested structure, instead
of only up to the masker's maximum depth.
diff --git
a/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
b/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
index b28d0d1a358..178b451c10b 100644
--- a/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
+++ b/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py
@@ -363,10 +363,12 @@ class SecretsMasker(logging.Filter):
self, item: Redactable, name: str | None, depth: int, max_depth: int,
replacement: str = "***"
) -> Redacted:
try:
- # Key-name-based redaction is unbounded by depth — sensitive keys
- # must fail closed at any nesting level. The depth cutoff below is
- # only used to bound the work of pattern-based string masking and
- # to terminate recursion through self-referential iterables.
+ # Both key-name-based and pattern-based redaction apply at any
+ # nesting depth: a registered secret or a sensitive key must fail
+ # closed wherever it sits in the structure. ``max_depth`` only
bounds
+ # the fail-closed walk of a value already known to be sensitive
+ # (``_redact_all``); self-referential structures are stopped by
+ # Python's recursion limit and the except clause below.
if name and self.should_hide_value_for_key(name):
return self._redact_all(item, depth, max_depth,
replacement=replacement)
# Always walk dicts so deeper sensitive keys are still caught;
@@ -402,11 +404,6 @@ class SecretsMasker(logging.Filter):
)
for subval in item
]
- # The depth cutoff only bounds the work of pattern-based string
- # masking below — key-name redaction (dicts and iterables above) is
- # unbounded so sensitive keys fail closed at any depth.
- if depth > max_depth:
- return item
if isinstance(item, Enum):
return self._redact(
item=item.value, name=name, depth=depth,
max_depth=max_depth, replacement=replacement
diff --git a/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
b/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
index ebecfc7279d..f993a723eba 100644
--- a/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
+++ b/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
@@ -675,9 +675,14 @@ class TestSecretsMasker:
(["abcdef"], ["***"], 1),
([[[["abcdef"]]]], [[[["***"]]]], None),
([[[[["abcdef"]]]]], [[[[["***"]]]]], None),
- # Items below max depth aren't redacted
- ([[[[[["abcdef"]]]]]], [[[[[["abcdef"]]]]]], None),
- ([["abcdef"]], [["abcdef"]], 1),
+ # Registered secrets are redacted at any depth, whatever max_depth
says
+ ([[[[[["abcdef"]]]]]], [[[[[["***"]]]]]], None),
+ ([["abcdef"]], [["***"]], 1),
+ (
+ {"a": {"b": {"c": {"d": {"e": {"f": {"g":
"prefix-abcdef-suffix"}}}}}}},
+ {"a": {"b": {"c": {"d": {"e": {"f": {"g":
"prefix-***-suffix"}}}}}}},
+ None,
+ ),
],
)
def test_redact_max_depth(self, val, expected, max_depth):