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

rahulvats 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 a9dea6d0b81 Prevent client secrets and proxy credentials from being 
logged in Microsoft Graph hook logs (#59688)
a9dea6d0b81 is described below

commit a9dea6d0b81d67843c2507bd85af772fde9dd738
Author: Ankit Chaurasia <[email protected]>
AuthorDate: Wed Dec 24 15:56:51 2025 +0545

    Prevent client secrets and proxy credentials from being logged in Microsoft 
Graph hook logs (#59688)
    
    Prevent client secrets and proxy credentials from being logged in Microsoft 
Graph hook logs (#59688)
---
 airflow-core/newsfragments/59688.improvement.rst   |  1 +
 .../src/airflow/providers/common/compat/sdk.py     | 12 +++++++++++-
 .../providers/microsoft/azure/hooks/msgraph.py     |  9 +++++----
 .../unit/microsoft/azure/hooks/test_msgraph.py     | 22 +++++++++++++++++++++-
 .../secrets_masker/secrets_masker.py               |  2 ++
 task-sdk/src/airflow/sdk/log.py                    |  4 +---
 6 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/airflow-core/newsfragments/59688.improvement.rst 
b/airflow-core/newsfragments/59688.improvement.rst
new file mode 100644
index 00000000000..d3c592cfcc3
--- /dev/null
+++ b/airflow-core/newsfragments/59688.improvement.rst
@@ -0,0 +1 @@
+Add ``proxy`` and ``proxies`` to ``DEFAULT_SENSITIVE_FIELDS`` in 
secrets_masker to treat proxy configurations as sensitive by default
diff --git a/providers/common/compat/src/airflow/providers/common/compat/sdk.py 
b/providers/common/compat/src/airflow/providers/common/compat/sdk.py
index f08c358133a..2d6edd2f43f 100644
--- a/providers/common/compat/src/airflow/providers/common/compat/sdk.py
+++ b/providers/common/compat/src/airflow/providers/common/compat/sdk.py
@@ -90,7 +90,8 @@ if TYPE_CHECKING:
         TaskDeferred as TaskDeferred,
         XComNotFound as XComNotFound,
     )
-    from airflow.sdk.observability.stats import Stats  # noqa: F401
+    from airflow.sdk.log import redact as redact
+    from airflow.sdk.observability.stats import Stats as Stats
 
     # Airflow 3-only exceptions (conditionally imported)
     if AIRFLOW_V_3_0_PLUS:
@@ -239,6 +240,15 @@ _IMPORT_MAP: dict[str, str | tuple[str, ...]] = {
     # Observability
     # 
============================================================================
     "Stats": ("airflow.sdk.observability.stats", "airflow.stats"),
+    # 
============================================================================
+    # Secrets Masking
+    # 
============================================================================
+    "redact": (
+        "airflow.sdk.log",
+        "airflow.sdk._shared.secrets_masker",
+        "airflow.sdk.execution_time.secrets_masker",
+        "airflow.utils.log.secrets_masker",
+    ),
 }
 
 # Airflow 3-only exceptions (not available in Airflow 2)
diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
index 84698c3d6a4..6b62fac0b78 100644
--- 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py
@@ -59,6 +59,7 @@ if TYPE_CHECKING:
 
     from airflow.providers.common.compat.sdk import Connection
 
+from airflow.providers.common.compat.sdk import redact
 
 PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]]
 
@@ -303,7 +304,7 @@ class KiotaRequestAdapterHook(BaseHook):
         self.log.info("Host: %s", host)
         self.log.info("Base URL: %s", base_url)
         self.log.info("Client id: %s", client_id)
-        self.log.info("Client secret: %s", client_secret)
+        self.log.info("Client secret: %s", redact(client_secret, 
name="client_secret"))
         self.log.info("API version: %s", api_version)
         self.log.info("Scope: %s", scopes)
         self.log.info("Verify: %s", verify)
