Skip to content

Commit 4612080

Browse files
Harden HyperbrowserError string formatting safety
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 70aa6e8 commit 4612080

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

hyperbrowser/exceptions.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22
from typing import Optional, Any
33

44

5+
def _safe_exception_text(value: Any, *, fallback: str) -> str:
6+
try:
7+
text_value = str(value)
8+
except Exception:
9+
return fallback
10+
sanitized_value = "".join(
11+
"?" if ord(character) < 32 or ord(character) == 127 else character
12+
for character in text_value
13+
)
14+
if sanitized_value.strip():
15+
return sanitized_value
16+
return fallback
17+
18+
519
class HyperbrowserError(Exception):
620
"""Base exception class for Hyperbrowser SDK errors"""
721

@@ -19,17 +33,29 @@ def __init__(
1933

2034
def __str__(self) -> str:
2135
"""Custom string representation to show a cleaner error message"""
22-
parts = [f"{self.args[0]}"]
36+
message_value = self.args[0] if self.args else "Hyperbrowser error"
37+
message_text = _safe_exception_text(
38+
message_value,
39+
fallback="Hyperbrowser error",
40+
)
41+
parts = [message_text]
2342

2443
if self.status_code is not None:
25-
parts.append(f"Status: {self.status_code}")
44+
status_text = _safe_exception_text(
45+
self.status_code,
46+
fallback="<unknown status>",
47+
)
48+
parts.append(f"Status: {status_text}")
2649

2750
if self.original_error and not isinstance(
2851
self.original_error, HyperbrowserError
2952
):
3053
error_type = type(self.original_error).__name__
31-
error_msg = str(self.original_error)
32-
if error_msg and error_msg != str(self.args[0]):
54+
error_msg = _safe_exception_text(
55+
self.original_error,
56+
fallback=f"<unstringifiable {error_type}>",
57+
)
58+
if error_msg != message_text:
3359
parts.append(f"Caused by {error_type}: {error_msg}")
3460

3561
return " - ".join(parts)

tests/test_exceptions.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,28 @@ def test_hyperbrowser_error_str_includes_original_error_details_once():
1212
error = HyperbrowserError("request failed", original_error=root_cause)
1313

1414
assert str(error) == "request failed - Caused by ValueError: boom"
15+
16+
17+
def test_hyperbrowser_error_str_handles_unstringifiable_original_error():
18+
class _UnstringifiableError(Exception):
19+
def __str__(self) -> str:
20+
raise RuntimeError("cannot stringify")
21+
22+
error = HyperbrowserError("request failed", original_error=_UnstringifiableError())
23+
24+
assert (
25+
str(error)
26+
== "request failed - Caused by _UnstringifiableError: <unstringifiable _UnstringifiableError>"
27+
)
28+
29+
30+
def test_hyperbrowser_error_str_sanitizes_control_characters():
31+
error = HyperbrowserError("bad\trequest\nmessage\x7f")
32+
33+
assert str(error) == "bad?request?message?"
34+
35+
36+
def test_hyperbrowser_error_str_uses_placeholder_for_blank_message():
37+
error = HyperbrowserError(" ")
38+
39+
assert str(error) == "Hyperbrowser error"

0 commit comments

Comments
 (0)