Skip to content

Commit edd4daf

Browse files
fix: cap heartbeat-derived read timeout to 300 seconds
Prevents a malicious or buggy server from sending an absurdly large heartbeat_timeout value that would effectively disable read timeouts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 680f5ba commit edd4daf

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

src/dqliteclient/protocol.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ async def handshake(self, client_id: int = 0) -> int:
6666
# Use heartbeat timeout for subsequent reads if larger than default
6767
if response.heartbeat_timeout > 0:
6868
heartbeat_seconds = response.heartbeat_timeout / 1000.0
69-
self._timeout = max(self._timeout, heartbeat_seconds)
69+
# Cap to prevent a malicious/buggy server from disabling timeouts
70+
self._timeout = max(self._timeout, min(heartbeat_seconds, 300.0))
7071
return response.heartbeat_timeout
7172

7273
async def get_leader(self) -> tuple[int, str]:

tests/test_protocol.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@ async def test_handshake_success(
2828

2929
assert timeout == 15000
3030

31+
async def test_handshake_caps_heartbeat_timeout(
32+
self,
33+
mock_reader: AsyncMock,
34+
mock_writer: MagicMock,
35+
) -> None:
36+
"""A huge heartbeat_timeout should be capped to prevent timeout bypass."""
37+
from dqlitewire.messages import WelcomeResponse
38+
39+
# Server sends an absurdly large heartbeat timeout (e.g., corrupted value)
40+
huge_timeout_ms = 10_000_000 # 10000 seconds
41+
mock_reader.read.return_value = WelcomeResponse(
42+
heartbeat_timeout=huge_timeout_ms
43+
).encode()
44+
45+
protocol = DqliteProtocol(mock_reader, mock_writer, timeout=10.0)
46+
await protocol.handshake()
47+
48+
# The read timeout should be capped, not set to 10000 seconds
49+
assert protocol._timeout <= 300.0
50+
3151
async def test_handshake_failure(
3252
self,
3353
protocol: DqliteProtocol,

0 commit comments

Comments
 (0)