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.
Files changed (51) hide show
  1. {ssh_licco-2.5.0/ssh_licco.egg-info → ssh_licco-2.5.2}/PKG-INFO +2 -2
  2. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/README.md +1 -1
  3. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/pyproject.toml +1 -1
  4. {ssh_licco-2.5.0 → ssh_licco-2.5.2/ssh_licco.egg-info}/PKG-INFO +2 -2
  5. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/__init__.py +2 -2
  6. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/security.py +24 -22
  7. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_security.py +21 -7
  8. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/LICENSE +0 -0
  9. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/setup.cfg +0 -0
  10. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/SOURCES.txt +0 -0
  11. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/dependency_links.txt +0 -0
  12. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/entry_points.txt +0 -0
  13. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/requires.txt +0 -0
  14. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_licco.egg-info/top_level.txt +0 -0
  15. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/__main__.py +0 -0
  16. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/audit_logger.py +0 -0
  17. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/batch_executor.py +0 -0
  18. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/cli.py +0 -0
  19. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/__init__.py +0 -0
  20. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/additional_clients.py +0 -0
  21. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/factory.py +0 -0
  22. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/interface.py +0 -0
  23. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/clients/paramiko_client.py +0 -0
  24. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/config_manager.py +0 -0
  25. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/connection_config.py +0 -0
  26. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/connection_pool.py +0 -0
  27. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/exceptions.py +0 -0
  28. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/executor.py +0 -0
  29. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/__init__.py +0 -0
  30. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/connect.py +0 -0
  31. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/context.py +0 -0
  32. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/docker.py +0 -0
  33. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/execute.py +0 -0
  34. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/file_transfer.py +0 -0
  35. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/host.py +0 -0
  36. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/key.py +0 -0
  37. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/process.py +0 -0
  38. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/schemas.py +0 -0
  39. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/session.py +0 -0
  40. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/handlers/utils.py +0 -0
  41. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/key_manager.py +0 -0
  42. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/logging_config.py +0 -0
  43. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/server.py +0 -0
  44. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/service.py +0 -0
  45. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/session_manager.py +0 -0
  46. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/tunnel.py +0 -0
  47. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/ssh_mcp/watchdog.py +0 -0
  48. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_config_manager.py +0 -0
  49. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_key_manager.py +0 -0
  50. {ssh_licco-2.5.0 → ssh_licco-2.5.2}/tests/test_server.py +0 -0
  51. {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.0
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
- | **测试用例** | 418 passed, 0 skipped |
625
+ | **测试用例** | 424 passed, 0 skipped |
626
626
  | **覆盖率** | 16 个源模块全覆盖 |
627
627
  | **测试框架** | pytest + pytest-asyncio |
628
628
 
@@ -589,7 +589,7 @@ ssh-licco 的核心依赖是:
589
589
 
590
590
  | 指标 | 状态 |
591
591
  |------|------|
592
- | **测试用例** | 418 passed, 0 skipped |
592
+ | **测试用例** | 424 passed, 0 skipped |
593
593
  | **覆盖率** | 16 个源模块全覆盖 |
594
594
  | **测试框架** | pytest + pytest-asyncio |
595
595
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssh-licco"
3
- version = "2.5.0"
3
+ version = "2.5.2"
4
4
  description = "SSH Model Context Protocol Server - Enable SSH functionality for AI models"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssh-licco
3
- Version: 2.5.0
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
- | **测试用例** | 418 passed, 0 skipped |
625
+ | **测试用例** | 424 passed, 0 skipped |
626
626
  | **覆盖率** | 16 个源模块全覆盖 |
627
627
  | **测试框架** | pytest + pytest-asyncio |
628
628
 
@@ -1,8 +1,8 @@
1
1
  # SSH-LICCO 版本信息
2
2
  # 所有代码中的版本信息都从这里读取
3
- # 版本号:2.5.0
3
+ # 版本号:2.5.2
4
4
 
5
- __version__ = "2.5.0"
5
+ __version__ = "2.5.2"
6
6
  __author__ = "Li Qi"
7
7
  __email__ = "1985694657@qq.com"
8
8
  __license__ = "MIT"
@@ -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
- - rm -fr <path>
869
+ - rmdir <path>
868
870
  - rm --recursive --force <path>
869
871
 
870
- 仅当检测到递归删除(含 -r/--recursive)且有明确目标时返回 True。
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
- flags.update(part[1:])
901
- elif part == "--recursive":
902
- flags.add("r")
903
- elif part == "--force":
904
- flags.add("f")
905
- elif part == "--dir":
906
- flags.add("d")
907
- elif not part.startswith("--"):
908
- targets.append(part)
909
-
910
- # rmdir 只删除空目录,风险较低,不强制要求备份
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
- return f"""⚠️ 检测到递归删除操作
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 test_parse_deletion_command_detects_recursive_rm(cmd: str, expected_targets: list[str]) -> None:
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 /tmp/single.log",
233
- "rm -f /tmp/single.log",
234
- "rmdir /tmp/empty_dir",
236
+ "rm",
237
+ "rm --help",
238
+ "rm --version",
239
+ "rmdir",
235
240
  "cp -r ./a ./b",
236
241
  "echo hello",
237
242
  ])
238
- def test_parse_deletion_command_ignores_safe_commands(cmd: str) -> None:
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