From 3e67346d9438a703f4e7e54fcf6b834daca57e35 Mon Sep 17 00:00:00 2001 From: RapidPoseidon Date: Fri, 24 Apr 2026 09:04:15 +0000 Subject: [PATCH] fix(auth): add HTTP timeouts to bridge-token and Client endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_get_bridge_tokens`, `_poll_read_key`, and `_create_client` all called `requests.*` without a `timeout=` argument. A slow or hung identity server — or a slowloris — would pin the calling thread indefinitely. The existing `poll_timeout` only caps the total polling loop, not individual requests, and the bridge/create-client calls had no cap at all. Add a shared `(connect=10s, read=30s)` pair and thread it through all three call sites. Session: https://session-bc38cc85.poseidon.rapidata.internal/ Co-Authored-By: Claude Opus 4.7 Co-Authored-By: lino --- src/rapidata/service/credential_manager.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/rapidata/service/credential_manager.py b/src/rapidata/service/credential_manager.py index cf659ca31..87e379a3c 100644 --- a/src/rapidata/service/credential_manager.py +++ b/src/rapidata/service/credential_manager.py @@ -32,6 +32,12 @@ class BridgeToken(BaseModel): class CredentialManager: + # Per-request HTTP timeout for the bridge/identity endpoints. A + # (connect, read) pair prevents slow or hung identity servers from + # pinning the calling thread indefinitely — the outer poll_timeout + # only caps the total polling loop, not individual requests. + _HTTP_TIMEOUT: Tuple[float, float] = (10.0, 30.0) + def __init__( self, endpoint: str, @@ -139,7 +145,11 @@ def _get_bridge_tokens(self) -> Optional[BridgeToken]: bridge_endpoint = ( f"{self.endpoint}/identity/bridge-token?clientId=rapidata-cli" ) - response = requests.post(bridge_endpoint, verify=self.cert_path) + response = requests.post( + bridge_endpoint, + verify=self.cert_path, + timeout=self._HTTP_TIMEOUT, + ) if not response.ok: logger.error("Failed to get bridge tokens: %s", response.status_code) return None @@ -158,7 +168,10 @@ def _poll_read_key(self, read_key: str) -> Optional[str]: while time.time() - start_time < self.poll_timeout: try: response = requests.get( - read_endpoint, params={"readKey": read_key}, verify=self.cert_path + read_endpoint, + params={"readKey": read_key}, + verify=self.cert_path, + timeout=self._HTTP_TIMEOUT, ) if response.status_code == 200: @@ -193,6 +206,7 @@ def _create_client(self, access_token: str) -> Optional[Tuple[str, str, str]]: }, json={"displayName": display_name}, verify=self.cert_path, + timeout=self._HTTP_TIMEOUT, ) response.raise_for_status() data = response.json()