Skip to content

Commit a72e3f0

Browse files
authored
Fixing security concern in __repr__ methods for ConnectionPools - passwords might leak in plain text logs (#3998)
* Fixing security concern in __repr__ methods for ConnectionPools - passwords might leak in plain text logs * Disabling dependency vulnerability check for the upcoming patch release (CVE-2026-32597 --> PyJWT does not validate the crit (Critical) Header Parameter defined in RFC 7515, this will be fixed in the next release) - will update the pyjwt version in master after a patch is released
1 parent 18b5352 commit a72e3f0

5 files changed

Lines changed: 83 additions & 2 deletions

File tree

.github/workflows/integration.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ jobs:
4545
ignore-vulns: |
4646
GHSA-w596-4wvx-j9j6 # subversion related git pull, dependency for pytest. There is no impact here.
4747
CVE-2026-26007 # dependency for entraid tests
48+
CVE-2026-32597 # PyJWT does not validate the crit (Critical) Header Parameter defined in RFC 7515, this will be fixed in the next release
4849
4950
lint:
5051
name: Code linters

redis/asyncio/connection.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,8 +1308,23 @@ def __init__(
13081308
if self._event_dispatcher is None:
13091309
self._event_dispatcher = EventDispatcher()
13101310

1311+
# Keys that should be redacted in __repr__ to avoid exposing sensitive information
1312+
SENSITIVE_REPR_KEYS = frozenset(
1313+
{
1314+
"password",
1315+
"username",
1316+
"ssl_password",
1317+
"credential_provider",
1318+
}
1319+
)
1320+
13111321
def __repr__(self):
1312-
conn_kwargs = ",".join([f"{k}={v}" for k, v in self.connection_kwargs.items()])
1322+
conn_kwargs = ",".join(
1323+
[
1324+
f"{k}={'<REDACTED>' if k in self.SENSITIVE_REPR_KEYS else v}"
1325+
for k, v in self.connection_kwargs.items()
1326+
]
1327+
)
13131328
return (
13141329
f"<{self.__class__.__module__}.{self.__class__.__name__}"
13151330
f"(<{self.connection_class.__module__}.{self.connection_class.__name__}"

redis/connection.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2876,8 +2876,23 @@ def __init__(
28762876

28772877
self.reset()
28782878

2879+
# Keys that should be redacted in __repr__ to avoid exposing sensitive information
2880+
SENSITIVE_REPR_KEYS = frozenset(
2881+
{
2882+
"password",
2883+
"username",
2884+
"ssl_password",
2885+
"credential_provider",
2886+
}
2887+
)
2888+
28792889
def __repr__(self) -> str:
2880-
conn_kwargs = ",".join([f"{k}={v}" for k, v in self.connection_kwargs.items()])
2890+
conn_kwargs = ",".join(
2891+
[
2892+
f"{k}={'<REDACTED>' if k in self.SENSITIVE_REPR_KEYS else v}"
2893+
for k, v in self.connection_kwargs.items()
2894+
]
2895+
)
28812896
return (
28822897
f"<{self.__class__.__module__}.{self.__class__.__name__}"
28832898
f"(<{self.connection_class.__module__}.{self.connection_class.__name__}"

tests/test_asyncio/test_connection_pool.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,31 @@ def test_repr_contains_db_info_unix(self):
418418
expected = "path=abc,db=0,client_name=test-client"
419419
assert expected in repr(pool)
420420

421+
def test_repr_redacts_sensitive_information(self):
422+
"""Test that __repr__ redacts sensitive values like password and username."""
423+
pool = ConnectionPool(
424+
host="localhost",
425+
port=6379,
426+
password="secret_password_123",
427+
username="myuser",
428+
ssl_password="ssl_secret_456",
429+
db=0,
430+
)
431+
repr_output = repr(pool)
432+
433+
# Verify sensitive values are redacted
434+
assert "secret_password_123" not in repr_output
435+
assert "myuser" not in repr_output
436+
assert "ssl_secret_456" not in repr_output
437+
438+
# Verify the REDACTED placeholder is present
439+
assert "<REDACTED>" in repr_output
440+
441+
# Verify non-sensitive values are still visible
442+
assert "host=localhost" in repr_output
443+
assert "port=6379" in repr_output
444+
assert "db=0" in repr_output
445+
421446

422447
class TestConnectionPoolURLParsing:
423448
def test_hostname(self):

tests/test_connection_pool.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,31 @@ def test_repr_contains_db_info_unix(self):
259259
expected = "path=abc,db=0,client_name=test-client"
260260
assert expected in repr(pool)
261261

262+
def test_repr_redacts_sensitive_information(self):
263+
"""Test that __repr__ redacts sensitive values like password and username."""
264+
pool = redis.ConnectionPool(
265+
host="localhost",
266+
port=6379,
267+
password="secret_password_123",
268+
username="myuser",
269+
ssl_password="ssl_secret_456",
270+
db=0,
271+
)
272+
repr_output = repr(pool)
273+
274+
# Verify sensitive values are redacted
275+
assert "secret_password_123" not in repr_output
276+
assert "myuser" not in repr_output
277+
assert "ssl_secret_456" not in repr_output
278+
279+
# Verify the REDACTED placeholder is present
280+
assert "<REDACTED>" in repr_output
281+
282+
# Verify non-sensitive values are still visible
283+
assert "host=localhost" in repr_output
284+
assert "port=6379" in repr_output
285+
assert "db=0" in repr_output
286+
262287
@pytest.mark.onlynoncluster
263288
@skip_if_resp_version(2)
264289
@skip_if_server_version_lt("7.4.0")

0 commit comments

Comments
 (0)