This is an automated email from the ASF dual-hosted git repository.

rahulvats pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new 74f7e73e7d6 [v3-1-test] Fix secrets masking in Rendered Templates for 
complex objects (#61394) (#61763)
74f7e73e7d6 is described below

commit 74f7e73e7d618f21139444471c0522c2ab8b837a
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Feb 12 23:39:04 2026 +0530

    [v3-1-test] Fix secrets masking in Rendered Templates for complex objects 
(#61394) (#61763)
    
    (cherry picked from commit 490eb31ac3275e901fd60c45c632a0032d716074)
    
    Co-authored-by: Amogh Desai <[email protected]>
    Co-authored-by: Rahul Vats <[email protected]>
---
 .../src/airflow/sdk/execution_time/task_runner.py  | 13 ++++++-
 .../task_sdk/execution_time/test_task_runner.py    | 45 ++++++++++++++++++++++
 2 files changed, 57 insertions(+), 1 deletion(-)

diff --git a/task-sdk/src/airflow/sdk/execution_time/task_runner.py 
b/task-sdk/src/airflow/sdk/execution_time/task_runner.py
index f98da597639..974378bd12d 100644
--- a/task-sdk/src/airflow/sdk/execution_time/task_runner.py
+++ b/task-sdk/src/airflow/sdk/execution_time/task_runner.py
@@ -868,13 +868,24 @@ def _serialize_template_field(template_field: Any, name: 
str) -> str | dict | li
             return tuple(sort_dict_recursively(item) for item in obj)
         return obj
 
+    def _fallback_serialization(obj):
+        """Serialize objects with to_dict() method (eg: k8s objects) for 
json.dumps() default parameter."""
+        if hasattr(obj, "to_dict"):
+            return obj.to_dict()
+        raise TypeError(f"cannot serialize {obj}")
+
     max_length = conf.getint("core", "max_templated_field_length")
 
     if not is_jsonable(template_field):
         try:
             serialized = template_field.serialize()
         except AttributeError:
-            serialized = str(template_field)
+            # check if these objects can be converted to JSON serializable 
types
+            try:
+                serialized = json.dumps(template_field, 
default=_fallback_serialization)
+            except (TypeError, ValueError):
+                # fall back to string representation if not
+                serialized = str(template_field)
         if len(serialized) > max_length:
             rendered = redact(serialized, name)
             return (
diff --git a/task-sdk/tests/task_sdk/execution_time/test_task_runner.py 
b/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
index 8bc7282903d..3cd01c4ff7d 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_task_runner.py
@@ -2380,6 +2380,51 @@ class TestRuntimeTaskInstance:
             in mock_supervisor_comms.send.mock_calls
         )
 
+    @pytest.mark.enable_redact
+    def test_rendered_templates_masks_secrets_in_complex_objects(
+        self, create_runtime_ti, mock_supervisor_comms
+    ):
+        """Test that secrets in complex objects like V1EnvVar are masked 
well."""
+        from kubernetes import client as k8s
+
+        from airflow.sdk._shared.secrets_masker import _secrets_masker
+
+        secret1 = "This is a longer test phrase. We are checking if this 
handles regular sentences."
+        secret2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 
minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
commodo consequat."
+        _secrets_masker().add_mask(secret1, None)
+        _secrets_masker().add_mask(secret2, None)
+
+        class CustomOperator(BaseOperator):
+            template_fields = ("env_vars",)
+
+            def __init__(self, env_vars, *args, **kwargs):
+                super().__init__(*args, **kwargs)
+                self.env_vars = env_vars
+
+            def execute(self, context):
+                pass
+
+        env_vars = [
+            k8s.V1EnvVar(name="var1", value="This is a test phrase."),
+            k8s.V1EnvVar(name="var2", value=secret1),
+            k8s.V1EnvVar(name="var3", value=secret2),
+        ]
+
+        task = CustomOperator(
+            task_id="test_complex_object_masking",
+            env_vars=env_vars,
+        )
+
+        runtime_ti = create_runtime_ti(task=task, 
dag_id="test_complex_object_dag")
+        run(runtime_ti, context=runtime_ti.get_template_context(), 
log=mock.MagicMock())
+
+        rendered_fields = 
mock_supervisor_comms.send.mock_calls[0].kwargs["msg"].rendered_fields
+        assert rendered_fields is not None
+        assert (
+            rendered_fields["env_vars"]
+            == '[{"name": "var1", "value": "This is a test phrase.", 
"value_from": null}, {"name": "var2", "value": "***", "value_from": null}, 
{"name": "var3", "value": "***", "value_from": null}]'
+        )
+
 
 class TestXComAfterTaskExecution:
     @pytest.mark.parametrize(

Reply via email to