Skip to content

Commit 5df9e0b

Browse files
Harden exception text sanitization against broken string subclasses
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent a24cfd7 commit 5df9e0b

2 files changed

Lines changed: 56 additions & 6 deletions

File tree

hyperbrowser/exceptions.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ def _safe_exception_text(value: Any, *, fallback: str) -> str:
2121
text_value = str(value)
2222
except Exception:
2323
return fallback
24-
sanitized_value = "".join(
25-
"?" if ord(character) < 32 or ord(character) == 127 else character
26-
for character in text_value
27-
)
28-
if sanitized_value.strip():
29-
return _truncate_exception_text(sanitized_value)
24+
if not isinstance(text_value, str):
25+
return fallback
26+
try:
27+
sanitized_value = "".join(
28+
"?" if ord(character) < 32 or ord(character) == 127 else character
29+
for character in text_value
30+
)
31+
if sanitized_value.strip():
32+
return _truncate_exception_text(sanitized_value)
33+
except Exception:
34+
return fallback
3035
return fallback
3136

3237

tests/test_exceptions.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,48 @@ def test_hyperbrowser_error_str_truncates_oversized_original_error_message():
5353
rendered_error = str(error)
5454
assert "Caused by ValueError:" in rendered_error
5555
assert rendered_error.endswith("... (truncated)")
56+
57+
58+
def test_hyperbrowser_error_str_handles_message_string_subclass_iteration_failures():
59+
class _BrokenMessage:
60+
class _BrokenString(str):
61+
def __iter__(self):
62+
raise RuntimeError("cannot iterate error message")
63+
64+
def __str__(self) -> str:
65+
return self._BrokenString("request failed")
66+
67+
error = HyperbrowserError(_BrokenMessage()) # type: ignore[arg-type]
68+
69+
assert str(error) == "Hyperbrowser error"
70+
71+
72+
def test_hyperbrowser_error_str_handles_status_string_subclass_iteration_failures():
73+
class _BrokenStatus:
74+
class _BrokenString(str):
75+
def __iter__(self):
76+
raise RuntimeError("cannot iterate status text")
77+
78+
def __str__(self) -> str:
79+
return self._BrokenString("status\tvalue")
80+
81+
error = HyperbrowserError("request failed", status_code=_BrokenStatus()) # type: ignore[arg-type]
82+
83+
assert str(error) == "request failed - Status: <unknown status>"
84+
85+
86+
def test_hyperbrowser_error_str_handles_original_error_string_subclass_iteration_failures():
87+
class _BrokenOriginalError(Exception):
88+
class _BrokenString(str):
89+
def __iter__(self):
90+
raise RuntimeError("cannot iterate original error text")
91+
92+
def __str__(self) -> str:
93+
return self._BrokenString("bad\tcause")
94+
95+
error = HyperbrowserError("request failed", original_error=_BrokenOriginalError())
96+
97+
assert (
98+
str(error)
99+
== "request failed - Caused by _BrokenOriginalError: <unstringifiable _BrokenOriginalError>"
100+
)

0 commit comments

Comments
 (0)