Skip to content

Commit c9318d8

Browse files
Sanitize control characters in polling exception messages
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 6f9ee0a commit c9318d8

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

hyperbrowser/client/polling.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,20 @@ def _safe_exception_text(exc: Exception) -> str:
3737
exception_message = str(exc)
3838
except Exception:
3939
return f"<unstringifiable {type(exc).__name__}>"
40-
if exception_message.strip():
41-
if len(exception_message) <= _MAX_EXCEPTION_TEXT_LENGTH:
42-
return exception_message
40+
sanitized_exception_message = "".join(
41+
"?" if ord(character) < 32 or ord(character) == 127 else character
42+
for character in exception_message
43+
)
44+
if sanitized_exception_message.strip():
45+
if len(sanitized_exception_message) <= _MAX_EXCEPTION_TEXT_LENGTH:
46+
return sanitized_exception_message
4347
available_message_length = (
4448
_MAX_EXCEPTION_TEXT_LENGTH - len(_TRUNCATED_EXCEPTION_TEXT_SUFFIX)
4549
)
4650
if available_message_length <= 0:
4751
return _TRUNCATED_EXCEPTION_TEXT_SUFFIX
4852
return (
49-
f"{exception_message[:available_message_length]}"
53+
f"{sanitized_exception_message[:available_message_length]}"
5054
f"{_TRUNCATED_EXCEPTION_TEXT_SUFFIX}"
5155
)
5256
return f"<{type(exc).__name__}>"

tests/test_polling.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6786,3 +6786,36 @@ def test_retry_operation_truncates_oversized_error_messages():
67866786
)
67876787

67886788
assert "... (truncated)" in str(exc_info.value)
6789+
6790+
6791+
def test_poll_until_terminal_status_sanitizes_control_characters_in_errors():
6792+
with pytest.raises(
6793+
HyperbrowserPollingError,
6794+
match=(
6795+
r"Failed to poll control-error poll after 1 attempts: "
6796+
r"bad\?message\?with\?controls"
6797+
),
6798+
):
6799+
poll_until_terminal_status(
6800+
operation_name="control-error poll",
6801+
get_status=lambda: (_ for _ in ()).throw(
6802+
RuntimeError("bad\tmessage\nwith\x7fcontrols")
6803+
),
6804+
is_terminal_status=lambda value: value == "completed",
6805+
poll_interval_seconds=0.0,
6806+
max_wait_seconds=1.0,
6807+
max_status_failures=1,
6808+
)
6809+
6810+
6811+
def test_retry_operation_sanitizes_control_characters_in_errors():
6812+
with pytest.raises(
6813+
HyperbrowserError,
6814+
match=r"control-error retry failed after 1 attempts: bad\?value\?error",
6815+
):
6816+
retry_operation(
6817+
operation_name="control-error retry",
6818+
operation=lambda: (_ for _ in ()).throw(ValueError("bad\tvalue\nerror")),
6819+
max_attempts=1,
6820+
retry_delay_seconds=0.0,
6821+
)

0 commit comments

Comments
 (0)