Merge pull request #3023

This commit is contained in:
nesquena-hermes
2026-05-28 01:58:34 +00:00
2 changed files with 29 additions and 3 deletions
+7 -3
View File
@@ -690,8 +690,10 @@ class Session:
@classmethod
def load(cls, sid):
# Validate session ID format to prevent path traversal
if not sid or not all(c in '0123456789abcdefghijklmnopqrstuvwxyz_' for c in sid):
# Validate session ID format to prevent path traversal. API/gateway
# session ids may contain hyphens (for example ``api-*`` and
# ``reachy-voice-*``); allow those but still reject dots/slashes.
if not sid or not all(c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-' for c in sid):
return None
p = SESSION_DIR / f'{sid}.json'
if not p.exists():
@@ -718,7 +720,9 @@ class Session:
top-level "messages" field and synthesize a small metadata-only object.
Falls back to load() for legacy or unexpected file layouts.
"""
if not sid or not all(c in '0123456789abcdefghijklmnopqrstuvwxyz_' for c in sid):
# Same path-safety contract as load(): hyphens are valid session ids,
# path separators and traversal dots are not.
if not sid or not all(c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-' for c in sid):
return None
p = SESSION_DIR / f'{sid}.json'
if not p.exists():
+22
View File
@@ -83,6 +83,28 @@ def test_compact_exposes_last_message_at_from_message_timestamp():
assert compact["last_message_at"] == 200.0
def test_session_load_allows_hyphenated_safe_ids_but_rejects_traversal():
sid = "api-182894de593468b6"
s = _make_session(sid, "API session", updated_at=100)
s.path.write_text(json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8")
assert Session.load(sid) is not None
assert Session.load_metadata_only(sid) is not None
assert Session.load("bad/../id") is None
assert Session.load_metadata_only("bad.id") is None
def test_full_index_rebuild_includes_hyphenated_sessions():
sid = "reachy-voice-20260513-1131-d5542adf"
s = _make_session(sid, "Reachy voice", updated_at=100)
s.path.write_text(json.dumps(s.__dict__, ensure_ascii=False, indent=2), encoding="utf-8")
_write_session_index(updates=None)
ids = [entry["session_id"] for entry in _read_index(models.SESSION_INDEX_FILE)]
assert sid in ids
def test_prune_session_from_index_removes_requested_row_only():
index_file = models.SESSION_INDEX_FILE
s_a = _make_session("sess_a", "A", updated_at=100)