Skip to content

Commit 412bac7

Browse files
committed
Fix ci issues.
1 parent d0039c1 commit 412bac7

2 files changed

Lines changed: 73 additions & 5 deletions

File tree

‎python_agent_harness/entry/server.py‎

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -820,12 +820,28 @@ def op_hello(self, op: dict[str, Any]) -> None:
820820
raw = op.get("protocol_versions")
821821
if raw is None:
822822
raw = op.get("protocol_version")
823+
candidates: list[Any] = (
824+
list(raw) if isinstance(raw, list) else ([] if raw is None else [raw])
825+
)
826+
# Narrow before converting rather than letting ``int()`` raise on
827+
# whatever arrives: the op is untrusted input, so the accepted
828+
# shapes belong in the code instead of in an ``except TypeError``.
829+
# An uncaught conversion error here would unwind the reader loop
830+
# and take the whole sandbox down with it.
823831
versions: list[int] = []
824-
for value in raw if isinstance(raw, list) else [raw]:
825-
try:
826-
versions.append(int(value))
827-
except (TypeError, ValueError):
828-
continue
832+
for value in candidates:
833+
if isinstance(value, bool):
834+
continue # False would otherwise be read as version 0
835+
if isinstance(value, int):
836+
versions.append(value)
837+
elif isinstance(value, float):
838+
# is_integer() is False for inf and nan too, which
839+
# ``json`` accepts by default and ``int()`` refuses
840+
if value.is_integer():
841+
versions.append(int(value))
842+
elif isinstance(value, str):
843+
with contextlib.suppress(ValueError):
844+
versions.append(int(value.strip()))
829845
if not versions:
830846
# A hello with no parseable version is a greeting, not a
831847
# negotiation: confirm what we speak and carry on.

‎tests/entry/test_server.py‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,58 @@ def test_garbled_versions_do_not_refuse(self) -> None:
19591959
lines = self._hello(server, {"op": "hello", "protocol_versions": ["x", None]})
19601960
self.assertEqual(len(_of_type(lines, "hello")), 1)
19611961

1962+
def test_numeric_strings_are_accepted(self) -> None:
1963+
server = self._server()
1964+
lines = self._hello(server, {"op": "hello", "protocol_versions": [" 1 "]})
1965+
self.assertEqual(len(_of_type(lines, "hello")), 1)
1966+
self.assertEqual(server.host_protocol_versions, [1])
1967+
1968+
def test_booleans_are_not_read_as_versions(self) -> None:
1969+
"""bool is an int subclass, so without the guard ``False`` would
1970+
be read as version 0 and the greeting refused as "no overlap".
1971+
1972+
Tested with False rather than True on purpose: ``int(True)`` is
1973+
1, which is the version we speak, so True cannot distinguish
1974+
the two behaviours.
1975+
"""
1976+
server = self._server()
1977+
lines = self._hello(server, {"op": "hello", "protocol_versions": [False]})
1978+
# nothing parseable -> a bare greeting, confirmed, not refused
1979+
self.assertEqual(len(_of_type(lines, "hello")), 1)
1980+
self.assertEqual(_of_type(lines, "error"), [])
1981+
self.assertEqual(server.host_protocol_versions, [PROTOCOL_VERSION])
1982+
1983+
def test_non_finite_versions_do_not_kill_the_server(self) -> None:
1984+
"""``json`` accepts Infinity/NaN by default and ``int()`` refuses
1985+
them. Uncaught, that unwound the reader loop and took the whole
1986+
sandbox down over one malformed greeting."""
1987+
for value in (float("inf"), float("-inf"), float("nan")):
1988+
server = self._server()
1989+
lines = self._hello(server, {"op": "hello", "protocol_versions": [value]})
1990+
self.assertEqual(len(_of_type(lines, "hello")), 1, value)
1991+
self.assertEqual(_of_type(lines, "error"), [], value)
1992+
self.assertEqual(server.host_protocol_versions, [PROTOCOL_VERSION])
1993+
1994+
def test_an_integral_float_version_is_accepted(self) -> None:
1995+
server = self._server()
1996+
lines = self._hello(server, {"op": "hello", "protocol_versions": [1.0]})
1997+
self.assertEqual(len(_of_type(lines, "hello")), 1)
1998+
self.assertEqual(server.host_protocol_versions, [1])
1999+
2000+
def test_a_fractional_float_is_not_truncated(self) -> None:
2001+
"""Truncating 1.9 to 1 would silently claim a shared version."""
2002+
server = self._server()
2003+
lines = self._hello(server, {"op": "hello", "protocol_versions": [1.9]})
2004+
self.assertEqual(len(_of_type(lines, "hello")), 1)
2005+
self.assertEqual(server.host_protocol_versions, [PROTOCOL_VERSION])
2006+
2007+
def test_a_version_the_host_cannot_parse_is_still_refused(self) -> None:
2008+
"""Narrowing must not turn "no overlap" into "greeting"."""
2009+
server = self._server()
2010+
lines = self._hello(server, {"op": "hello", "protocol_versions": ["7"]})
2011+
self.assertEqual(_of_type(lines, "hello"), [])
2012+
self.assertIn("no shared protocol version", _of_type(lines, "error")[0]["message"])
2013+
19622014
def test_hello_echoes_the_op_id(self) -> None:
19632015
server = self._server()
19642016
lines = self._hello(server, {"op": "hello", "protocol_versions": [1], "op_id": "h1"})

0 commit comments

Comments
 (0)