From ef49b606f8fd9926d3f472f8c1b94cc08a226d3d Mon Sep 17 00:00:00 2001 From: NguyenCong2k <123174148+NguyenCong2k@users.noreply.github.com> Date: Thu, 14 May 2026 11:36:25 +0700 Subject: [PATCH 1/2] Redact AWS session token from client repr --- pymongo/asynchronous/mongo_client.py | 2 ++ pymongo/common.py | 25 ++++++++++++++++++++ pymongo/synchronous/mongo_client.py | 2 ++ test/asynchronous/test_client.py | 35 ++++++++++++++++++++++++++++ test/test_client.py | 35 ++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+) diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 2a53462f59..cf073a7519 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -1305,6 +1305,8 @@ def option_repr(option: str, value: Any) -> str: return "document_class=dict" else: return f"document_class={value.__module__}.{value.__name__}" + if option == "authmechanismproperties": + value = common.redact_auth_mechanism_properties_for_repr(value) if option in common.TIMEOUT_OPTIONS and value is not None: return f"{option}={int(value * 1000)}" diff --git a/pymongo/common.py b/pymongo/common.py index 7baed05d3e..57606a6317 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -423,6 +423,31 @@ def validate_read_preference_tags(name: str, value: Any) -> list[dict[str, str]] ) +_SAFE_AUTH_MECHANISM_PROPS_FOR_REPR = frozenset( + [ + "ALLOWED_HOSTS", + "CANONICALIZE_HOST_NAME", + "ENVIRONMENT", + "SERVICE_HOST", + "SERVICE_NAME", + "SERVICE_REALM", + "TOKEN_RESOURCE", + ] +) + + +def redact_auth_mechanism_properties_for_repr(value: Any) -> Any: + """Redact sensitive auth mechanism properties before including them in repr.""" + if not isinstance(value, dict): + return value + + redacted = value.copy() + for key in redacted: + if str(key).upper() not in _SAFE_AUTH_MECHANISM_PROPS_FOR_REPR: + redacted[key] = "" + return redacted + + def validate_auth_mechanism_properties(option: str, value: Any) -> dict[str, Union[bool, str]]: """Validate authMechanismProperties.""" props: dict[str, Any] = {} diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 24a0401410..bc18836d3e 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -1306,6 +1306,8 @@ def option_repr(option: str, value: Any) -> str: return "document_class=dict" else: return f"document_class={value.__module__}.{value.__name__}" + if option == "authmechanismproperties": + value = common.redact_auth_mechanism_properties_for_repr(value) if option in common.TIMEOUT_OPTIONS and value is not None: return f"{option}={int(value * 1000)}" diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index a7f66d9620..f26f3fb85b 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -196,6 +196,41 @@ def test_types(self): self.assertRaises(ConfigurationError, AsyncMongoClient, []) + async def test_repr_redacts_aws_session_token(self): + token = "SECRET_AWS_SESSION_TOKEN" + client = AsyncMongoClient( + "mongodb://AKIA:SECRET@localhost:27017/" + f"?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:{token}", + connect=False, + ) + + the_repr = repr(client) + + self.assertNotIn(token, the_repr) + self.assertIn("'AWS_SESSION_TOKEN': ''", the_repr) + + async def test_repr_redacts_secret_auth_mechanism_properties(self): + token = "SECRET_AWS_SESSION_TOKEN" + api_key = "SECRET_API_KEY" + client = AsyncMongoClient( + "mongodb://AKIA:SECRET@localhost:27017/", + authMechanism="MONGODB-AWS", + authMechanismProperties={ + "aws_session_token": token, + "CUSTOM_API_KEY": api_key, + "TOKEN_RESOURCE": "mongodb://cluster.example", + }, + connect=False, + ) + + the_repr = repr(client) + + self.assertNotIn(token, the_repr) + self.assertNotIn(api_key, the_repr) + self.assertIn("'aws_session_token': ''", the_repr) + self.assertIn("'CUSTOM_API_KEY': ''", the_repr) + self.assertIn("'TOKEN_RESOURCE': 'mongodb://cluster.example'", the_repr) + async def test_max_pool_size_zero(self): self.simple_client(maxPoolSize=0) diff --git a/test/test_client.py b/test/test_client.py index 8f0da71321..c801d3a178 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -193,6 +193,41 @@ def test_types(self): self.assertRaises(ConfigurationError, MongoClient, []) + def test_repr_redacts_aws_session_token(self): + token = "SECRET_AWS_SESSION_TOKEN" + client = MongoClient( + "mongodb://AKIA:SECRET@localhost:27017/" + f"?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:{token}", + connect=False, + ) + + the_repr = repr(client) + + self.assertNotIn(token, the_repr) + self.assertIn("'AWS_SESSION_TOKEN': ''", the_repr) + + def test_repr_redacts_secret_auth_mechanism_properties(self): + token = "SECRET_AWS_SESSION_TOKEN" + api_key = "SECRET_API_KEY" + client = MongoClient( + "mongodb://AKIA:SECRET@localhost:27017/", + authMechanism="MONGODB-AWS", + authMechanismProperties={ + "aws_session_token": token, + "CUSTOM_API_KEY": api_key, + "TOKEN_RESOURCE": "mongodb://cluster.example", + }, + connect=False, + ) + + the_repr = repr(client) + + self.assertNotIn(token, the_repr) + self.assertNotIn(api_key, the_repr) + self.assertIn("'aws_session_token': ''", the_repr) + self.assertIn("'CUSTOM_API_KEY': ''", the_repr) + self.assertIn("'TOKEN_RESOURCE': 'mongodb://cluster.example'", the_repr) + def test_max_pool_size_zero(self): self.simple_client(maxPoolSize=0) From ec6087306cc55c58466b3ecb05bf78500545b3e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 02:29:28 +0000 Subject: [PATCH 2/2] Add changelog entry for auth property redaction --- doc/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index b3e1c75319..39ff537551 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -8,6 +8,10 @@ Changes in Version 4.18.0 to the same server, avoiding a full handshake on each new connection. Session resumption is supported on all Python versions for synchronous clients and on Python 3.11+ for async clients. +- Redacted potentially sensitive authentication mechanism properties, including + AWS session tokens, from the representations of + :class:`~pymongo.synchronous.mongo_client.MongoClient` and + :class:`~pymongo.asynchronous.mongo_client.AsyncMongoClient`. Changes in Version 4.17.0 (2026/04/20) --------------------------------------