Skip to content

Commit 15df5b0

Browse files
Reject API keys containing control characters
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 58c3bb6 commit 15df5b0

File tree

6 files changed

+46
-2
lines changed

6 files changed

+46
-2
lines changed

hyperbrowser/config.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def __post_init__(self) -> None:
2121
self.api_key = self.api_key.strip()
2222
if not self.api_key:
2323
raise HyperbrowserError("api_key must not be empty")
24+
if any(
25+
ord(character) < 32 or ord(character) == 127 for character in self.api_key
26+
):
27+
raise HyperbrowserError("api_key must not contain control characters")
2428
self.base_url = self.normalize_base_url(self.base_url)
2529
self.headers = normalize_headers(
2630
self.headers,
@@ -74,7 +78,9 @@ def normalize_base_url(base_url: str) -> str:
7478
break
7579
decoded_base_path = next_decoded_base_path
7680
else:
77-
raise HyperbrowserError("base_url path contains excessively nested URL encoding")
81+
raise HyperbrowserError(
82+
"base_url path contains excessively nested URL encoding"
83+
)
7884
if "\\" in decoded_base_path:
7985
raise HyperbrowserError("base_url must not contain backslashes")
8086
if any(character.isspace() for character in decoded_base_path):
@@ -101,7 +107,9 @@ def normalize_base_url(base_url: str) -> str:
101107
break
102108
decoded_base_netloc = next_decoded_base_netloc
103109
else:
104-
raise HyperbrowserError("base_url host contains excessively nested URL encoding")
110+
raise HyperbrowserError(
111+
"base_url host contains excessively nested URL encoding"
112+
)
105113
if "\\" in decoded_base_netloc:
106114
raise HyperbrowserError("base_url host must not contain backslashes")
107115
if any(character.isspace() for character in decoded_base_netloc):

hyperbrowser/transport/async_transport.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ def __init__(self, api_key: str, headers: Optional[Mapping[str, str]] = None):
2222
normalized_api_key = api_key.strip()
2323
if not normalized_api_key:
2424
raise HyperbrowserError("api_key must not be empty")
25+
if any(
26+
ord(character) < 32 or ord(character) == 127
27+
for character in normalized_api_key
28+
):
29+
raise HyperbrowserError("api_key must not contain control characters")
2530
merged_headers = merge_headers(
2631
{
2732
"x-api-key": normalized_api_key,

hyperbrowser/transport/sync.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ def __init__(self, api_key: str, headers: Optional[Mapping[str, str]] = None):
2222
normalized_api_key = api_key.strip()
2323
if not normalized_api_key:
2424
raise HyperbrowserError("api_key must not be empty")
25+
if any(
26+
ord(character) < 32 or ord(character) == 127
27+
for character in normalized_api_key
28+
):
29+
raise HyperbrowserError("api_key must not contain control characters")
2530
merged_headers = merge_headers(
2631
{
2732
"x-api-key": normalized_api_key,

tests/test_client_api_key.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,17 @@ def test_async_client_rejects_blank_constructor_api_key(monkeypatch):
7676

7777
with pytest.raises(HyperbrowserError, match="api_key must not be empty"):
7878
AsyncHyperbrowser(api_key="\t")
79+
80+
81+
def test_sync_client_rejects_control_character_api_key():
82+
with pytest.raises(
83+
HyperbrowserError, match="api_key must not contain control characters"
84+
):
85+
Hyperbrowser(api_key="bad\nkey")
86+
87+
88+
def test_async_client_rejects_control_character_api_key():
89+
with pytest.raises(
90+
HyperbrowserError, match="api_key must not contain control characters"
91+
):
92+
AsyncHyperbrowser(api_key="bad\nkey")

tests/test_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ def test_client_config_rejects_non_string_values():
195195

196196
with pytest.raises(HyperbrowserError, match="api_key must not be empty"):
197197
ClientConfig(api_key=" ")
198+
with pytest.raises(
199+
HyperbrowserError, match="api_key must not contain control characters"
200+
):
201+
ClientConfig(api_key="bad\nkey")
198202

199203

200204
def test_client_config_rejects_empty_or_invalid_base_url():

tests/test_custom_headers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ def test_sync_transport_rejects_invalid_api_key_values():
3333
SyncTransport(api_key=None) # type: ignore[arg-type]
3434
with pytest.raises(HyperbrowserError, match="api_key must not be empty"):
3535
SyncTransport(api_key=" ")
36+
with pytest.raises(
37+
HyperbrowserError, match="api_key must not contain control characters"
38+
):
39+
SyncTransport(api_key="bad\nkey")
3640

3741

3842
def test_sync_transport_rejects_empty_header_name():
@@ -88,6 +92,10 @@ def test_async_transport_rejects_invalid_api_key_values():
8892
AsyncTransport(api_key=None) # type: ignore[arg-type]
8993
with pytest.raises(HyperbrowserError, match="api_key must not be empty"):
9094
AsyncTransport(api_key=" ")
95+
with pytest.raises(
96+
HyperbrowserError, match="api_key must not contain control characters"
97+
):
98+
AsyncTransport(api_key="bad\nkey")
9199

92100

93101
def test_async_transport_rejects_empty_header_name():

0 commit comments

Comments
 (0)