Skip to content

Commit ba0324c

Browse files
Harden api_key empty-check length boundaries across config and clients
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 9836d42 commit ba0324c

6 files changed

Lines changed: 134 additions & 3 deletions

File tree

hyperbrowser/client/base.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,16 @@ def __init__(
4949
"Failed to normalize api_key",
5050
original_error=exc,
5151
) from exc
52-
if not normalized_resolved_api_key:
52+
try:
53+
is_empty_api_key = len(normalized_resolved_api_key) == 0
54+
except HyperbrowserError:
55+
raise
56+
except Exception as exc:
57+
raise HyperbrowserError(
58+
"Failed to normalize api_key",
59+
original_error=exc,
60+
) from exc
61+
if is_empty_api_key:
5362
if api_key_from_constructor:
5463
raise HyperbrowserError("api_key must not be empty")
5564
raise HyperbrowserError(

hyperbrowser/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,16 @@ def normalize_api_key(
4545
"Failed to normalize api_key",
4646
original_error=exc,
4747
) from exc
48-
if not normalized_api_key:
48+
try:
49+
is_empty_api_key = len(normalized_api_key) == 0
50+
except HyperbrowserError:
51+
raise
52+
except Exception as exc:
53+
raise HyperbrowserError(
54+
"Failed to normalize api_key",
55+
original_error=exc,
56+
) from exc
57+
if is_empty_api_key:
4958
raise HyperbrowserError(empty_error_message)
5059
try:
5160
contains_control_character = any(

hyperbrowser/transport/base.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,16 @@ def _normalize_transport_api_key(api_key: str) -> str:
7979
original_error=exc,
8080
) from exc
8181

82-
if not normalized_api_key:
82+
try:
83+
is_empty_api_key = len(normalized_api_key) == 0
84+
except HyperbrowserError:
85+
raise
86+
except Exception as exc:
87+
raise HyperbrowserError(
88+
"Failed to normalize api_key",
89+
original_error=exc,
90+
) from exc
91+
if is_empty_api_key:
8392
raise HyperbrowserError("api_key must not be empty")
8493

8594
try:

tests/test_client_api_key.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,39 @@ def strip(self, chars=None): # type: ignore[override]
237237
client_class(api_key=_NonStringStripResultApiKey("test-key"))
238238

239239
assert isinstance(exc_info.value.original_error, TypeError)
240+
241+
242+
@pytest.mark.parametrize("client_class", [Hyperbrowser, AsyncHyperbrowser])
243+
def test_client_wraps_api_key_empty_check_length_failures(client_class):
244+
class _BrokenLengthApiKey(str):
245+
class _NormalizedKey(str):
246+
def __len__(self):
247+
raise RuntimeError("api key length exploded")
248+
249+
def strip(self, chars=None): # type: ignore[override]
250+
_ = chars
251+
return self._NormalizedKey("test-key")
252+
253+
with pytest.raises(HyperbrowserError, match="Failed to normalize api_key") as exc_info:
254+
client_class(api_key=_BrokenLengthApiKey("test-key"))
255+
256+
assert isinstance(exc_info.value.original_error, RuntimeError)
257+
258+
259+
@pytest.mark.parametrize("client_class", [Hyperbrowser, AsyncHyperbrowser])
260+
def test_client_preserves_hyperbrowser_api_key_empty_check_length_failures(
261+
client_class,
262+
):
263+
class _BrokenLengthApiKey(str):
264+
class _NormalizedKey(str):
265+
def __len__(self):
266+
raise HyperbrowserError("custom length failure")
267+
268+
def strip(self, chars=None): # type: ignore[override]
269+
_ = chars
270+
return self._NormalizedKey("test-key")
271+
272+
with pytest.raises(HyperbrowserError, match="custom length failure") as exc_info:
273+
client_class(api_key=_BrokenLengthApiKey("test-key"))
274+
275+
assert exc_info.value.original_error is None

tests/test_config.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,38 @@ def strip(self, chars=None): # type: ignore[override]
383383
assert isinstance(exc_info.value.original_error, TypeError)
384384

385385

386+
def test_client_config_wraps_api_key_empty_check_length_failures():
387+
class _BrokenApiKey(str):
388+
class _NormalizedKey(str):
389+
def __len__(self):
390+
raise RuntimeError("api key length exploded")
391+
392+
def strip(self, chars=None): # type: ignore[override]
393+
_ = chars
394+
return self._NormalizedKey("test-key")
395+
396+
with pytest.raises(HyperbrowserError, match="Failed to normalize api_key") as exc_info:
397+
ClientConfig(api_key=_BrokenApiKey("test-key"))
398+
399+
assert isinstance(exc_info.value.original_error, RuntimeError)
400+
401+
402+
def test_client_config_preserves_hyperbrowser_api_key_empty_check_length_failures():
403+
class _BrokenApiKey(str):
404+
class _NormalizedKey(str):
405+
def __len__(self):
406+
raise HyperbrowserError("custom length failure")
407+
408+
def strip(self, chars=None): # type: ignore[override]
409+
_ = chars
410+
return self._NormalizedKey("test-key")
411+
412+
with pytest.raises(HyperbrowserError, match="custom length failure") as exc_info:
413+
ClientConfig(api_key=_BrokenApiKey("test-key"))
414+
415+
assert exc_info.value.original_error is None
416+
417+
386418
def test_client_config_wraps_api_key_iteration_runtime_errors():
387419
class _BrokenApiKey(str):
388420
def strip(self, chars=None): # type: ignore[override]

tests/test_transport_api_key.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,42 @@ def __iter__(self):
6262
assert isinstance(exc_info.value.original_error, RuntimeError)
6363

6464

65+
@pytest.mark.parametrize("transport_class", [SyncTransport, AsyncTransport])
66+
def test_transport_wraps_api_key_empty_check_length_failures(transport_class):
67+
class _BrokenLengthApiKey(str):
68+
class _NormalizedKey(str):
69+
def __len__(self):
70+
raise RuntimeError("api key length exploded")
71+
72+
def strip(self, chars=None): # type: ignore[override]
73+
_ = chars
74+
return self._NormalizedKey("test-key")
75+
76+
with pytest.raises(HyperbrowserError, match="Failed to normalize api_key") as exc_info:
77+
transport_class(api_key=_BrokenLengthApiKey("test-key"))
78+
79+
assert isinstance(exc_info.value.original_error, RuntimeError)
80+
81+
82+
@pytest.mark.parametrize("transport_class", [SyncTransport, AsyncTransport])
83+
def test_transport_preserves_hyperbrowser_api_key_empty_check_length_failures(
84+
transport_class,
85+
):
86+
class _BrokenLengthApiKey(str):
87+
class _NormalizedKey(str):
88+
def __len__(self):
89+
raise HyperbrowserError("custom length failure")
90+
91+
def strip(self, chars=None): # type: ignore[override]
92+
_ = chars
93+
return self._NormalizedKey("test-key")
94+
95+
with pytest.raises(HyperbrowserError, match="custom length failure") as exc_info:
96+
transport_class(api_key=_BrokenLengthApiKey("test-key"))
97+
98+
assert exc_info.value.original_error is None
99+
100+
65101
@pytest.mark.parametrize("transport_class", [SyncTransport, AsyncTransport])
66102
def test_transport_preserves_hyperbrowser_api_key_character_iteration_failures(
67103
transport_class,

0 commit comments

Comments
 (0)