fix(history): bound lifecycle kind checks before hashing (#209)

This commit is contained in:
Vito Sansevero
2026-10-17 15:06:35 -07:00
parent b731fdfe1b
commit 508f03d0a5
2 changed files with 44 additions and 3 deletions
+33
View File
@@ -344,3 +344,36 @@ class ExecutionHistoryEventsTests(unittest.TestCase):
classify_evidence(JOB, command.evidence).reason, "evidence_limit"
)
self.assertLessEqual(len(encode_command(command)), 2048)
def test_oversized_unknown_kind_is_ignored_without_hashing(self):
class OversizedKind(str):
def __hash__(self):
raise AssertionError("Oversized event kind must not be hashed")
command = finish(
[
message(OversizedKind("x" * 1_000_000)),
message("execution_success", timestamp=300),
]
)
result = classify_evidence(JOB, command.evidence)
self.assertEqual(
(result.outcome, result.reason), ("succeeded", "terminal_success")
)
self.assertLessEqual(len(encode_command(command)), 2048)
def test_encoder_rejects_oversized_kind_without_hashing(self):
class OversizedKind(str):
def __hash__(self):
raise AssertionError("Oversized event kind must not be hashed")
command = finish([message("execution_success")])
event = LifecycleEvent(
OversizedKind("x" * 1_000_000), JOB, None, None, None, None
)
with self.assertRaises(ValueError):
encode_command(
replace(
command, evidence=TerminalEvidence((event,), False, False, False)
)
)
+11 -3
View File
@@ -13,6 +13,7 @@ TERMINAL_KINDS = {
"execution_interrupted": ("interrupted", "terminal_interrupted"),
}
EVENT_KINDS = frozenset((*TERMINAL_KINDS, "execution_start"))
MAX_EVENT_KIND_LENGTH = max(map(len, EVENT_KINDS))
@dataclass(frozen=True)
@@ -68,6 +69,14 @@ class Completion:
RunCommand = RunStart | RunFinish
def _valid_event_kind(value: object) -> bool:
return (
isinstance(value, str)
and len(value) <= MAX_EVENT_KIND_LENGTH
and value in EVENT_KINDS
)
def _valid_integer(value: object) -> bool:
return type(value) is int and 0 <= value <= MAX_INTEGER
@@ -159,7 +168,7 @@ def make_finish(
if not malformed:
kind, data = message
malformed = not isinstance(kind, str)
if not malformed and kind not in EVENT_KINDS:
if not malformed and not _valid_event_kind(kind):
continue
malformed |= usable_job_id is None
if not malformed:
@@ -302,8 +311,7 @@ def _valid_evidence(evidence: object) -> bool:
if type(event) is not LifecycleEvent:
return False
if (
not isinstance(event.kind, str)
or event.kind not in EVENT_KINDS
not _valid_event_kind(event.kind)
or not _valid_uuid(event.job_id)
or (event.timestamp is not None and not _valid_integer(event.timestamp))
):