From d2cbe6536e196fe0f2f1692cddfc816ea3fe9c40 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:19:01 +0900 Subject: [PATCH] fix(session): reject symlinked legacy session migration --- nanobot/session/manager.py | 7 ++++- tests/session/test_session_location.py | 39 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/nanobot/session/manager.py b/nanobot/session/manager.py index 30d1c06b4..83498bc2e 100644 --- a/nanobot/session/manager.py +++ b/nanobot/session/manager.py @@ -550,9 +550,14 @@ class JsonlSessionStore: def _migrate_from_workspace(self, workspace: Path) -> None: """Move legacy in-workspace session files into the out-of-workspace store.""" old_dir = Path(workspace).expanduser() / "sessions" - if not old_dir.is_dir(): + if old_dir.is_symlink() or not old_dir.is_dir(): + if old_dir.is_symlink(): + logger.warning("Skipping symlinked legacy sessions directory: {}", old_dir) return for src in old_dir.glob("*.jsonl"): + if src.is_symlink() or not src.is_file(): + logger.warning("Skipping unsafe legacy session file: {}", src) + continue dst = self.sessions_dir / src.name if dst.exists(): continue diff --git a/tests/session/test_session_location.py b/tests/session/test_session_location.py index 2fcc4a96e..07e8b48f7 100644 --- a/tests/session/test_session_location.py +++ b/tests/session/test_session_location.py @@ -5,6 +5,8 @@ from __future__ import annotations import json from pathlib import Path +import pytest + from nanobot.session.manager import JsonlSessionStore, SessionManager @@ -101,3 +103,40 @@ def test_legacy_in_workspace_sessions_are_migrated(tmp_path: Path) -> None: # Migration is idempotent: a second construction must not corrupt anything. again = SessionManager(workspace=workspace).get_or_create(key) assert again.messages[-1]["content"] == "migrated-msg" + + +def test_legacy_migration_rejects_symlinked_session_file(tmp_path: Path) -> None: + workspace = tmp_path / "workspace" + old_dir = workspace / "sessions" + old_dir.mkdir(parents=True) + key = "telegram:symlink" + outside = _write_legacy_session(tmp_path / "outside", key, "outside-secret") + source = old_dir / outside.name + try: + source.symlink_to(outside) + except OSError as exc: + pytest.skip(f"file symlink unavailable: {exc}") + + manager = SessionManager(workspace=workspace) + + assert source.is_symlink() + assert not (manager.sessions_dir / source.name).exists() + assert manager.get_or_create(key).messages == [] + + +def test_legacy_migration_rejects_symlinked_sessions_directory(tmp_path: Path) -> None: + workspace = tmp_path / "workspace" + outside = tmp_path / "outside" + key = "telegram:directory-symlink" + outside_file = _write_legacy_session(outside, key, "outside-secret") + workspace.mkdir() + try: + (workspace / "sessions").symlink_to(outside, target_is_directory=True) + except OSError as exc: + pytest.skip(f"directory symlink unavailable: {exc}") + + manager = SessionManager(workspace=workspace) + + assert outside_file.exists() + assert not (manager.sessions_dir / outside_file.name).exists() + assert manager.get_or_create(key).messages == []