Skip to content

Commit e5d1425

Browse files
committed
feat: add payload length limit, proto field count limit, value range validation
1 parent ec46894 commit e5d1425

File tree

3 files changed

+64
-0
lines changed

3 files changed

+64
-0
lines changed

cs2_inspect/inspect_link.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ def serialize(data: ItemPreviewData) -> str:
106106
Returns:
107107
Uppercase hex string, e.g. "00183C20B803..."
108108
"""
109+
if data.paintwear < 0.0 or data.paintwear > 1.0:
110+
raise ValueError(
111+
f"paintwear must be in [0.0, 1.0], got {data.paintwear}"
112+
)
113+
if data.customname is not None and len(data.customname) > 100:
114+
raise ValueError(
115+
f"customname must not exceed 100 characters, got {len(data.customname)}"
116+
)
109117
proto_bytes = encode_item(data)
110118
buffer = b"\x00" + proto_bytes
111119
checksum = _crc32_checksum(buffer, len(proto_bytes))
@@ -125,6 +133,10 @@ def deserialize(hex_or_url: str) -> ItemPreviewData:
125133
(where the first byte is the XOR key applied to all subsequent bytes).
126134
"""
127135
hex_str = _extract_hex(hex_or_url)
136+
if len(hex_str) > 4096:
137+
raise ValueError(
138+
f"Payload too long (max 4096 hex chars): {hex_or_url[:64]!r}..."
139+
)
128140
raw = binascii.unhexlify(hex_str)
129141

130142
if len(raw) < 6:

cs2_inspect/proto.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ def read_length_delimited(self) -> bytes:
8383
def read_all_fields(self) -> list[tuple[int, int, Any]]:
8484
"""Read all (field_number, wire_type, value) tuples until EOF."""
8585
fields: list[tuple[int, int, Any]] = []
86+
field_count = 0
8687
while self.remaining() > 0:
88+
field_count += 1
89+
if field_count > 100:
90+
raise ValueError("Protobuf field count exceeds limit of 100")
8791
field_num, wire_type = self.read_tag()
8892
if wire_type == WIRE_VARINT:
8993
value = self.read_varint()

tests/test_inspect_link.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,51 @@ def test_known_hex_checksum_matches(self):
289289
rarity=5,
290290
)
291291
assert serialize(data) == TOOL_HEX
292+
293+
294+
# ---------------------------------------------------------------------------
295+
# Defensive validation tests
296+
# ---------------------------------------------------------------------------
297+
298+
class TestDefensiveChecks:
299+
def test_deserialize_payload_too_long_raises(self):
300+
"""Payloads longer than 4096 hex chars must raise ValueError."""
301+
long_hex = "00" * 2049 # 4098 hex chars > 4096 limit
302+
with pytest.raises(ValueError, match="Payload too long"):
303+
deserialize(long_hex)
304+
305+
def test_serialize_paintwear_above_one_raises(self):
306+
"""paintwear > 1.0 must raise ValueError."""
307+
data = ItemPreviewData(defindex=7, paintwear=1.1)
308+
with pytest.raises(ValueError, match="paintwear"):
309+
serialize(data)
310+
311+
def test_serialize_paintwear_below_zero_raises(self):
312+
"""paintwear < 0.0 must raise ValueError."""
313+
data = ItemPreviewData(defindex=7, paintwear=-0.1)
314+
with pytest.raises(ValueError, match="paintwear"):
315+
serialize(data)
316+
317+
def test_serialize_paintwear_zero_is_valid(self):
318+
"""paintwear == 0.0 is a valid boundary value."""
319+
data = ItemPreviewData(defindex=7, paintwear=0.0)
320+
result = serialize(data)
321+
assert result.startswith("00")
322+
323+
def test_serialize_paintwear_one_is_valid(self):
324+
"""paintwear == 1.0 is a valid boundary value."""
325+
data = ItemPreviewData(defindex=7, paintwear=1.0)
326+
result = serialize(data)
327+
assert result.startswith("00")
328+
329+
def test_serialize_customname_101_chars_raises(self):
330+
"""customname exceeding 100 characters must raise ValueError."""
331+
data = ItemPreviewData(defindex=7, customname="x" * 101)
332+
with pytest.raises(ValueError, match="customname"):
333+
serialize(data)
334+
335+
def test_serialize_customname_100_chars_is_valid(self):
336+
"""customname of exactly 100 characters must be accepted."""
337+
data = ItemPreviewData(defindex=7, customname="x" * 100)
338+
result = serialize(data)
339+
assert result.startswith("00")

0 commit comments

Comments
 (0)