Skip to content

Commit 5ef3125

Browse files
Fix: Mask credentials in duckdb attach for postgres, mysql logging (#5528)
1 parent 31daa66 commit 5ef3125

File tree

2 files changed

+71
-22
lines changed

2 files changed

+71
-22
lines changed

sqlmesh/core/config/connection.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"clickhouse",
5959
}
6060
MOTHERDUCK_TOKEN_REGEX = re.compile(r"(\?|\&)(motherduck_token=)(\S*)")
61+
PASSWORD_REGEX = re.compile(r"(password=)(\S+)")
6162

6263

6364
def _get_engine_import_validator(
@@ -479,13 +480,13 @@ def create_engine_adapter(
479480
adapter = BaseDuckDBConnectionConfig._data_file_to_adapter.get(key)
480481
if adapter is not None:
481482
logger.info(
482-
f"Using existing DuckDB adapter due to overlapping data file: {self._mask_motherduck_token(key)}"
483+
f"Using existing DuckDB adapter due to overlapping data file: {self._mask_sensitive_data(key)}"
483484
)
484485
return adapter
485486

486487
if data_files:
487488
masked_files = {
488-
self._mask_motherduck_token(file if isinstance(file, str) else file.path)
489+
self._mask_sensitive_data(file if isinstance(file, str) else file.path)
489490
for file in data_files
490491
}
491492
logger.info(f"Creating new DuckDB adapter for data files: {masked_files}")
@@ -507,10 +508,14 @@ def get_catalog(self) -> t.Optional[str]:
507508
return list(self.catalogs)[0]
508509
return None
509510

510-
def _mask_motherduck_token(self, string: str) -> str:
511-
return MOTHERDUCK_TOKEN_REGEX.sub(
512-
lambda m: f"{m.group(1)}{m.group(2)}{'*' * len(m.group(3))}", string
511+
def _mask_sensitive_data(self, string: str) -> str:
512+
# Mask MotherDuck tokens with fixed number of asterisks
513+
result = MOTHERDUCK_TOKEN_REGEX.sub(
514+
lambda m: f"{m.group(1)}{m.group(2)}{'*' * 8 if m.group(3) else ''}", string
513515
)
516+
# Mask PostgreSQL/MySQL passwords with fixed number of asterisks
517+
result = PASSWORD_REGEX.sub(lambda m: f"{m.group(1)}{'*' * 8}", result)
518+
return result
514519

515520

516521
class MotherDuckConnectionConfig(BaseDuckDBConnectionConfig):

tests/core/test_connection_config.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -944,42 +944,86 @@ def test_motherduck_token_mask(make_config):
944944
assert isinstance(config_1, MotherDuckConnectionConfig)
945945
assert isinstance(config_2, MotherDuckConnectionConfig)
946946
assert isinstance(config_3, MotherDuckConnectionConfig)
947-
assert config_1._mask_motherduck_token(config_1.database) == "whodunnit"
947+
948+
# motherduck format
949+
assert config_1._mask_sensitive_data(config_1.database) == "whodunnit"
948950
assert (
949-
config_1._mask_motherduck_token(f"md:{config_1.database}?motherduck_token={config_1.token}")
950-
== "md:whodunnit?motherduck_token=*****"
951+
config_1._mask_sensitive_data(f"md:{config_1.database}?motherduck_token={config_1.token}")
952+
== "md:whodunnit?motherduck_token=********"
951953
)
952954
assert (
953-
config_1._mask_motherduck_token(
955+
config_1._mask_sensitive_data(
954956
f"md:{config_1.database}?attach_mode=single&motherduck_token={config_1.token}"
955957
)
956-
== "md:whodunnit?attach_mode=single&motherduck_token=*****"
958+
== "md:whodunnit?attach_mode=single&motherduck_token=********"
957959
)
958960
assert (
959-
config_2._mask_motherduck_token(f"md:{config_2.database}?motherduck_token={config_2.token}")
960-
== "md:whodunnit?motherduck_token=******************"
961+
config_2._mask_sensitive_data(f"md:{config_2.database}?motherduck_token={config_2.token}")
962+
== "md:whodunnit?motherduck_token=********"
961963
)
962964
assert (
963-
config_3._mask_motherduck_token(f"md:?motherduck_token={config_3.token}")
964-
== "md:?motherduck_token=**********"
965+
config_3._mask_sensitive_data(f"md:?motherduck_token={config_3.token}")
966+
== "md:?motherduck_token=********"
965967
)
966968
assert (
967-
config_1._mask_motherduck_token("?motherduck_token=secret1235")
968-
== "?motherduck_token=**********"
969+
config_1._mask_sensitive_data("?motherduck_token=secret1235")
970+
== "?motherduck_token=********"
969971
)
970972
assert (
971-
config_1._mask_motherduck_token("md:whodunnit?motherduck_token=short")
972-
== "md:whodunnit?motherduck_token=*****"
973+
config_1._mask_sensitive_data("md:whodunnit?motherduck_token=short")
974+
== "md:whodunnit?motherduck_token=********"
973975
)
974976
assert (
975-
config_1._mask_motherduck_token("md:whodunnit?motherduck_token=longtoken123456789")
976-
== "md:whodunnit?motherduck_token=******************"
977+
config_1._mask_sensitive_data("md:whodunnit?motherduck_token=longtoken123456789")
978+
== "md:whodunnit?motherduck_token=********"
977979
)
978980
assert (
979-
config_1._mask_motherduck_token("md:whodunnit?motherduck_token=")
981+
config_1._mask_sensitive_data("md:whodunnit?motherduck_token=")
980982
== "md:whodunnit?motherduck_token="
981983
)
982-
assert config_1._mask_motherduck_token(":memory:") == ":memory:"
984+
assert config_1._mask_sensitive_data(":memory:") == ":memory:"
985+
986+
# postgres format
987+
assert (
988+
config_1._mask_sensitive_data(
989+
"postgres:dbname=mydb user=myuser password=secret123 host=localhost"
990+
)
991+
== "postgres:dbname=mydb user=myuser password=******** host=localhost"
992+
)
993+
994+
assert (
995+
config_1._mask_sensitive_data(
996+
"dbname=postgres user=postgres password=pg_secret host=127.0.0.1"
997+
)
998+
== "dbname=postgres user=postgres password=******** host=127.0.0.1"
999+
)
1000+
assert (
1001+
config_1._mask_sensitive_data(
1002+
"postgres:dbname=testdb password=verylongpassword123 user=admin"
1003+
)
1004+
== "postgres:dbname=testdb password=******** user=admin"
1005+
)
1006+
assert config_1._mask_sensitive_data("postgres:password=short") == "postgres:password=********"
1007+
assert (
1008+
config_1._mask_sensitive_data("postgres:host=localhost password=p@ssw0rd! dbname=db")
1009+
== "postgres:host=localhost password=******** dbname=db"
1010+
)
1011+
1012+
assert (
1013+
config_1._mask_sensitive_data("postgres:dbname=mydb user=myuser host=localhost")
1014+
== "postgres:dbname=mydb user=myuser host=localhost"
1015+
)
1016+
1017+
assert (
1018+
config_1._mask_sensitive_data("md:db?motherduck_token=token123 postgres:password=secret")
1019+
== "md:db?motherduck_token=******** postgres:password=********"
1020+
)
1021+
1022+
# MySQL format
1023+
assert (
1024+
config_1._mask_sensitive_data("host=localhost user=root password=mysql123 database=mydb")
1025+
== "host=localhost user=root password=******** database=mydb"
1026+
)
9831027

9841028

9851029
def test_bigquery(make_config):

0 commit comments

Comments
 (0)