ssh-licco 2.5.0__tar.gz → 2.5.2__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.5.0/ssh_licco.egg-info → ssh_licco-2.5.2}/PKG-INFO +2 -2
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/README.md +1 -1
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/pyproject.toml +1 -1
- {ssh_licco-2.5.0 → ssh_licco-2.5.2/ssh_licco.egg-info}/PKG-INFO +2 -2
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/__init__.py +2 -2
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/security.py +24 -22
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_security.py +21 -7
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/LICENSE +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/setup.cfg +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/SOURCES.txt +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/dependency_links.txt +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/entry_points.txt +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/requires.txt +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/top_level.txt +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/__main__.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/audit_logger.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/batch_executor.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/cli.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/__init__.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/additional_clients.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/factory.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/interface.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/paramiko_client.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/config_manager.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/connection_config.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/connection_pool.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/exceptions.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/executor.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/__init__.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/connect.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/context.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/docker.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/execute.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/file_transfer.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/host.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/key.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/process.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/schemas.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/session.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/utils.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/key_manager.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/logging_config.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/server.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/service.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/session_manager.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/tunnel.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/watchdog.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_config_manager.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_key_manager.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_server.py +0 -0
- {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_session_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssh-licco
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: SSH Model Context Protocol Server - Enable SSH functionality for AI models
|
|
5
5
|
Author-email: SSH LICCO Team <contact@ssh-licco.dev>
|
|
6
6
|
License: MIT
|
|
@@ -622,7 +622,7 @@ ssh-licco 的核心依赖是:
|
|
|
622
622
|
|
|
623
623
|
| 指标 | 状态 |
|
|
624
624
|
|------|------|
|
|
625
|
-
| **测试用例** |
|
|
625
|
+
| **测试用例** | 424 passed, 0 skipped |
|
|
626
626
|
| **覆盖率** | 16 个源模块全覆盖 |
|
|
627
627
|
| **测试框架** | pytest + pytest-asyncio |
|
|
628
628
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssh-licco
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: SSH Model Context Protocol Server - Enable SSH functionality for AI models
|
|
5
5
|
Author-email: SSH LICCO Team <contact@ssh-licco.dev>
|
|
6
6
|
License: MIT
|
|
@@ -622,7 +622,7 @@ ssh-licco 的核心依赖是:
|
|
|
622
622
|
|
|
623
623
|
| 指标 | 状态 |
|
|
624
624
|
|------|------|
|
|
625
|
-
| **测试用例** |
|
|
625
|
+
| **测试用例** | 424 passed, 0 skipped |
|
|
626
626
|
| **覆盖率** | 16 个源模块全覆盖 |
|
|
627
627
|
| **测试框架** | pytest + pytest-asyncio |
|
|
628
628
|
|
|
@@ -859,15 +859,17 @@ class CommandValidator:
|
|
|
859
859
|
# -----------------------------------------------------------------------------
|
|
860
860
|
|
|
861
861
|
def parse_deletion_command(command: str) -> tuple[bool, list[str]]:
|
|
862
|
-
"""
|
|
862
|
+
"""解析命令是否为需要备份确认的删除操作,并返回目标路径列表。
|
|
863
863
|
|
|
864
864
|
支持的形式(含 sudo 前缀):
|
|
865
|
+
- rm <path>
|
|
866
|
+
- rm -f <path>
|
|
865
867
|
- rm -rf <path>
|
|
866
868
|
- rm -r <path>
|
|
867
|
-
-
|
|
869
|
+
- rmdir <path>
|
|
868
870
|
- rm --recursive --force <path>
|
|
869
871
|
|
|
870
|
-
|
|
872
|
+
只要有明确删除目标(排除 --help/--version 等无目标用法)就返回 True。
|
|
871
873
|
绝对路径的 rm -rf 会在硬拦截阶段被阻止,因此此处主要针对相对路径或
|
|
872
874
|
已通过安全策略显式允许的删除操作。
|
|
873
875
|
|
|
@@ -875,7 +877,7 @@ def parse_deletion_command(command: str) -> tuple[bool, list[str]]:
|
|
|
875
877
|
command: 原始 shell 命令
|
|
876
878
|
|
|
877
879
|
Returns:
|
|
878
|
-
tuple[bool, list[str]]: (
|
|
880
|
+
tuple[bool, list[str]]: (是否为删除操作, 目标路径列表)
|
|
879
881
|
"""
|
|
880
882
|
try:
|
|
881
883
|
parts = shlex.split(command)
|
|
@@ -892,27 +894,25 @@ def parse_deletion_command(command: str) -> tuple[bool, list[str]]:
|
|
|
892
894
|
return False, []
|
|
893
895
|
|
|
894
896
|
base = parts[0]
|
|
895
|
-
flags: set[str] = set()
|
|
896
897
|
targets: list[str] = []
|
|
897
898
|
|
|
898
899
|
for part in parts[1:]:
|
|
900
|
+
# 忽略帮助/版本信息类选项(无实际删除目标)
|
|
901
|
+
if part in ("--help", "--version", "-V"):
|
|
902
|
+
return False, []
|
|
903
|
+
# 普通短选项(非 -- 长选项)不收集为 target
|
|
899
904
|
if part.startswith("-") and len(part) > 1 and not part.startswith("--"):
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if base == "rmdir":
|
|
912
|
-
return False, []
|
|
913
|
-
|
|
914
|
-
# 仅对递归删除要求备份确认
|
|
915
|
-
if "r" in flags and targets:
|
|
905
|
+
continue
|
|
906
|
+
# 显式终止选项 `--` 本身不是 target
|
|
907
|
+
if part == "--":
|
|
908
|
+
continue
|
|
909
|
+
# 长选项(如 --recursive)不是 target
|
|
910
|
+
if part.startswith("--"):
|
|
911
|
+
continue
|
|
912
|
+
targets.append(part)
|
|
913
|
+
|
|
914
|
+
# 有明确删除目标才触发备份确认
|
|
915
|
+
if targets:
|
|
916
916
|
return True, targets
|
|
917
917
|
|
|
918
918
|
return False, []
|
|
@@ -949,7 +949,9 @@ def generate_backup_command(targets: list[str]) -> str:
|
|
|
949
949
|
def format_backup_prompt(command: str, targets: list[str]) -> str:
|
|
950
950
|
"""生成备份确认提示文案。"""
|
|
951
951
|
targets_str = ", ".join(targets)
|
|
952
|
-
|
|
952
|
+
# rmdir 删除的是目录但非递归;rm -r/rf 属于递归删除。
|
|
953
|
+
operation_type = "目录删除操作" if command.lstrip().startswith("rmdir") else "递归删除操作"
|
|
954
|
+
return f"""⚠️ 检测到{operation_type}
|
|
953
955
|
|
|
954
956
|
📋 将要执行的命令:
|
|
955
957
|
{command}
|
|
@@ -219,9 +219,13 @@ def test_hard_block_patterns_cover_documented_categories() -> None:
|
|
|
219
219
|
("rm --recursive --force ./data", ["./data"]),
|
|
220
220
|
("sudo rm -rf /home/user/tmp", ["/home/user/tmp"]),
|
|
221
221
|
("rm -rf -- ./data", ["./data"]),
|
|
222
|
+
("rm /tmp/single.log", ["/tmp/single.log"]),
|
|
223
|
+
("rm -f /tmp/single.log", ["/tmp/single.log"]),
|
|
224
|
+
("rmdir /tmp/empty_dir", ["/tmp/empty_dir"]),
|
|
225
|
+
("sudo rmdir /tmp/empty_dir", ["/tmp/empty_dir"]),
|
|
222
226
|
])
|
|
223
|
-
def
|
|
224
|
-
"""递归 rm
|
|
227
|
+
def test_parse_deletion_command_detects_deletion(cmd: str, expected_targets: list[str]) -> None:
|
|
228
|
+
"""普通 rm / rmdir / 递归 rm 都应被识别为需要备份确认的删除操作。"""
|
|
225
229
|
from ssh_mcp.security import parse_deletion_command
|
|
226
230
|
is_deletion, targets = parse_deletion_command(cmd)
|
|
227
231
|
assert is_deletion is True
|
|
@@ -229,14 +233,15 @@ def test_parse_deletion_command_detects_recursive_rm(cmd: str, expected_targets:
|
|
|
229
233
|
|
|
230
234
|
|
|
231
235
|
@pytest.mark.parametrize("cmd", [
|
|
232
|
-
"rm
|
|
233
|
-
"rm
|
|
234
|
-
"
|
|
236
|
+
"rm",
|
|
237
|
+
"rm --help",
|
|
238
|
+
"rm --version",
|
|
239
|
+
"rmdir",
|
|
235
240
|
"cp -r ./a ./b",
|
|
236
241
|
"echo hello",
|
|
237
242
|
])
|
|
238
|
-
def
|
|
239
|
-
"""
|
|
243
|
+
def test_parse_deletion_command_ignores_non_deletion_commands(cmd: str) -> None:
|
|
244
|
+
"""无目标、帮助/版本或无关命令不应触发备份确认。"""
|
|
240
245
|
from ssh_mcp.security import parse_deletion_command
|
|
241
246
|
is_deletion, targets = parse_deletion_command(cmd)
|
|
242
247
|
assert is_deletion is False
|
|
@@ -266,3 +271,12 @@ def test_format_backup_prompt_mentions_backup_before_delete() -> None:
|
|
|
266
271
|
assert "backup_before_delete=true" in prompt
|
|
267
272
|
assert "backup_before_delete=false" in prompt
|
|
268
273
|
assert "./data" in prompt
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_format_backup_prompt_for_rmdir() -> None:
|
|
277
|
+
"""rmdir 的提示文案应使用“检测到目录删除操作”。"""
|
|
278
|
+
from ssh_mcp.security import format_backup_prompt
|
|
279
|
+
prompt = format_backup_prompt("rmdir ./empty_dir", ["./empty_dir"])
|
|
280
|
+
assert "检测到目录删除操作" in prompt
|
|
281
|
+
assert "检测到递归删除操作" not in prompt
|
|
282
|
+
assert "backup_before_delete=true" in prompt
|
|
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
|