@@ -311,8 +312,8 @@ class KiotaRequestAdapterHook(BaseHook):
         self.log.info("Trust env: %s", trust_env)
         self.log.info("Authority: %s", authority)
         self.log.info("Allowed hosts: %s", allowed_hosts)
-        self.log.info("Proxies: %s", proxies)
-        self.log.info("HTTPX Proxies: %s", httpx_proxies)
+        self.log.info("Proxies: %s", redact(proxies, name="proxies"))
+        self.log.info("HTTPX Proxies: %s", redact(httpx_proxies, 
name="proxies"))
         credentials = self.get_credentials(
             login=connection.login,
             password=connection.password,
@@ -433,7 +434,7 @@ class KiotaRequestAdapterHook(BaseHook):
         self.log.info("Certificate data: %s", certificate_data is not None)
         self.log.info("Authority: %s", authority)
         self.log.info("Disable instance discovery: %s", 
disable_instance_discovery)
-        self.log.info("MSAL Proxies: %s", msal_proxies)
+        self.log.info("MSAL Proxies: %s", redact(msal_proxies, name="proxies"))
         if certificate_path or certificate_data:
             return CertificateCredential(
                 tenant_id=tenant_id,
diff --git 
a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py 
b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
index f4d8bba7d8f..6f6216d8fa9 100644
--- a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
+++ b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py
@@ -21,7 +21,7 @@ import inspect
 from json import JSONDecodeError
 from os.path import dirname
 from typing import TYPE_CHECKING, cast
-from unittest.mock import Mock
+from unittest.mock import Mock, patch
 
 import pytest
 from httpx import Response
@@ -412,6 +412,26 @@ class TestKiotaRequestAdapterHook:
             assert isinstance(actual, list)
             assert actual == [users, next_users]
 
+    @pytest.mark.asyncio
+    async def test_build_request_adapter_masks_secrets(self):
+        """Test that sensitive data is masked when building request adapter."""
+        with patch_hook(
+            side_effect=lambda conn_id: get_airflow_connection(
+                conn_id=conn_id,
+                password="my_secret_password",
+                proxies={"http": "http://user:pass@proxy:3128"},
+            )
+        ):
+            with 
patch("airflow.providers.microsoft.azure.hooks.msgraph.redact") as mock_redact:
+                mock_redact.side_effect = lambda x, name=None: "***" if x else 
x
+
+                hook = KiotaRequestAdapterHook(conn_id="msgraph_api")
+                await hook.get_async_conn()
+
+                assert mock_redact.call_count >= 3
+                mock_redact.assert_any_call({"http": 
"http://user:pass@proxy:3128"}, name="proxies")
+                mock_redact.assert_any_call("my_secret_password", 
name="client_secret")
+
 
 class TestResponseHandler:
     def test_default_response_handler_when_json(self):
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 bf3fd91ebd7..e9d439e2e74 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
@@ -58,6 +58,8 @@ DEFAULT_SENSITIVE_FIELDS = frozenset(
         "passwd",
         "password",
         "private_key",
+        "proxy",
+        "proxies",
         "secret",
         "token",
         "keyfile_dict",
diff --git a/task-sdk/src/airflow/sdk/log.py b/task-sdk/src/airflow/sdk/log.py
index 198b156a1d6..9e0835a1efb 100644
--- a/task-sdk/src/airflow/sdk/log.py
+++ b/task-sdk/src/airflow/sdk/log.py
@@ -37,7 +37,7 @@ if TYPE_CHECKING:
     from airflow.sdk.types import Logger, RuntimeTaskInstanceProtocol as 
RuntimeTI
 
 
-__all__ = ["configure_logging", "reset_logging", "mask_secret"]
+from airflow.sdk._shared.secrets_masker import redact
 
 
 class _WarningsInterceptor:
@@ -64,8 +64,6 @@ class _WarningsInterceptor:
 
 
 def mask_logs(logger: Any, method_name: str, event_dict: EventDict) -> 
EventDict:
-    from airflow.sdk._shared.secrets_masker import redact
-
     event_dict = redact(event_dict)  # type: ignore[assignment]
     return event_dict
 

Reply via email to