Skip to content

Commit c8ec996

Browse files
Validate computer action endpoint string hygiene
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 6cb170c commit c8ec996

3 files changed

Lines changed: 150 additions & 4 deletions

File tree

hyperbrowser/client/managers/async_manager/computer_action.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,56 @@ async def _execute_request(
4747
original_error=exc,
4848
) from exc
4949

50-
if not computer_action_endpoint:
50+
if computer_action_endpoint is None:
5151
raise HyperbrowserError(
5252
"Computer action endpoint not available for this session"
5353
)
54+
if type(computer_action_endpoint) is not str:
55+
raise HyperbrowserError("session computer_action_endpoint must be a string")
56+
try:
57+
normalized_computer_action_endpoint = computer_action_endpoint.strip()
58+
if type(normalized_computer_action_endpoint) is not str:
59+
raise TypeError("normalized computer_action_endpoint must be a string")
60+
except HyperbrowserError:
61+
raise
62+
except Exception as exc:
63+
raise HyperbrowserError(
64+
"Failed to normalize session computer_action_endpoint",
65+
original_error=exc,
66+
) from exc
67+
68+
if not normalized_computer_action_endpoint:
69+
raise HyperbrowserError(
70+
"Computer action endpoint not available for this session"
71+
)
72+
if normalized_computer_action_endpoint != computer_action_endpoint:
73+
raise HyperbrowserError(
74+
"session computer_action_endpoint must not contain leading or trailing whitespace"
75+
)
76+
try:
77+
contains_control_character = any(
78+
ord(character) < 32 or ord(character) == 127
79+
for character in normalized_computer_action_endpoint
80+
)
81+
except HyperbrowserError:
82+
raise
83+
except Exception as exc:
84+
raise HyperbrowserError(
85+
"Failed to validate session computer_action_endpoint characters",
86+
original_error=exc,
87+
) from exc
88+
if contains_control_character:
89+
raise HyperbrowserError(
90+
"session computer_action_endpoint must not contain control characters"
91+
)
5492

5593
if isinstance(params, BaseModel):
5694
payload = params.model_dump(by_alias=True, exclude_none=True)
5795
else:
5896
payload = params
5997

6098
response = await self._client.transport.post(
61-
computer_action_endpoint,
99+
normalized_computer_action_endpoint,
62100
data=payload,
63101
)
64102
return parse_response_model(

hyperbrowser/client/managers/sync_manager/computer_action.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,56 @@ def _execute_request(
4747
original_error=exc,
4848
) from exc
4949

50-
if not computer_action_endpoint:
50+
if computer_action_endpoint is None:
5151
raise HyperbrowserError(
5252
"Computer action endpoint not available for this session"
5353
)
54+
if type(computer_action_endpoint) is not str:
55+
raise HyperbrowserError("session computer_action_endpoint must be a string")
56+
try:
57+
normalized_computer_action_endpoint = computer_action_endpoint.strip()
58+
if type(normalized_computer_action_endpoint) is not str:
59+
raise TypeError("normalized computer_action_endpoint must be a string")
60+
except HyperbrowserError:
61+
raise
62+
except Exception as exc:
63+
raise HyperbrowserError(
64+
"Failed to normalize session computer_action_endpoint",
65+
original_error=exc,
66+
) from exc
67+
68+
if not normalized_computer_action_endpoint:
69+
raise HyperbrowserError(
70+
"Computer action endpoint not available for this session"
71+
)
72+
if normalized_computer_action_endpoint != computer_action_endpoint:
73+
raise HyperbrowserError(
74+
"session computer_action_endpoint must not contain leading or trailing whitespace"
75+
)
76+
try:
77+
contains_control_character = any(
78+
ord(character) < 32 or ord(character) == 127
79+
for character in normalized_computer_action_endpoint
80+
)
81+
except HyperbrowserError:
82+
raise
83+
except Exception as exc:
84+
raise HyperbrowserError(
85+
"Failed to validate session computer_action_endpoint characters",
86+
original_error=exc,
87+
) from exc
88+
if contains_control_character:
89+
raise HyperbrowserError(
90+
"session computer_action_endpoint must not contain control characters"
91+
)
5492

5593
if isinstance(params, BaseModel):
5694
payload = params.model_dump(by_alias=True, exclude_none=True)
5795
else:
5896
payload = params
5997

6098
response = self._client.transport.post(
61-
computer_action_endpoint,
99+
normalized_computer_action_endpoint,
62100
data=payload,
63101
)
64102
return parse_response_model(

tests/test_computer_action_manager.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,76 @@ async def run() -> None:
6464
asyncio.run(run())
6565

6666

67+
def test_sync_computer_action_manager_rejects_non_string_endpoints():
68+
manager = SyncComputerActionManager(_DummyClient())
69+
70+
with pytest.raises(
71+
HyperbrowserError, match="session computer_action_endpoint must be a string"
72+
) as exc_info:
73+
manager.screenshot(SimpleNamespace(computer_action_endpoint=123))
74+
75+
assert exc_info.value.original_error is None
76+
77+
78+
def test_async_computer_action_manager_rejects_non_string_endpoints():
79+
async def run() -> None:
80+
manager = AsyncComputerActionManager(_DummyClient())
81+
with pytest.raises(
82+
HyperbrowserError, match="session computer_action_endpoint must be a string"
83+
) as exc_info:
84+
await manager.screenshot(SimpleNamespace(computer_action_endpoint=123))
85+
assert exc_info.value.original_error is None
86+
87+
asyncio.run(run())
88+
89+
90+
def test_sync_computer_action_manager_rejects_string_subclass_endpoints():
91+
class _Endpoint(str):
92+
pass
93+
94+
manager = SyncComputerActionManager(_DummyClient())
95+
96+
with pytest.raises(
97+
HyperbrowserError, match="session computer_action_endpoint must be a string"
98+
) as exc_info:
99+
manager.screenshot(
100+
SimpleNamespace(
101+
computer_action_endpoint=_Endpoint("https://example.com/cua")
102+
)
103+
)
104+
105+
assert exc_info.value.original_error is None
106+
107+
108+
def test_sync_computer_action_manager_rejects_whitespace_wrapped_endpoints():
109+
manager = SyncComputerActionManager(_DummyClient())
110+
111+
with pytest.raises(
112+
HyperbrowserError,
113+
match="session computer_action_endpoint must not contain leading or trailing whitespace",
114+
) as exc_info:
115+
manager.screenshot(
116+
SimpleNamespace(computer_action_endpoint=" https://example.com/cua ")
117+
)
118+
119+
assert exc_info.value.original_error is None
120+
121+
122+
def test_async_computer_action_manager_rejects_control_character_endpoints():
123+
async def run() -> None:
124+
manager = AsyncComputerActionManager(_DummyClient())
125+
with pytest.raises(
126+
HyperbrowserError,
127+
match="session computer_action_endpoint must not contain control characters",
128+
) as exc_info:
129+
await manager.screenshot(
130+
SimpleNamespace(computer_action_endpoint="https://exa\tmple.com/cua")
131+
)
132+
assert exc_info.value.original_error is None
133+
134+
asyncio.run(run())
135+
136+
67137
def test_sync_computer_action_manager_rejects_string_subclass_session_ids():
68138
class _SessionId(str):
69139
pass

0 commit comments

Comments
 (0)