Skip to content

Commit 86656b3

Browse files
authored
Merge branch 'master' into ps_fix_otel_init_py_to_expose_base_objects
2 parents 202dd00 + a72e3f0 commit 86656b3

8 files changed

Lines changed: 818 additions & 109 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/cluster.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,7 +1560,7 @@ def _execute_command(self, target_node, *args, **kwargs):
15601560
self._record_command_metric(
15611561
command_name=command,
15621562
duration_seconds=time.monotonic() - start_time,
1563-
connection=connection,
1563+
connection=e.connection,
15641564
error=e,
15651565
)
15661566
raise
@@ -1576,7 +1576,7 @@ def _execute_command(self, target_node, *args, **kwargs):
15761576
self._record_command_metric(
15771577
command_name=command,
15781578
duration_seconds=time.monotonic() - start_time,
1579-
connection=connection,
1579+
connection=e.connection,
15801580
error=e,
15811581
)
15821582
raise
@@ -1615,11 +1615,10 @@ def _execute_command(self, target_node, *args, **kwargs):
16151615

16161616
# DON'T set redis_connection = None - keep the pool for reuse
16171617
self.nodes_manager.initialize()
1618-
e.connection = connection
16191618
self._record_command_metric(
16201619
command_name=command,
16211620
duration_seconds=time.monotonic() - start_time,
1622-
connection=connection,
1621+
connection=e.connection,
16231622
error=e,
16241623
)
16251624
raise e
@@ -1723,17 +1722,19 @@ def _execute_command(self, target_node, *args, **kwargs):
17231722
self._record_command_metric(
17241723
command_name=command,
17251724
duration_seconds=time.monotonic() - start_time,
1726-
connection=connection,
1725+
connection=e.connection,
17271726
error=e,
17281727
)
17291728
raise
17301729
except ResponseError as e:
17311730
# this is used to report the metrics based on host and port info
1732-
e.connection = connection
1731+
# ResponseError typically happens after get_connection() succeeds,
1732+
# so connection should be available
1733+
e.connection = connection if connection else target_node
17331734
self._record_command_metric(
17341735
command_name=command,
17351736
duration_seconds=time.monotonic() - start_time,
1736-
connection=connection,
1737+
connection=e.connection,
17371738
error=e,
17381739
)
17391740
raise
@@ -1748,7 +1749,7 @@ def _execute_command(self, target_node, *args, **kwargs):
17481749
self._record_command_metric(
17491750
command_name=command,
17501751
duration_seconds=time.monotonic() - start_time,
1751-
connection=connection,
1752+
connection=e.connection,
17521753
error=e,
17531754
)
17541755
raise e
@@ -1780,12 +1781,16 @@ def _record_command_metric(
17801781
"""
17811782
Records operation duration metric directly.
17821783
"""
1784+
host = connection.host if connection else "unknown"
1785+
port = connection.port if connection else 0
1786+
db = str(connection.db) if connection and hasattr(connection, "db") else "0"
1787+
17831788
record_operation_duration(
17841789
command_name=command_name,
17851790
duration_seconds=duration_seconds,
1786-
server_address=connection.host,
1787-
server_port=connection.port,
1788-
db_namespace=str(connection.db),
1791+
server_address=host,
1792+
server_port=port,
1793+
db_namespace=db,
17891794
error=error,
17901795
)
17911796

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):

0 commit comments

Comments
 (0)