Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from bundle
Telegram MTProto MCP server with userbot watcher, chat/DM parser and context builders
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
test_file_path_security.py
1import os2from pathlib import Path34import pytest5from mcp import types6from mcp.shared.exceptions import McpError7from mcp.types import ErrorData89os.environ.setdefault("TELEGRAM_API_ID", "12345")10os.environ.setdefault("TELEGRAM_API_HASH", "dummy_hash")1112import main131415class _DummySession:16def __init__(self, roots):17self._roots = roots1819async def list_roots(self):20return types.ListRootsResult(roots=self._roots)212223class _DummyContext:24def __init__(self, roots):25self.session = _DummySession(roots)262728class _FailingSession:29def __init__(self, error):30self._error = error3132async def list_roots(self):33raise self._error343536class _FailingContext:37def __init__(self, error):38self.session = _FailingSession(error)394041class _MissingRootsSession:42pass434445class _MissingRootsContext:46def __init__(self):47self.session = _MissingRootsSession()484950@pytest.mark.asyncio51async def test_readable_relative_path_resolves_inside_first_server_root(tmp_path, monkeypatch):52root = (tmp_path / "root").resolve()53root.mkdir(parents=True)54target = root / "document.txt"55target.write_text("ok", encoding="utf-8")5657monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [root])5859resolved, error = await main._resolve_readable_file_path(60raw_path="document.txt",61ctx=None,62tool_name="send_file",63)6465assert error is None66assert resolved == target.resolve()676869@pytest.mark.asyncio70async def test_readable_path_rejects_traversal(tmp_path, monkeypatch):71root = (tmp_path / "root").resolve()72root.mkdir(parents=True)73monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [root])7475resolved, error = await main._resolve_readable_file_path(76raw_path="../etc/passwd",77ctx=None,78tool_name="send_file",79)8081assert resolved is None82assert error == "Path traversal is not allowed."838485@pytest.mark.asyncio86async def test_readable_path_rejects_outside_root(tmp_path, monkeypatch):87root = (tmp_path / "root").resolve()88outside_root = (tmp_path / "outside").resolve()89root.mkdir(parents=True)90outside_root.mkdir(parents=True)9192outside_file = outside_root / "outside.txt"93outside_file.write_text("no", encoding="utf-8")9495monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [root])9697resolved, error = await main._resolve_readable_file_path(98raw_path=str(outside_file),99ctx=None,100tool_name="send_file",101)102103assert resolved is None104assert error == "Path is outside allowed roots."105106107@pytest.mark.asyncio108async def test_client_roots_replace_server_allowlist(tmp_path, monkeypatch):109server_root = (tmp_path / "server_root").resolve()110client_root = (tmp_path / "client_root").resolve()111server_root.mkdir(parents=True)112client_root.mkdir(parents=True)113114(server_root / "server.txt").write_text("server", encoding="utf-8")115client_file = client_root / "client.txt"116client_file.write_text("client", encoding="utf-8")117118monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [server_root])119ctx = _DummyContext([types.Root(uri=client_root.as_uri())])120121roots = await main._get_effective_allowed_roots(ctx)122assert roots == [client_root]123124resolved, error = await main._resolve_readable_file_path(125raw_path="client.txt",126ctx=ctx,127tool_name="send_file",128)129assert error is None130assert resolved == client_file.resolve()131132133@pytest.mark.asyncio134async def test_empty_client_roots_disable_file_tools(tmp_path, monkeypatch):135server_root = (tmp_path / "server_root").resolve()136server_root.mkdir(parents=True)137138monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [server_root])139ctx = _DummyContext([])140141roots = await main._get_effective_allowed_roots(ctx)142assert roots == []143144resolved, error = await main._resolve_readable_file_path(145raw_path="server.txt",146ctx=ctx,147tool_name="send_file",148)149assert resolved is None150assert error is not None151assert "empty MCP Roots list" in error152assert "deny-all" in error153154155@pytest.mark.asyncio156async def test_mcp_method_not_found_falls_back_to_server_allowlist(tmp_path, monkeypatch):157server_root = (tmp_path / "server_root").resolve()158server_root.mkdir(parents=True)159160monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [server_root])161ctx = _FailingContext(McpError(ErrorData(code=-32601, message="Method not found")))162163roots = await main._get_effective_allowed_roots(ctx)164assert roots == [server_root]165166167@pytest.mark.asyncio168async def test_missing_list_roots_method_falls_back_to_server_allowlist(tmp_path, monkeypatch):169server_root = (tmp_path / "server_root").resolve()170server_root.mkdir(parents=True)171172monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [server_root])173ctx = _MissingRootsContext()174175roots = await main._get_effective_allowed_roots(ctx)176assert roots == [server_root]177178179@pytest.mark.asyncio180async def test_unexpected_roots_error_disables_file_path_tools(tmp_path, monkeypatch):181server_root = (tmp_path / "server_root").resolve()182server_root.mkdir(parents=True)183monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [server_root])184185ctx = _FailingContext(RuntimeError("transport failure"))186roots = await main._get_effective_allowed_roots(ctx)187assert roots == []188189resolved, error = await main._resolve_readable_file_path(190raw_path="anything.txt",191ctx=ctx,192tool_name="send_file",193)194assert resolved is None195assert error is not None196assert "disabled" in error197198199@pytest.mark.asyncio200async def test_writable_default_path_uses_downloads_subdir(tmp_path, monkeypatch):201root = (tmp_path / "root").resolve()202root.mkdir(parents=True)203monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [root])204205resolved, error = await main._resolve_writable_file_path(206raw_path=None,207default_filename="example.bin",208ctx=None,209tool_name="download_media",210)211212assert error is None213assert resolved == (root / "downloads" / "example.bin").resolve()214assert resolved.parent.exists()215216217@pytest.mark.asyncio218async def test_extension_allowlist_is_enforced_for_sticker(tmp_path, monkeypatch):219root = (tmp_path / "root").resolve()220root.mkdir(parents=True)221file_path = root / "sticker.txt"222file_path.write_text("bad", encoding="utf-8")223224monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [root])225226resolved, error = await main._resolve_readable_file_path(227raw_path=str(file_path),228ctx=None,229tool_name="send_sticker",230)231232assert resolved is None233assert error is not None234assert "extension is not allowed" in error235236237@pytest.mark.asyncio238async def test_file_tools_disabled_without_any_roots(monkeypatch):239monkeypatch.setattr(main, "SERVER_ALLOWED_ROOTS", [])240241resolved, error = await main._resolve_readable_file_path(242raw_path="anything.txt",243ctx=None,244tool_name="send_file",245)246247assert resolved is None248assert error is not None249assert "disabled" in error250