ssh-licco 2.1.2__tar.gz → 2.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ssh_licco-2.1.2/ssh_licco.egg-info → ssh_licco-2.1.3}/PKG-INFO +1 -1
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/pyproject.toml +1 -1
- {ssh_licco-2.1.2 → ssh_licco-2.1.3/ssh_licco.egg-info}/PKG-INFO +1 -1
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/__init__.py +2 -2
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/file_transfer.py +56 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/LICENSE +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/README.md +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/setup.cfg +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_licco.egg-info/SOURCES.txt +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_licco.egg-info/dependency_links.txt +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_licco.egg-info/entry_points.txt +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_licco.egg-info/requires.txt +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_licco.egg-info/top_level.txt +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/__main__.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/approval.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/audit_logger.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/batch_executor.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/cli.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/clients/__init__.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/clients/additional_clients.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/clients/factory.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/clients/interface.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/clients/paramiko_client.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/config_manager.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/connection_config.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/connection_pool.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/exceptions.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/executor.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/__init__.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/approval.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/connect.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/context.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/docker.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/execute.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/host.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/key.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/process.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/schemas.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/session.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/handlers/utils.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/key_manager.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/logging_config.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/runtime_guard.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/secret_provider.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/security.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/server.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/service.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/session_manager.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/tunnel.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/ssh_mcp/watchdog.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/tests/test_config_manager.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/tests/test_key_manager.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/tests/test_security.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/tests/test_server.py +0 -0
- {ssh_licco-2.1.2 → ssh_licco-2.1.3}/tests/test_session_manager.py +0 -0
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
import shlex
|
|
7
|
+
from pathlib import PurePosixPath
|
|
7
8
|
|
|
8
9
|
from mcp.types import TextContent
|
|
9
10
|
|
|
@@ -12,6 +13,58 @@ from .context import HandlerContext
|
|
|
12
13
|
from .utils import ensure_session
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
# 远程敏感路径黑名单:禁止通过文件传输操作访问或删除
|
|
17
|
+
FORBIDDEN_REMOTE_PATHS = [
|
|
18
|
+
"/etc",
|
|
19
|
+
"/root",
|
|
20
|
+
"/boot",
|
|
21
|
+
"/proc",
|
|
22
|
+
"/sys",
|
|
23
|
+
"/dev",
|
|
24
|
+
"/bin",
|
|
25
|
+
"/sbin",
|
|
26
|
+
"/usr/bin",
|
|
27
|
+
"/usr/sbin",
|
|
28
|
+
"/lib",
|
|
29
|
+
"/lib64",
|
|
30
|
+
"/var/lib/postgresql",
|
|
31
|
+
"/var/lib/mysql",
|
|
32
|
+
"/var/lib/redis",
|
|
33
|
+
"/var/lib/docker",
|
|
34
|
+
"/var/log",
|
|
35
|
+
"/var/spool",
|
|
36
|
+
"/var/mail",
|
|
37
|
+
"/home/*/.ssh",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _validate_remote_path(remote_path: str, operation: str = "access") -> tuple[bool, str]:
|
|
42
|
+
"""校验远程路径是否安全。
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
(is_safe, error_message)
|
|
46
|
+
"""
|
|
47
|
+
if not remote_path or not remote_path.strip():
|
|
48
|
+
return False, "远程路径不能为空"
|
|
49
|
+
|
|
50
|
+
# 路径遍历检查
|
|
51
|
+
normalized = str(PurePosixPath(remote_path)).rstrip("/") or "/"
|
|
52
|
+
if ".." in normalized.split("/"):
|
|
53
|
+
return False, f"路径遍历被阻止:{remote_path}"
|
|
54
|
+
|
|
55
|
+
# 检查是否命中敏感路径黑名单
|
|
56
|
+
path_for_check = normalized.rstrip("/*") or "/"
|
|
57
|
+
for forbidden in FORBIDDEN_REMOTE_PATHS:
|
|
58
|
+
if forbidden.endswith("/*"):
|
|
59
|
+
prefix = forbidden[:-1]
|
|
60
|
+
if path_for_check.startswith(prefix) or path_for_check + "/" == prefix:
|
|
61
|
+
return False, f"禁止{operation}敏感路径:{forbidden}"
|
|
62
|
+
elif path_for_check == forbidden or path_for_check.startswith(forbidden + "/"):
|
|
63
|
+
return False, f"禁止{operation}敏感路径:{forbidden}"
|
|
64
|
+
|
|
65
|
+
return True, ""
|
|
66
|
+
|
|
67
|
+
|
|
15
68
|
async def handle_file_transfer(ctx: HandlerContext, args: dict) -> list[TextContent]:
|
|
16
69
|
"""Transfer files between local and remote, or perform remote file operations."""
|
|
17
70
|
session_id = await ensure_session(ctx, args, handle_connect)
|
|
@@ -51,6 +104,9 @@ async def handle_file_transfer(ctx: HandlerContext, args: dict) -> list[TextCont
|
|
|
51
104
|
elif direction == "delete":
|
|
52
105
|
if not remote_path:
|
|
53
106
|
return [TextContent(type="text", text="delete requires remote_path")]
|
|
107
|
+
safe, err = _validate_remote_path(remote_path, operation="delete")
|
|
108
|
+
if not safe:
|
|
109
|
+
return [TextContent(type="text", text=f"❌ Delete blocked: {err}")]
|
|
54
110
|
result = await session.delete_file(remote_path)
|
|
55
111
|
elif direction == "mkdir":
|
|
56
112
|
if not remote_path:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|