This is an automated email from the ASF dual-hosted git repository.
jscheffl 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 5151f6cd203 Fix SecretsMasker merge round-trip for Kubernetes env vars
(#67122)
5151f6cd203 is described below
commit 5151f6cd2033bfcc3ffdb356330333de8a6591d5
Author: Henry Chen <[email protected]>
AuthorDate: Thu May 28 01:08:33 2026 +0800
Fix SecretsMasker merge round-trip for Kubernetes env vars (#67122)
---
.../secrets_masker/secrets_masker.py | 8 +++++
.../tests/secrets_masker/test_secrets_masker.py | 35 ++++++++++++++++++++++
2 files changed, 43 insertions(+)
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 6ae5343ec00..03198bf48a8 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
@@ -445,12 +445,20 @@ class SecretsMasker(logging.Filter):
# Determine if we should treat this as sensitive
is_sensitive = force_sensitive or (name is not None and
self.should_hide_value_for_key(name))
+ v1_env_var_name = None
+ if isinstance(new_item, dict) and _is_v1_env_var(old_item):
+ # redact(V1EnvVar) returns a dict, so merge against the old
object's serialized shape.
+ old_item = old_item.to_dict()
+ v1_env_var_name = old_item.get("name")
+
if isinstance(new_item, dict) and isinstance(old_item, dict):
merged = {}
for key in new_item.keys():
if key in old_item:
# For dicts, pass the key as name unless we're in
sensitive mode
child_name = None if is_sensitive else key
+ if key == "value" and v1_env_var_name:
+ child_name = v1_env_var_name
merged[key] = self._merge(
new_item[key],
old_item[key],
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 1e8b50522e2..1f107fee07e 100644
--- a/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
+++ b/shared/secrets_masker/tests/secrets_masker/test_secrets_masker.py
@@ -1482,6 +1482,41 @@ class TestSecretsMaskerMerge:
assert result == new_enum
assert isinstance(result, MyEnum)
+ def test_merge_round_trip_kubernetes_env_var(self):
+ class MockV1EnvVar:
+ def __init__(self, name, value, value_from=None):
+ self.name = name
+ self.value = value
+ self.value_from = value_from
+
+ def to_dict(self):
+ return {"name": self.name, "value": self.value, "value_from":
self.value_from}
+
+ original_env_var = MockV1EnvVar("password", "original_password",
"original_source")
+ normal_env_var = MockV1EnvVar("app_name", "original_app")
+
+ with patch(
+ "airflow_shared.secrets_masker.secrets_masker._is_v1_env_var",
+ side_effect=lambda item: isinstance(item, MockV1EnvVar),
+ ):
+ redacted_env_var = self.masker.redact(original_env_var)
+ redacted_env_var["value_from"] = "***"
+
+ merged = self.masker.merge(redacted_env_var, original_env_var)
+
+ updated_env_var = {**redacted_env_var, "value": "updated_password"}
+ merged_updated = self.masker.merge(updated_env_var,
original_env_var)
+
+ normal_merged = self.masker.merge({"name": "app_name", "value":
"***"}, normal_env_var)
+
+ assert merged == {
+ "name": "password",
+ "value": "original_password",
+ "value_from": "***",
+ }
+ assert merged_updated["value"] == "updated_password"
+ assert normal_merged["value"] == "***"
+
def test_merge_round_trip(self):
# Original data with sensitive information
original_config = {