fix(history): bound lifecycle kind checks before hashing (#209)
This commit is contained in:
@@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user