|
| 1 | +"""Test that sensitive data is not exposed in debug logging.""" |
| 2 | + |
| 3 | +import logging |
| 4 | +from io import StringIO |
| 5 | + |
| 6 | +from tests.testmodels import User |
| 7 | +from tortoise.contrib.test import TestCase |
| 8 | + |
| 9 | + |
| 10 | +class TestLoggingSecurity(TestCase): |
| 11 | + """Test cases for ensuring sensitive data is not logged by Tortoise ORM.""" |
| 12 | + |
| 13 | + async def test_query_parameters_not_logged_in_tortoise_db_client(self): |
| 14 | + """Test that query parameters are not logged by tortoise.db_client logger.""" |
| 15 | + # Create a string IO to capture log output |
| 16 | + log_capture = StringIO() |
| 17 | + |
| 18 | + # Get the tortoise db_client logger and add our handler |
| 19 | + logger = logging.getLogger("tortoise.db_client") |
| 20 | + original_level = logger.level |
| 21 | + handler = logging.StreamHandler(log_capture) |
| 22 | + handler.setLevel(logging.DEBUG) |
| 23 | + formatter = logging.Formatter("%(name)s:%(levelname)s:%(message)s") |
| 24 | + handler.setFormatter(formatter) |
| 25 | + |
| 26 | + # Set up logging |
| 27 | + logger.setLevel(logging.DEBUG) |
| 28 | + logger.addHandler(handler) |
| 29 | + |
| 30 | + try: |
| 31 | + # Create a user with potentially sensitive data |
| 32 | + sensitive_email = "admin@secret-company.com" |
| 33 | + sensitive_username = "admin_with_secret_key_123" |
| 34 | + sensitive_bio = "bio with password: my_secret_password_123" |
| 35 | + |
| 36 | + user = await User.create( |
| 37 | + username=sensitive_username, mail=sensitive_email, bio=sensitive_bio |
| 38 | + ) |
| 39 | + |
| 40 | + # Get the captured log output |
| 41 | + log_output = log_capture.getvalue() |
| 42 | + |
| 43 | + # Verify that the SQL query structure is still logged |
| 44 | + self.assertIn("INSERT INTO", log_output) |
| 45 | + self.assertIn("user", log_output.lower()) |
| 46 | + |
| 47 | + # Verify that sensitive data is NOT in the log output |
| 48 | + self.assertNotIn( |
| 49 | + sensitive_email, log_output, f"Sensitive email found in log output: {log_output}" |
| 50 | + ) |
| 51 | + self.assertNotIn( |
| 52 | + sensitive_username, |
| 53 | + log_output, |
| 54 | + f"Sensitive username found in log output: {log_output}", |
| 55 | + ) |
| 56 | + self.assertNotIn( |
| 57 | + sensitive_bio, log_output, f"Sensitive bio found in log output: {log_output}" |
| 58 | + ) |
| 59 | + self.assertNotIn( |
| 60 | + "my_secret_password_123", |
| 61 | + log_output, |
| 62 | + f"Sensitive password found in log output: {log_output}", |
| 63 | + ) |
| 64 | + |
| 65 | + # Test UPDATE operation |
| 66 | + log_capture.seek(0) # Reset the capture |
| 67 | + log_capture.truncate(0) |
| 68 | + |
| 69 | + new_sensitive_email = "super_secret_admin@classified.gov" |
| 70 | + user.mail = new_sensitive_email |
| 71 | + await user.save() |
| 72 | + |
| 73 | + log_output = log_capture.getvalue() |
| 74 | + self.assertNotIn( |
| 75 | + new_sensitive_email, |
| 76 | + log_output, |
| 77 | + f"Sensitive email found in UPDATE log: {log_output}", |
| 78 | + ) |
| 79 | + |
| 80 | + # Test SELECT operation |
| 81 | + log_capture.seek(0) # Reset the capture |
| 82 | + log_capture.truncate(0) |
| 83 | + |
| 84 | + await User.filter(username=sensitive_username).first() |
| 85 | + |
| 86 | + log_output = log_capture.getvalue() |
| 87 | + self.assertNotIn( |
| 88 | + sensitive_username, |
| 89 | + log_output, |
| 90 | + f"Sensitive username found in SELECT log: {log_output}", |
| 91 | + ) |
| 92 | + |
| 93 | + # Clean up |
| 94 | + await user.delete() |
| 95 | + |
| 96 | + finally: |
| 97 | + # Restore original logging setup |
| 98 | + logger.removeHandler(handler) |
| 99 | + logger.setLevel(original_level) |
| 100 | + handler.close() |
0 commit comments