fix(streaming): reject hostile session SSE cursors (#4812)
This commit is contained in:
parent
3e53ef505c
commit
9b3538f1a6
2 changed files with 45 additions and 1 deletions
|
|
@ -337,6 +337,17 @@ def read_session_run_events(
|
|||
sid = _validate_id(session_id, "session_id")
|
||||
cursor_run_id, cursor_seq = _parse_run_journal_event_id(after_event_id)
|
||||
raw_cursor = str(after_event_id or "").strip()
|
||||
if raw_cursor and cursor_run_id is not None:
|
||||
try:
|
||||
cursor_run_id = _validate_id(cursor_run_id, "run_id")
|
||||
except ValueError:
|
||||
cursor_seq = None
|
||||
if raw_cursor:
|
||||
try:
|
||||
if int(raw_cursor.rsplit(":", 1)[-1]) < 0:
|
||||
cursor_seq = None
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
if raw_cursor and (cursor_run_id is None or cursor_seq is None):
|
||||
return {
|
||||
"session_id": sid,
|
||||
|
|
@ -360,7 +371,8 @@ def read_session_run_events(
|
|||
if cursor_entry is None:
|
||||
summary = find_run_summary(cursor_run_id or "", session_dir=session_dir) if cursor_run_id else None
|
||||
status = "cursor_run_missing"
|
||||
if summary and str(summary.get("session_id") or "") != sid:
|
||||
summary_sid = str(summary.get("session_id") or "").strip() if summary else ""
|
||||
if summary_sid and summary_sid != sid:
|
||||
status = "cursor_session_mismatch"
|
||||
return {
|
||||
"session_id": sid,
|
||||
|
|
|
|||
|
|
@ -163,6 +163,38 @@ def test_session_journal_distinguishes_missing_and_foreign_cursor(tmp_path):
|
|||
assert foreign["status"] == "cursor_session_mismatch"
|
||||
|
||||
|
||||
def test_session_journal_rejects_hostile_cursor_without_replay(tmp_path, monkeypatch):
|
||||
append_run_event(
|
||||
"session_1",
|
||||
"run_a",
|
||||
"token",
|
||||
{"text": "a1"},
|
||||
session_dir=tmp_path,
|
||||
seq=1,
|
||||
created_at=100.0,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"api.run_journal.find_run_summary",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("invalid cursor should not scan summaries")),
|
||||
)
|
||||
|
||||
malformed = read_session_run_events("session_1", after_event_id="bad/run:1", session_dir=tmp_path)
|
||||
negative = read_session_run_events("session_1", after_event_id="run_a:-5", session_dir=tmp_path)
|
||||
|
||||
assert malformed["status"] == "cursor_invalid"
|
||||
assert malformed["events"] == []
|
||||
assert negative["status"] == "cursor_invalid"
|
||||
assert negative["events"] == []
|
||||
|
||||
|
||||
def test_session_journal_blank_summary_session_stays_missing(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr("api.run_journal.find_run_summary", lambda *_args, **_kwargs: {"session_id": ""})
|
||||
|
||||
replay = read_session_run_events("session_1", after_event_id="run_missing:1", session_dir=tmp_path)
|
||||
|
||||
assert replay["status"] == "cursor_run_missing"
|
||||
|
||||
|
||||
def test_session_route_prefers_last_event_id_then_query_fallback(monkeypatch):
|
||||
import api.routes as routes
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue