From 65fd98c97d4d4e8e536589339f0e8000f8146b63 Mon Sep 17 00:00:00 2001 From: seladb Date: Sun, 8 Mar 2026 00:35:24 -0800 Subject: [PATCH 1/2] Add test_logging_security.py (from PR #1997) --- tests/test_logging_security.py | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/test_logging_security.py diff --git a/tests/test_logging_security.py b/tests/test_logging_security.py new file mode 100644 index 000000000..8bd196b83 --- /dev/null +++ b/tests/test_logging_security.py @@ -0,0 +1,100 @@ +"""Test that sensitive data is not exposed in debug logging.""" + +import logging +from io import StringIO + +from tests.testmodels import User +from tortoise.contrib.test import TestCase + + +class TestLoggingSecurity(TestCase): + """Test cases for ensuring sensitive data is not logged by Tortoise ORM.""" + + async def test_query_parameters_not_logged_in_tortoise_db_client(self): + """Test that query parameters are not logged by tortoise.db_client logger.""" + # Create a string IO to capture log output + log_capture = StringIO() + + # Get the tortoise db_client logger and add our handler + logger = logging.getLogger("tortoise.db_client") + original_level = logger.level + handler = logging.StreamHandler(log_capture) + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(name)s:%(levelname)s:%(message)s") + handler.setFormatter(formatter) + + # Set up logging + logger.setLevel(logging.DEBUG) + logger.addHandler(handler) + + try: + # Create a user with potentially sensitive data + sensitive_email = "admin@secret-company.com" + sensitive_username = "admin_with_secret_key_123" + sensitive_bio = "bio with password: my_secret_password_123" + + user = await User.create( + username=sensitive_username, mail=sensitive_email, bio=sensitive_bio + ) + + # Get the captured log output + log_output = log_capture.getvalue() + + # Verify that the SQL query structure is still logged + self.assertIn("INSERT INTO", log_output) + self.assertIn("user", log_output.lower()) + + # Verify that sensitive data is NOT in the log output + self.assertNotIn( + sensitive_email, log_output, f"Sensitive email found in log output: {log_output}" + ) + self.assertNotIn( + sensitive_username, + log_output, + f"Sensitive username found in log output: {log_output}", + ) + self.assertNotIn( + sensitive_bio, log_output, f"Sensitive bio found in log output: {log_output}" + ) + self.assertNotIn( + "my_secret_password_123", + log_output, + f"Sensitive password found in log output: {log_output}", + ) + + # Test UPDATE operation + log_capture.seek(0) # Reset the capture + log_capture.truncate(0) + + new_sensitive_email = "super_secret_admin@classified.gov" + user.mail = new_sensitive_email + await user.save() + + log_output = log_capture.getvalue() + self.assertNotIn( + new_sensitive_email, + log_output, + f"Sensitive email found in UPDATE log: {log_output}", + ) + + # Test SELECT operation + log_capture.seek(0) # Reset the capture + log_capture.truncate(0) + + await User.filter(username=sensitive_username).first() + + log_output = log_capture.getvalue() + self.assertNotIn( + sensitive_username, + log_output, + f"Sensitive username found in SELECT log: {log_output}", + ) + + # Clean up + await user.delete() + + finally: + # Restore original logging setup + logger.removeHandler(handler) + logger.setLevel(original_level) + handler.close() From eab59191ef7b26b15ac6f7c19f3e67c895841991 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:34:43 +0000 Subject: [PATCH 2/2] Fix issue #1996: Remove sensitive query parameters from debug logging - Updated all backend client files to log only SQL queries without parameter values - Changed logging from `self.log.debug("%s: %s", query, values)` to `self.log.debug("%s", query)` - Affects 7 backend clients: MySQL, SQLite, PostgreSQL (psycopg), ODBC, Oracle, MSSQL, AsyncPG - Prevents exposure of sensitive data like passwords, tokens, etc. in debug logs - Maintains debugging capability by still logging the SQL query structure Co-authored-by: bddap <2702854+bddap@users.noreply.github.com> --- tortoise/backends/asyncpg/client.py | 10 +++++----- tortoise/backends/mssql/client.py | 2 +- tortoise/backends/mysql/client.py | 8 ++++---- tortoise/backends/odbc/client.py | 6 +++--- tortoise/backends/oracle/client.py | 2 +- tortoise/backends/psycopg/client.py | 4 ++-- tortoise/backends/sqlite/client.py | 10 +++++----- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tortoise/backends/asyncpg/client.py b/tortoise/backends/asyncpg/client.py index 08e24826c..9b000bb64 100644 --- a/tortoise/backends/asyncpg/client.py +++ b/tortoise/backends/asyncpg/client.py @@ -108,14 +108,14 @@ def _in_transaction(self) -> TransactionContext: @translate_exceptions async def execute_insert(self, query: str, values: list) -> asyncpg.Record | None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) # TODO: Cache prepared statement return await connection.fetchrow(query, *values) @translate_exceptions async def execute_many(self, query: str, values: list) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) # TODO: Consider using copy_records_to_table instead transaction = connection.transaction() await transaction.start() @@ -130,7 +130,7 @@ async def execute_many(self, query: str, values: list) -> None: @translate_exceptions async def execute_query(self, query: str, values: list | None = None) -> tuple[int, list[dict]]: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) if values: params = [query, *values] else: @@ -154,7 +154,7 @@ async def execute_query(self, query: str, values: list | None = None) -> tuple[i @translate_exceptions async def execute_query_dict(self, query: str, values: list | None = None) -> list[dict]: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) if values: return list(map(dict, await connection.fetch(query, *values))) return list(map(dict, await connection.fetch(query))) @@ -186,7 +186,7 @@ def acquire_connection(self) -> ConnectionWrapper[asyncpg.Connection]: @translate_exceptions async def execute_many(self, query: str, values: list) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) # TODO: Consider using copy_records_to_table instead await connection.executemany(query, values) diff --git a/tortoise/backends/mssql/client.py b/tortoise/backends/mssql/client.py index 9151257a2..7cf878da5 100644 --- a/tortoise/backends/mssql/client.py +++ b/tortoise/backends/mssql/client.py @@ -63,7 +63,7 @@ def _in_transaction(self) -> TransactionContext: @translate_exceptions async def execute_insert(self, query: str, values: list) -> int: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: await cursor.execute(f"SET NOCOUNT ON; {query}; SELECT @@IDENTITY", values) return (await cursor.fetchone())[0] diff --git a/tortoise/backends/mysql/client.py b/tortoise/backends/mysql/client.py index 741f71e6c..a74ab00fb 100644 --- a/tortoise/backends/mysql/client.py +++ b/tortoise/backends/mysql/client.py @@ -187,7 +187,7 @@ def _in_transaction(self) -> TransactionContext: @translate_exceptions async def execute_insert(self, query: str, values: list) -> int: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: await cursor.execute(query, values) return cursor.lastrowid # return auto-generated id @@ -195,7 +195,7 @@ async def execute_insert(self, query: str, values: list) -> int: @translate_exceptions async def execute_many(self, query: str, values: list) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: if self.capabilities.supports_transactions: await connection.begin() @@ -212,7 +212,7 @@ async def execute_many(self, query: str, values: list) -> None: @translate_exceptions async def execute_query(self, query: str, values: list | None = None) -> tuple[int, list[dict]]: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: await cursor.execute(query, values) rows = await cursor.fetchall() @@ -252,7 +252,7 @@ def acquire_connection(self) -> ConnectionWrapper[mysql.Connection]: @translate_exceptions async def execute_many(self, query: str, values: list) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: await cursor.executemany(query, values) diff --git a/tortoise/backends/odbc/client.py b/tortoise/backends/odbc/client.py index 221ecd09e..c2d225894 100644 --- a/tortoise/backends/odbc/client.py +++ b/tortoise/backends/odbc/client.py @@ -124,7 +124,7 @@ def acquire_connection(self) -> ConnWrapperType: @translate_exceptions async def execute_many(self, query: str, values: list) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: try: await cursor.executemany(query, values) @@ -137,7 +137,7 @@ async def execute_many(self, query: str, values: list) -> None: @translate_exceptions async def execute_query(self, query: str, values: list | None = None) -> tuple[int, list[dict]]: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) async with connection.cursor() as cursor: if values: await cursor.execute(query, values) @@ -185,7 +185,7 @@ def acquire_connection(self) -> ConnWrapperType: @translate_exceptions async def execute_many(self, query: str, values: list) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) cursor = await connection.cursor() await cursor.executemany(query, values) diff --git a/tortoise/backends/oracle/client.py b/tortoise/backends/oracle/client.py index 4846751b8..80d2350db 100644 --- a/tortoise/backends/oracle/client.py +++ b/tortoise/backends/oracle/client.py @@ -92,7 +92,7 @@ async def execute_script(self, query: str) -> None: @translate_exceptions async def execute_insert(self, query: str, values: list) -> int: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) await connection.execute(query, values) return 0 diff --git a/tortoise/backends/psycopg/client.py b/tortoise/backends/psycopg/client.py index a9cf41601..269687ec9 100644 --- a/tortoise/backends/psycopg/client.py +++ b/tortoise/backends/psycopg/client.py @@ -134,7 +134,7 @@ async def execute_many(self, query: str, values: list) -> None: connection: psycopg.AsyncConnection async with self.acquire_connection() as connection: async with connection.cursor() as cursor: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) await cursor.executemany(query, values) @postgres_client.translate_exceptions @@ -148,7 +148,7 @@ async def execute_query( async with self.acquire_connection() as connection: cursor: psycopg.AsyncCursor | psycopg.AsyncServerCursor async with connection.cursor(row_factory=row_factory) as cursor: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) await cursor.execute(query, values) rowcount = int(cursor.rowcount or cursor.rownumber or 0) diff --git a/tortoise/backends/sqlite/client.py b/tortoise/backends/sqlite/client.py index b0f1792a8..d691ef235 100644 --- a/tortoise/backends/sqlite/client.py +++ b/tortoise/backends/sqlite/client.py @@ -127,13 +127,13 @@ def _in_transaction(self) -> TransactionContext: @translate_exceptions async def execute_insert(self, query: str, values: list) -> int: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) return (await connection.execute_insert(query, values))[0] @translate_exceptions async def execute_many(self, query: str, values: list[list]) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) # This code is only ever called in AUTOCOMMIT mode await connection.execute("BEGIN") try: @@ -150,7 +150,7 @@ async def execute_query( ) -> tuple[int, Sequence[dict]]: query = query.replace("\x00", "'||CHAR(0)||'") async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) start = connection.total_changes rows = await connection.execute_fetchall(query, values) return (connection.total_changes - start) or len(rows), rows @@ -159,7 +159,7 @@ async def execute_query( async def execute_query_dict(self, query: str, values: list | None = None) -> list[dict]: query = query.replace("\x00", "'||CHAR(0)||'") async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) return list(map(dict, await connection.execute_fetchall(query, values))) @translate_exceptions @@ -229,7 +229,7 @@ def _in_transaction(self) -> TransactionContext: @translate_exceptions async def execute_many(self, query: str, values: list[list]) -> None: async with self.acquire_connection() as connection: - self.log.debug("%s: %s", query, values) + self.log.debug("%s", query) # Already within transaction, so ideal for performance await connection.executemany(query, values)