From 1b5e8a83744e282194f5c57e7f3a45b35b0c1ddd Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:07:38 +0545 Subject: [PATCH 1/9] Prevent client secrets and proxy credentials from being logged in plain text at INFO level. --- .../microsoft/azure/hooks/msgraph.py | 34 ++++++++-- .../microsoft/azure/hooks/test_msgraph.py | 65 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) 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 84698c3d6a457..ac25fe6029fc9 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 @@ -58,6 +58,16 @@ from kiota_abstractions.serialization import ParsableFactory from airflow.providers.common.compat.sdk import Connection + from airflow.sdk._shared.secrets_masker import mask_secret, redact +else: + try: + from airflow.sdk._shared.secrets_masker import redact + from airflow.sdk.log import mask_secret + except ImportError: + try: + from airflow.sdk._shared.secrets_masker import mask_secret, redact + except ImportError: + from airflow.utils.log.secrets_masker import mask_secret, redact PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]] @@ -261,6 +271,19 @@ def to_httpx_proxies(cls, proxies: dict | None) -> dict | None: return proxies return None + @staticmethod + def _register_proxy_secrets(proxies: dict | None) -> None: + """Extract and register proxy credentials as secrets to be masked in logs.""" + if not proxies: + return + for value in proxies.values(): + if isinstance(value, str): + parsed = urlparse(value) + if parsed.password: + mask_secret(parsed.password) + if parsed.username: + mask_secret(parsed.username) + def to_msal_proxies(self, authority: str | None, proxies: dict | None) -> dict | None: self.log.debug("authority: %s", authority) if authority and proxies: @@ -295,6 +318,8 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: trust_env = config.get("trust_env", False) allowed_hosts = (config.get("allowed_hosts", authority) or "").split(",") + self._register_proxy_secrets(proxies) + self.log.info( "Creating Microsoft Graph SDK client %s for conn_id: %s", api_version, @@ -303,7 +328,7 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: 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 +336,8 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: 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)) + self.log.info("HTTPX Proxies: %s", redact(httpx_proxies)) credentials = self.get_credentials( login=connection.login, password=connection.password, @@ -428,12 +453,13 @@ def get_credentials( certificate_data = config.get("certificate_data") disable_instance_discovery = config.get("disable_instance_discovery", False) msal_proxies = self.to_msal_proxies(authority=authority, proxies=proxies) + self._register_proxy_secrets(msal_proxies) self.log.info("Tenant id: %s", tenant_id) self.log.info("Certificate path: %s", certificate_path) 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)) 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 f4d8bba7d8ff6..d612ccf7f66d7 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 @@ 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,69 @@ async def test_paginated_run(self): assert isinstance(actual, list) assert actual == [users, next_users] + def test_register_proxy_secrets_with_credentials(self): + """Test that proxy credentials are registered as secrets.""" + proxies = { + "http": "http://user:secret_pass@proxy.example.com:3128", + "https": "https://admin:super_secret@proxy.example.com:3128", + } + + with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: + KiotaRequestAdapterHook._register_proxy_secrets(proxies) + + assert mock_mask_secret.call_count == 4 + mock_mask_secret.assert_any_call("secret_pass") + mock_mask_secret.assert_any_call("user") + mock_mask_secret.assert_any_call("super_secret") + mock_mask_secret.assert_any_call("admin") + + def test_register_proxy_secrets_without_credentials(self): + """Test that no secrets are registered when proxies have no credentials.""" + proxies = { + "http": "http://proxy.example.com:3128", + "https": "https://proxy.example.com:3128", + } + + with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: + KiotaRequestAdapterHook._register_proxy_secrets(proxies) + + mock_mask_secret.assert_not_called() + + def test_register_proxy_secrets_with_none(self): + """Test that no error occurs when proxies is None.""" + with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: + KiotaRequestAdapterHook._register_proxy_secrets(None) + + mock_mask_secret.assert_not_called() + + def test_register_proxy_secrets_with_empty_dict(self): + """Test that no error occurs when proxies is an empty dict.""" + with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: + KiotaRequestAdapterHook._register_proxy_secrets({}) + + mock_mask_secret.assert_not_called() + + @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.mask_secret") as mock_mask_secret: + 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() + + mock_mask_secret.assert_any_call("pass") + mock_mask_secret.assert_any_call("user") + assert mock_redact.call_count >= 3 + class TestResponseHandler: def test_default_response_handler_when_json(self): From c01e86f1348ddd4276bba05a4f6b50ecfa07a310 Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Mon, 22 Dec 2025 02:21:48 +0545 Subject: [PATCH 2/9] Add 'proxies' to DEFAULT_SENSITIVE_FIELDS and use redact() with name parameter to automatically mask proxy configurations containing credentials. --- .../microsoft/azure/hooks/msgraph.py | 29 ++-------- .../microsoft/azure/hooks/test_msgraph.py | 57 +++---------------- .../secrets_masker/secrets_masker.py | 1 + 3 files changed, 14 insertions(+), 73 deletions(-) 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 ac25fe6029fc9..eba9a1f9a9f06 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 @@ -58,16 +58,15 @@ from kiota_abstractions.serialization import ParsableFactory from airflow.providers.common.compat.sdk import Connection - from airflow.sdk._shared.secrets_masker import mask_secret, redact + from airflow.sdk._shared.secrets_masker import redact else: try: from airflow.sdk._shared.secrets_masker import redact - from airflow.sdk.log import mask_secret except ImportError: try: - from airflow.sdk._shared.secrets_masker import mask_secret, redact + from airflow.sdk._shared.secrets_masker import redact except ImportError: - from airflow.utils.log.secrets_masker import mask_secret, redact + from airflow.utils.log.secrets_masker import redact PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]] @@ -271,19 +270,6 @@ def to_httpx_proxies(cls, proxies: dict | None) -> dict | None: return proxies return None - @staticmethod - def _register_proxy_secrets(proxies: dict | None) -> None: - """Extract and register proxy credentials as secrets to be masked in logs.""" - if not proxies: - return - for value in proxies.values(): - if isinstance(value, str): - parsed = urlparse(value) - if parsed.password: - mask_secret(parsed.password) - if parsed.username: - mask_secret(parsed.username) - def to_msal_proxies(self, authority: str | None, proxies: dict | None) -> dict | None: self.log.debug("authority: %s", authority) if authority and proxies: @@ -318,8 +304,6 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: trust_env = config.get("trust_env", False) allowed_hosts = (config.get("allowed_hosts", authority) or "").split(",") - self._register_proxy_secrets(proxies) - self.log.info( "Creating Microsoft Graph SDK client %s for conn_id: %s", api_version, @@ -336,8 +320,8 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: 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", redact(proxies)) - self.log.info("HTTPX Proxies: %s", redact(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, @@ -453,13 +437,12 @@ def get_credentials( certificate_data = config.get("certificate_data") disable_instance_discovery = config.get("disable_instance_discovery", False) msal_proxies = self.to_msal_proxies(authority=authority, proxies=proxies) - self._register_proxy_secrets(msal_proxies) self.log.info("Tenant id: %s", tenant_id) self.log.info("Certificate path: %s", certificate_path) 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", redact(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 d612ccf7f66d7..6f6216d8fa9d1 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 @@ -412,48 +412,6 @@ async def test_paginated_run(self): assert isinstance(actual, list) assert actual == [users, next_users] - def test_register_proxy_secrets_with_credentials(self): - """Test that proxy credentials are registered as secrets.""" - proxies = { - "http": "http://user:secret_pass@proxy.example.com:3128", - "https": "https://admin:super_secret@proxy.example.com:3128", - } - - with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: - KiotaRequestAdapterHook._register_proxy_secrets(proxies) - - assert mock_mask_secret.call_count == 4 - mock_mask_secret.assert_any_call("secret_pass") - mock_mask_secret.assert_any_call("user") - mock_mask_secret.assert_any_call("super_secret") - mock_mask_secret.assert_any_call("admin") - - def test_register_proxy_secrets_without_credentials(self): - """Test that no secrets are registered when proxies have no credentials.""" - proxies = { - "http": "http://proxy.example.com:3128", - "https": "https://proxy.example.com:3128", - } - - with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: - KiotaRequestAdapterHook._register_proxy_secrets(proxies) - - mock_mask_secret.assert_not_called() - - def test_register_proxy_secrets_with_none(self): - """Test that no error occurs when proxies is None.""" - with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: - KiotaRequestAdapterHook._register_proxy_secrets(None) - - mock_mask_secret.assert_not_called() - - def test_register_proxy_secrets_with_empty_dict(self): - """Test that no error occurs when proxies is an empty dict.""" - with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: - KiotaRequestAdapterHook._register_proxy_secrets({}) - - mock_mask_secret.assert_not_called() - @pytest.mark.asyncio async def test_build_request_adapter_masks_secrets(self): """Test that sensitive data is masked when building request adapter.""" @@ -464,16 +422,15 @@ async def test_build_request_adapter_masks_secrets(self): proxies={"http": "http://user:pass@proxy:3128"}, ) ): - with patch("airflow.providers.microsoft.azure.hooks.msgraph.mask_secret") as mock_mask_secret: - with patch("airflow.providers.microsoft.azure.hooks.msgraph.redact") as mock_redact: - mock_redact.side_effect = lambda x, name=None: "***" if x else x + 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() + hook = KiotaRequestAdapterHook(conn_id="msgraph_api") + await hook.get_async_conn() - mock_mask_secret.assert_any_call("pass") - mock_mask_secret.assert_any_call("user") - assert mock_redact.call_count >= 3 + 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: 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 bf3fd91ebd7e6..6d4fb18071069 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,7 @@ def to_dict(self) -> dict[str, Any]: ... "passwd", "password", "private_key", + "proxies", "secret", "token", "keyfile_dict", From 1b67b41733240a07ab9e9f2aba7684d2c5b1ec06 Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Mon, 22 Dec 2025 02:42:26 +0545 Subject: [PATCH 3/9] Add newsfragment for proxies addition to DEFAULT_SENSITIVE_FIELDS --- airflow-core/newsfragments/59688.improvement.rst | 1 + .../src/airflow/providers/microsoft/azure/hooks/msgraph.py | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 airflow-core/newsfragments/59688.improvement.rst diff --git a/airflow-core/newsfragments/59688.improvement.rst b/airflow-core/newsfragments/59688.improvement.rst new file mode 100644 index 0000000000000..d0d64628c2370 --- /dev/null +++ b/airflow-core/newsfragments/59688.improvement.rst @@ -0,0 +1 @@ +Add ``proxies`` to ``DEFAULT_SENSITIVE_FIELDS`` in secrets_masker to treat proxy configurations as sensitive by default 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 eba9a1f9a9f06..660b3095ed805 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 @@ -63,10 +63,7 @@ try: from airflow.sdk._shared.secrets_masker import redact except ImportError: - try: - from airflow.sdk._shared.secrets_masker import redact - except ImportError: - from airflow.utils.log.secrets_masker import redact + from airflow.sdk.execution_time.secrets_masker import redact PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]] From edcd5131a3355cf7aa0c85b0cb82f961d4f28a8f Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Mon, 22 Dec 2025 12:43:44 +0545 Subject: [PATCH 4/9] Use compat module for redact import in Microsoft Graph hook Re-export redact from airflow.sdk.log and add to compat module import map to enable unconditional import via airflow.providers.common.compat.sdk --- .../src/airflow/providers/common/compat/sdk.py | 5 +++++ .../providers/microsoft/azure/hooks/msgraph.py | 9 +-------- task-sdk/src/airflow/sdk/log.py | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 9 deletions(-) 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 f08c358133a2d..e15987df6f9cc 100644 --- a/providers/common/compat/src/airflow/providers/common/compat/sdk.py +++ b/providers/common/compat/src/airflow/providers/common/compat/sdk.py @@ -90,6 +90,7 @@ TaskDeferred as TaskDeferred, XComNotFound as XComNotFound, ) + from airflow.sdk.log import redact as redact from airflow.sdk.observability.stats import Stats # noqa: F401 # Airflow 3-only exceptions (conditionally imported) @@ -239,6 +240,10 @@ # Observability # ============================================================================ "Stats": ("airflow.sdk.observability.stats", "airflow.stats"), + # ============================================================================ + # Secrets Masking + # ============================================================================ + "redact": ("airflow.sdk.log", "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 660b3095ed805..e92d6a24a2b63 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 @@ -57,14 +57,7 @@ from kiota_abstractions.response_handler import NativeResponseType from kiota_abstractions.serialization import ParsableFactory - from airflow.providers.common.compat.sdk import Connection - from airflow.sdk._shared.secrets_masker import redact -else: - try: - from airflow.sdk._shared.secrets_masker import redact - except ImportError: - from airflow.sdk.execution_time.secrets_masker import redact - +from airflow.providers.common.compat.sdk import Connection, redact PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]] diff --git a/task-sdk/src/airflow/sdk/log.py b/task-sdk/src/airflow/sdk/log.py index 0dc6298182a3c..676442cc3fec9 100644 --- a/task-sdk/src/airflow/sdk/log.py +++ b/task-sdk/src/airflow/sdk/log.py @@ -37,7 +37,20 @@ from airflow.sdk.types import Logger, RuntimeTaskInstanceProtocol as RuntimeTI -__all__ = ["configure_logging", "reset_logging", "mask_secret"] +def redact( + value: Any, name: str | None = None, max_depth: int | None = None, replacement: str = "***" +) -> Any: + """ + Redact any secrets found in ``value`` with the given replacement. + + This is a re-export from airflow.sdk._shared.secrets_masker for provider compatibility. + """ + from airflow.sdk._shared.secrets_masker import redact as _redact + + return _redact(value, name, max_depth, replacement) + + +__all__ = ["configure_logging", "reset_logging", "mask_secret", "redact"] class _WarningsInterceptor: From f24cc380a7895c12b17082d6092cc744c072fbaf Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:10:23 +0545 Subject: [PATCH 5/9] Add airflow.sdk._shared.secrets_masker and airflow.sdk.execution_time.secrets_masker to the import fallback chain for redact to support compact 3.0.6 and 3.1.5 environments --- .../compat/src/airflow/providers/common/compat/sdk.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 e15987df6f9cc..c5579be65a690 100644 --- a/providers/common/compat/src/airflow/providers/common/compat/sdk.py +++ b/providers/common/compat/src/airflow/providers/common/compat/sdk.py @@ -243,7 +243,12 @@ # ============================================================================ # Secrets Masking # ============================================================================ - "redact": ("airflow.sdk.log", "airflow.utils.log.secrets_masker"), + "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) From 227cfc41576093eca1824802adfab36e9a89ac86 Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Tue, 23 Dec 2025 13:26:45 +0545 Subject: [PATCH 6/9] Simplify redact re-export in log.py --- task-sdk/src/airflow/sdk/log.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/task-sdk/src/airflow/sdk/log.py b/task-sdk/src/airflow/sdk/log.py index 676442cc3fec9..72c7a02333b97 100644 --- a/task-sdk/src/airflow/sdk/log.py +++ b/task-sdk/src/airflow/sdk/log.py @@ -37,18 +37,7 @@ from airflow.sdk.types import Logger, RuntimeTaskInstanceProtocol as RuntimeTI -def redact( - value: Any, name: str | None = None, max_depth: int | None = None, replacement: str = "***" -) -> Any: - """ - Redact any secrets found in ``value`` with the given replacement. - - This is a re-export from airflow.sdk._shared.secrets_masker for provider compatibility. - """ - from airflow.sdk._shared.secrets_masker import redact as _redact - - return _redact(value, name, max_depth, replacement) - +from airflow.sdk._shared.secrets_masker import redact __all__ = ["configure_logging", "reset_logging", "mask_secret", "redact"] From 665e22d117d36c9bb5964c0527ae5ff030667ec9 Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Tue, 23 Dec 2025 13:38:48 +0545 Subject: [PATCH 7/9] Remove explicit __all__ declaration as it's not needed for the re-export --- task-sdk/src/airflow/sdk/log.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/task-sdk/src/airflow/sdk/log.py b/task-sdk/src/airflow/sdk/log.py index 72c7a02333b97..e6d578888cc6d 100644 --- a/task-sdk/src/airflow/sdk/log.py +++ b/task-sdk/src/airflow/sdk/log.py @@ -39,8 +39,6 @@ from airflow.sdk._shared.secrets_masker import redact -__all__ = ["configure_logging", "reset_logging", "mask_secret", "redact"] - class _WarningsInterceptor: """A class to hold the reference to the original warnings.showwarning function.""" @@ -66,8 +64,6 @@ def emit_warning(*args: Any) -> None: 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 From 4f3e1bd676d9895f698a66c2d7beca5899138352 Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:05:57 +0545 Subject: [PATCH 8/9] Fix PR comments: import paths changes --- .../src/airflow/providers/microsoft/azure/hooks/msgraph.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 e92d6a24a2b63..6b62fac0b783c 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 @@ -57,7 +57,9 @@ from kiota_abstractions.response_handler import NativeResponseType from kiota_abstractions.serialization import ParsableFactory -from airflow.providers.common.compat.sdk import Connection, redact + from airflow.providers.common.compat.sdk import Connection + +from airflow.providers.common.compat.sdk import redact PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]] From 2c6b2c12152dd3c055f2bf6e5f3c1a280d17407e Mon Sep 17 00:00:00 2001 From: Ankit Chaurasia <8670962+sunank200@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:17:01 +0545 Subject: [PATCH 9/9] Add both singular and plural forms of proxy field names to DEFAULT_SENSITIVE_FIELDS in secrets_masker to ensure proxy configurations are treated as sensitive by default regardless of field naming convention. --- airflow-core/newsfragments/59688.improvement.rst | 2 +- .../common/compat/src/airflow/providers/common/compat/sdk.py | 2 +- .../src/airflow_shared/secrets_masker/secrets_masker.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/airflow-core/newsfragments/59688.improvement.rst b/airflow-core/newsfragments/59688.improvement.rst index d0d64628c2370..d3c592cfcc3a1 100644 --- a/airflow-core/newsfragments/59688.improvement.rst +++ b/airflow-core/newsfragments/59688.improvement.rst @@ -1 +1 @@ -Add ``proxies`` to ``DEFAULT_SENSITIVE_FIELDS`` in secrets_masker to treat proxy configurations as sensitive by default +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 c5579be65a690..2d6edd2f43f2d 100644 --- a/providers/common/compat/src/airflow/providers/common/compat/sdk.py +++ b/providers/common/compat/src/airflow/providers/common/compat/sdk.py @@ -91,7 +91,7 @@ XComNotFound as XComNotFound, ) from airflow.sdk.log import redact as redact - from airflow.sdk.observability.stats import Stats # noqa: F401 + from airflow.sdk.observability.stats import Stats as Stats # Airflow 3-only exceptions (conditionally imported) if AIRFLOW_V_3_0_PLUS: 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 6d4fb18071069..e9d439e2e7462 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,7 @@ def to_dict(self) -> dict[str, Any]: ... "passwd", "password", "private_key", + "proxy", "proxies", "secret", "token",