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

hainenber pushed a commit to branch 
fix/mask-sensitive-data-in-debug-log-for-failed-import-validation
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 2f438d47a9b2d47a64f9dda1b7fa4330352acb14
Author: hainenber <[email protected]>
AuthorDate: Sun Jul 12 20:00:41 2026 +0700

    fix(sec): redact sensitive data in debug log for failed import validation
    
    Signed-off-by: hainenber <[email protected]>
---
 superset/commands/importers/v1/utils.py            | 17 +++++-
 .../unit_tests/commands/importers/v1/utils_test.py | 66 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/superset/commands/importers/v1/utils.py 
b/superset/commands/importers/v1/utils.py
index f2fe9eaba0a..e2bba1e645a 100644
--- a/superset/commands/importers/v1/utils.py
+++ b/superset/commands/importers/v1/utils.py
@@ -149,6 +149,7 @@ def load_configs(
         prefix = file_name.split("/")[0]
         schema = schemas.get(f"{prefix}/")
         if schema:
+            config = None
             try:
                 config = load_yaml(file_name, content)
 
@@ -230,7 +231,21 @@ def load_configs(
                     prefix,
                     exc.messages,
                 )
-                logger.debug("Config content that failed validation: %s", 
config)
+                # Mask sensitive data before logging out for debug
+                if config:
+                    redacted_config = json.redact_sensitive(
+                        config,
+                        {
+                            "$.password",
+                            "$.ssh_tunnel.password",
+                            "$.ssh_tunnel.private_key",
+                            "$.ssh_tunnel.private_key_password",
+                            "$.masked_encrypted_extra",
+                        },
+                    )
+                    logger.debug(
+                        "Config content that failed validation: %s.", 
redacted_config
+                    )
                 exc.messages = {file_name: exc.messages}
                 exceptions.append(exc)
 
diff --git a/tests/unit_tests/commands/importers/v1/utils_test.py 
b/tests/unit_tests/commands/importers/v1/utils_test.py
index eef9d4ea2e9..f6df6a92fab 100644
--- a/tests/unit_tests/commands/importers/v1/utils_test.py
+++ b/tests/unit_tests/commands/importers/v1/utils_test.py
@@ -16,10 +16,17 @@
 # under the License.
 """Tests for superset/commands/dataset/importers/v1/utils.py temporal 
helpers."""
 
+import logging
 from unittest.mock import patch
 
 import pandas as pd
 import pytest
+from jsonpath_ng import parse
+from marshmallow import Schema
+from marshmallow.exceptions import ValidationError
+
+from superset.constants import PASSWORD_MASK
+from superset.utils import json
 
 
 class TestConvertTemporalColumns:
@@ -117,3 +124,62 @@ class TestConvertTemporalColumns:
 
         call_args = mock_logger.warning.call_args[0]
         assert call_args[1] == 2  # 2 out-of-bounds, 1 pre-existing null
+
+
+class TestLoadConfigs:
+    def test_load_configs_caught_exception_and_log_redacted_configs(self) -> 
None:
+        logging.basicConfig(level=logging.DEBUG)
+        from superset.commands.importers.v1.utils import (
+            load_configs,
+        )
+
+        with (
+            patch("superset.db") as mock_db,
+            patch("yaml.safe_load") as mock_yaml_safe_load,
+            patch("superset.commands.importers.v1.utils.logger") as 
mock_logger,
+        ):
+            mock_yaml_safe_load.return_value = {
+                "masked_encrypted_extra": json.dumps(
+                    {"oauth2_client_info": {"secret": "MASKED"}}
+                ),
+                "ssh_tunnel": {},
+            }
+            mock_db.session.query.return_value = {"uuid-1": 
"SECRET_TO_BE_SEEN"}
+
+            class RaiseValidationSchema(Schema):
+                def load(self, value):
+                    raise ValidationError("Exception when validating config")
+
+            failed_schema = RaiseValidationSchema()
+
+            load_configs(
+                contents={"file1/": "here is some content"},
+                schemas={
+                    "file1/": failed_schema,
+                },
+                passwords={"file1/": "PASSWORD"},
+                exceptions=[],
+                ssh_tunnel_passwords={"file1/": "SECRET_SSH_TUNNEL_PASSWORD"},
+                ssh_tunnel_private_keys={"file1/": 
"SECRET_SSH_TUNNEL_PRIVATE_KEY"},
+                ssh_tunnel_priv_key_passwords={
+                    "file1/": "SECRET_SSH_TUNNEL_PRIV_KEY_PASSWORD"
+                },
+                encrypted_extra_secrets={
+                    "file1/": {
+                        "$.oauth2_client_info.secret": 
"SECRET_OAUTH2_CLIENT_INFO"
+                    }
+                },
+            )
+
+            logged_config = mock_logger.debug.call_args[0][1]
+
+            for redacted_field in [
+                "$.password",
+                "$.ssh_tunnel.password",
+                "$.ssh_tunnel.private_key",
+                "$.ssh_tunnel.private_key_password",
+                "$.masked_encrypted_extra",
+            ]:
+                jsonpath_expr = parse(redacted_field)
+                for match in jsonpath_expr.find(logged_config):
+                    assert match.value == PASSWORD_MASK

Reply via email to