Repository: TileDB-Inc/TileDB-Py
Pull request #2029 (2024-08-01 15:26:14)

Hide sensitive params shown in `Config::__repr__`
`__repr__` for `tiledb.Config` redacts privileged info. The values should still be accessed by directly slicing a `Config` object, but hiding them from `__repr__` reduces the risk of accidental disclosure when copy-pasting.

`unserialized_params_` used in this PR comes from https://github.com/TileDB-Inc/TileDB/blob/dev/tiledb/sm/config/config.cc#L522-L541

---
[sc-50665]


Patch:
diff --git a/tiledb/ctx.py b/tiledb/ctx.py
index 05d2b85b..b4898f37 100644
--- a/tiledb/ctx.py
+++ b/tiledb/ctx.py
@@ -136,10 +136,36 @@ class Config(lt.Config):
                 return False
         return True
 
+    unserialized_params_ = {
+        "vfs.azure.storage_account_name",
+        "vfs.azure.storage_account_key",
+        "vfs.azure.storage_sas_token",
+        "vfs.s3.proxy_username",
+        "vfs.s3.proxy_password",
+        "vfs.s3.aws_access_key_id",
+        "vfs.s3.aws_secret_access_key",
+        "vfs.s3.aws_session_token",
+        "vfs.s3.aws_role_arn",
+        "vfs.s3.aws_external_id",
+        "vfs.s3.aws_load_frequency",
+        "vfs.s3.aws_session_name",
+        "vfs.gcs.service_account_key",
+        "vfs.gcs.workload_identity_configuration",
+        "vfs.gcs.impersonate_service_account",
+        "rest.username",
+        "rest.password",
+        "rest.token",
+    }
+
     def __repr__(self):
         colnames = ["Parameter", "Value"]
         params = list(self.keys())
         values = list(map(repr, self.values()))
+        # for unserialized params, we don't want to print their values
+        values = [
+            "*" * 10 if p in self.unserialized_params_ and v != "''" else v
+            for i, (p, v) in enumerate(zip(params, values))
+        ]
         colsizes = [
             max(len(colnames[0]), *map(len, (p for p in params))),
             max(len(colnames[1]), *map(len, (v for v in values))),
@@ -163,6 +189,11 @@ class Config(lt.Config):
 
         params = list(self.keys())
         values = list(map(repr, self.values()))
+        # for unserialized params, we don't want to print their values
+        values = [
+            "*" * 10 if p in self.unserialized_params_ and v != "''" else v
+            for i, (p, v) in enumerate(zip(params, values))
+        ]
 
         for p, v in zip(params, values):
             output.write("<tr>")


Test patch:
diff --git a/tiledb/tests/test_context_and_config.py b/tiledb/tests/test_context_and_config.py
index db4d6b86..05433899 100644
--- a/tiledb/tests/test_context_and_config.py
+++ b/tiledb/tests/test_context_and_config.py
@@ -187,6 +187,72 @@ class TestConfig(DiskTestCase):
         assert issubclass(type(config), tiledb.libtiledb.Config)
         self.assertEqual(config["sm.memory_budget"], "100")
 
+    def test_config_repr_sensitive_params_hidden(self):
+        # checks that the sensitive parameters set are not printed,
+        # sensitive parameters not set are printed as '',
+        # non-sensitive parameters set are printed as is, and
+        # non-sensitive parameters not set are not hidden (some are empty, some are default)
+
+        unserialized_params_ = {
+            "vfs.azure.storage_account_name",
+            "vfs.azure.storage_account_key",
+            "vfs.azure.storage_sas_token",
+            "vfs.s3.proxy_username",
+            "vfs.s3.proxy_password",
+            "vfs.s3.aws_access_key_id",
+            "vfs.s3.aws_secret_access_key",
+            "vfs.s3.aws_session_token",
+            "vfs.s3.aws_role_arn",
+            "vfs.s3.aws_external_id",
+            "vfs.s3.aws_load_frequency",
+            "vfs.s3.aws_session_name",
+            "vfs.gcs.service_account_key",
+            "vfs.gcs.workload_identity_configuration",
+            "vfs.gcs.impersonate_service_account",
+            "rest.username",
+            "rest.password",
+            "rest.token",
+        }
+
+        random_sensitive_params = {
+            "vfs.azure.storage_account_name": "myaccount",
+            "vfs.s3.aws_access_key_id": "myaccesskey",
+            "vfs.gcs.service_account_key": "myserviceaccountkey",
+            "rest.username": "myusername",
+        }
+
+        random_non_sensitive_params = {
+            "rest.use_refactored_array_open": "false",
+            "rest.use_refactored_array_open_and_query_submit": "true",
+            "sm.allow_separate_attribute_writes": "true",
+            "sm.allow_updates_experimental": "false",
+        }
+
+        config = tiledb.Config()
+
+        for param, value in random_sensitive_params.items():
+            config[param] = value
+
+        for param, value in random_non_sensitive_params.items():
+            config[param] = value
+
+        # skip first two lines
+        for line in repr(config).split("\n")[2:]:
+            param, value = line.split("|")
+            # remove leading and trailing spaces
+            param = param.strip()
+            value = value.strip()
+            if param in unserialized_params_:
+                if param in random_sensitive_params:
+                    self.assertEqual(value, "*" * 10)
+                else:
+                    self.assertEqual(value, "''")
+            else:
+                if param in random_non_sensitive_params:
+                    self.assertEqual(value, f"'{random_non_sensitive_params[param]}'")
+                else:
+                    self.assertNotEqual(value, "*" * 10)
+
     def test_config_repr_html(self):
         config = tiledb.Config()
         try: