fix(web): scrub a session's on-disk artifacts in the delete endpoint

DELETE /api/sessions/{id} removed the DB row but never passed the
profile's sessions dir to SessionDB.delete_session, so the on-disk
transcript artifacts survived the UI delete: legacy session_<id>.json
snapshots (which can carry plaintext secrets) and the gateway's
request_dump_<id>_*.json dumps. The CLI delete path threaded the
directory all along; the endpoint was the outlier.

Also sweep the legacy session_<id>.json snapshot name in
SessionDB._remove_session_files so deletes and prunes clear it from
installs whose older builds wrote it.

Fixes #60207

Co-authored-by: izumi0uu <izumi0uu@gmail.com>
This commit is contained in:
Brooklyn Nicholson
2026-09-25 12:20:54 -05:00
committed by brooklyn!
parent 181af69ffe
commit 28e6496a5e
4 changed files with 96 additions and 3 deletions

View File

@@ -1527,11 +1527,14 @@ class SessionSessionsMixin:
@staticmethod
def _remove_session_files(sessions_dir: Optional[Path], session_id: str) -> None:
"""Remove ``<id>.json``/``.jsonl`` and gateway ``request_dump_<id>_*.json``; OSError is swallowed
so a filesystem hiccup never blocks a DB operation."""
"""Remove ``<id>.json``/``.jsonl``, the legacy ``session_<id>.json`` snapshot, and gateway
``request_dump_<id>_*.json``; OSError is swallowed so a filesystem hiccup never blocks a
DB operation. Every historical writer name is swept because a "deleted" session's snapshot
can carry plaintext secrets (#20334, #60207)."""
if sessions_dir is None:
return
targets = [sessions_dir / f"{session_id}{suffix}" for suffix in (".json", ".jsonl")]
targets.append(sessions_dir / f"session_{session_id}.json")
try:
# glob.escape: a session id carrying ``[`` / ``?`` / ``*`` is a PATTERN otherwise, so the
# dump sweep either matches nothing or matches another session's files.