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 8400915021a Mask the whole secret when another secret is its prefix
(#76142)
8400915021a is described below
commit 8400915021ab0ef1c6cb0a369d737ccd8f4865ed
Author: Aaron Thomas <[email protected]>
AuthorDate: Sun Nov 1 18:15:51 2026 +0530
Mask the whole secret when another secret is its prefix (#76142)
The masker joins every registered secret into one regex alternation, which
takes the first alternative that matches rather than the longest. When one
secret is a prefix of another, the order of the set-backed patterns decided
whether the longer secret was fully masked or had its tail left in the
output, so the leak varied from process to process with the hash seed.
Compile the alternation longest-first so the longest registered secret
always wins.
---
.../src/airflow_shared/secrets_masker/secrets_masker.py | 4 +++-
.../tests/secrets_masker/test_secrets_masker.py | 11 +++++++++++
2 files changed, 14 insertions(+), 1 deletion(-)
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 178b451c10b..c2d2a744d59 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
@@ -638,7 +638,9 @@ class SecretsMasker(logging.Filter):
self.patterns.add(pattern)
new_mask = True
if new_mask:
- self.replacer = re.compile("|".join(self.patterns))
+ # Regex alternation takes the first alternative that matches,
not the longest, so
+ # longer secrets must come first or a secret that is a prefix
of another leaves its tail.
+ self.replacer = re.compile("|".join(sorted(self.patterns,
key=len, reverse=True)))
elif isinstance(secret, collections.abc.Iterable):
for v in secret:
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 f993a723eba..01d9142da1a 100644
--- a/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
+++ b/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
@@ -638,6 +638,17 @@ class TestSecretsMasker:
assert filt.redact(value, name) == expected
+ def test_redact_masks_whole_secret_when_another_secret_is_its_prefix(self):
+ secret = "hunter2-production-key"
+ filt = SecretsMasker()
+ configure_secrets_masker_for_test(filt)
+ # ``patterns`` is a set, so every prefix is registered to make the
result independent of its
+ # iteration order.
+ for length in range(5, len(secret) + 1):
+ filt.add_mask(secret[:length])
+
+ assert filt.redact(f"token={secret};") == "token=***;"
+
@pytest.mark.parametrize(
("name", "value", "expected"),
[