Skip to main content
Glama

SSH MCP 服务器(安全版)

npm 版本 CI/CD 许可证:MIT

这是 zibdie/SSH-MCP-Server 的一个安全分支,具备命令白名单/黑名单过滤、网络设备支持和批量连接管理功能,可通过 MCP(模型上下文协议)实现安全的远程服务器管理。

主要特性

  • 一次性执行:ssh_run 在单次工具调用中完成连接、执行命令和断开连接——无需传递 connectionId

  • 命令白名单/黑名单:控制可执行的命令

  • 危险模式检测:阻止 fork 炸弹、命令注入和破坏性模式

  • 网络设备支持:Cisco、Juniper、MikroTik、FortiGate、Palo Alto、Sophos,支持持久化 shell 会话和自动分页抑制

  • 跳转 Shell 支持:SSH 连接到主机后进入嵌套 CLI(telnet 到主机、FreeSWITCH fs_cli 等)——命令在嵌套 shell 内执行,并提供有序的跳转命令回退列表

  • 批量连接管理:从 CSV/JSON 文件加载数十个连接

  • 环境变量凭据:密码通过 connectionId 自动从环境变量解析——聊天中不出现任何机密信息

  • 多连接执行:可同时在所有或选定的连接上运行命令

  • 连接健康监控:keepalive 跟踪、死连接检测、自动清理

  • 可配置安全策略:通过配置文件或环境变量设置

  • 审计日志:记录所有被阻止的命令尝试

Related MCP server: Tunnel Manager

安装

快速设置(推荐)

# Add to Claude CLI
claude mcp add ssh-mcp-secured npx '@marian-craciunescu/ssh-mcp-server-secured@latest'

手动安装

npm install -g @marian-craciunescu/ssh-mcp-server-secured
{
  "mcpServers": {
    "ssh-mcp-secured": {
      "command": "ssh-mcp-server-secured"
    }
  }
}

使用方法

1. 单个连接

使用 ssh_connect 连接到主机。您只需提供主机、用户名和 connectionId——密码会自动从环境变量中解析:

Connect to host 172.168.0.2 with user admin connectionId=router1

LLM 调用 ssh_connect 时使用:

{
  "host": "172.168.0.2",
  "username": "admin",
  "deviceType": "cisco",
  "connectionId": "router1"
}

工具调用中不包含密码。 服务器会自动从环境变量中查找 ROUTER1_PASSWORD。

凭据解析约定

connectionId 会转换为环境变量前缀:转换为大写,非字母数字字符替换为 _。

connectionId

密码的环境变量

启用密码的环境变量

router1

ROUTER1_PASSWORD

ROUTER1_ENABLE_PASSWORD

my-connection

MY_CONNECTION_PASSWORD

MY_CONNECTION_ENABLE_PASSWORD

dc1.switch.3

DC1_SWITCH_3_PASSWORD

DC1_SWITCH_3_ENABLE_PASSWORD

可选地,如果未提供用户名,也会解析 <PREFIX>_USERNAME。

在 MCP 配置中设置凭据:

{
  "mcpServers": {
    "ssh-mcp-secured": {
      "command": "ssh-mcp-server-secured",
      "env": {
        "SSH_FILTER_MODE": "blacklist",
        "ROUTER1_PASSWORD": "admin123",
        "ROUTER1_ENABLE_PASSWORD": "enable123",
        "SERVER1_PASSWORD": "rootpass",
        "SERVER1_USERNAME": "root"
      }
    }
  }
}

凭据存放在 MCP 配置中(或通过 CI/CD、vault 等注入),绝不会出现在聊天或工具调用中。如果在工具调用中显式提供了密码,则优先于环境变量。

旧设备的 SSH 选项

当连接到需要非默认算法的旧设备时(相当于 ssh -o),请使用 sshOptions 参数:

用自然语言描述:

以用户身份连接到 10.0.0.1 端口 2222,connectionId 为 old-switch,使用 KexAlgorithms +diffie-hellman-group-exchange-sha1 和 HostKeyAlgorithms +ssh-rsa

{
  "host": "10.0.0.1",
  "port": 2222,
  "username": "admin",
  "connectionId": "old-switch",
  "sshOptions": {
    "KexAlgorithms": "+diffie-hellman-group-exchange-sha1",
    "HostKeyAlgorithms": "+ssh-rsa"
  }
}

这相当于:

ssh -p 2222 admin@10.0.0.1 -o KexAlgorithms=+diffie-hellman-group-exchange-sha1 -o HostKeyAlgorithms=+ssh-rsa

在值前加 + 表示追加到 ssh2 默认值。不加 + 时,该值将完全替换默认值。

选项

SSH2 等效项

使用场景

KexAlgorithms

algorithms.kex

旧式密钥交换(例如 diffie-hellman-group1-sha1)

HostKeyAlgorithms

algorithms.serverHostKey

旧式主机密钥(例如 ssh-rsa、ssh-dss)

Ciphers

algorithms.cipher

旧式加密算法(例如 aes128-cbc)

MACs

algorithms.hmac

旧式 MAC(例如 hmac-sha1)

sshOptions 支持在 ssh_connect、ssh_connect_with_jump_command 以及通过 ssh_load_connections 加载的 JSON 文件中使用。

键盘交互认证会自动启用(tryKeyboard: true)。拒绝标准密码认证并要求 keyboard-interactive 的旧设备无需额外配置即可正常工作。

2. 从文件批量连接

使用 ssh_load_connections 从 CSV 或 JSON 文件加载多个连接。密码使用相同的 connectionId 约定从环境变量解析:

CSV 格式(connections.csv):

host,username,port,deviceType,connectionId
172.168.0.2,admin,22,cisco,router1
10.1.2.15,noc,22,cisco,router2
192.168.1.1,root,22,linux,server1

文件中不包含密码。服务器会从环境变量中解析 ROUTER1_PASSWORD、ROUTER2_PASSWORD、SERVER1_PASSWORD。

注意:CSV 无法承载对象,因此旧设备的 SSH 选项必须通过单独的环境变量或 JSON 文件设置。

JSON 格式(connections.json):

[
  {
    "host": "172.168.0.2",
    "username": "admin",
    "deviceType": "cisco",
    "connectionId": "router1"
  },
  {
    "host": "10.1.2.15",
    "username": "noc",
    "deviceType": "cisco",
    "connectionId": "router2",
    "sshOptions": {
      "KexAlgorithms": "+diffie-hellman-group-exchange-sha1",
      "HostKeyAlgorithms": "+ssh-rsa"
    }
  }
]

配置文件: 定义可复用的连接配置文件,用于连接具有相似设置的同类设备(例如所有 Cisco 交换机)。 配置文件可以包含旧设备的默认 SSH 选项,这样您就不必在每个连接中重复设置。

解析优先级: 显式参数 > 配置文件环境变量 > connectionId 环境变量

export PROFILE_CISCO_USER=admin
export PROFILE_CISCO_PASSWORD=secret123
export PROFILE_CISCO_DEVICE_TYPE=cisco
export PROFILE_CISCO_PORT=2222
export PROFILE_CISCO_SSH_OPTIONS='{"KexAlgorithms":"+diffie-hellman-group-exchange-sha1","HostKeyAlgorithms":"+ssh-rsa"}'

以下是加载 CSV/JSON 连接时配置文件环境变量如何解析的示例。PROFILE_CISCO_SSH_OPTIONS 的值会解析为 JSON,并应用于所有 deviceType 为 cisco 的连接。

环境变量示例

字段

值

PROFILE_CISCO_USER

username

admin

PROFILE_CISCO_PASSWORD

password

secret123

PROFILE_CISCO_DEVICE_TYPE

deviceType

cisco

PROFILE_CISCO_SSH_OPTIONS

sshOptions(解析为 JSON)

{"KexAlgorithms":"+diffie-hellman-group-exchange-sha1","HostKeyAlgorithms":"+ssh-rsa"}

PROFILE_CISCO_JUMP_COMMAND

jumpCommand

telnet lh

PROFILE_CISCO_PRESET

preset

topex

PROFILE_CISCO_PORT

port

2222

PROFILE_CISCO_WHITELIST

按配置文件划分的命令白名单(逗号分隔或 JSON 数组)

show ospf neigh,show version

PROFILE_CISCO_BLACKLIST

按配置文件划分的命令黑名单(逗号分隔或 JSON 数组)

show running config,conf t

PROFILE_CISCO_DISABLE_PAGER

按配置文件划分的分页开关(true/false)

false

ssh_connect host=10.0.0.1 profile=CISCO connectionId=SWITCH1"

按配置文件划分的命令过滤

除了全局的 SSH_WHITELIST / SSH_BLACKLIST 之外,每个配置文件还可以通过 PROFILE_<NAME>_WHITELIST 和 PROFILE_<NAME>_BLACKLIST 携带自己的命令过滤器。这些过滤器在执行时叠加在全局过滤器之上,适用于使用该配置文件打开的任何连接:

  • 配置文件黑名单始终阻止——即使全局过滤器允许的命令也会被阻止(例如阻止 show running config)。

  • 配置文件白名单重新允许特定命令,并且当存在时具有权威性:未列出的任何内容都会被阻止(例如允许 show ospf neigh,而黑名单仍然阻止其余命令)。

  • 在直接冲突时,黑名单优先。

export PROFILE_ROUTERS_BLACKLIST="show running config,conf t,configure terminal"
export PROFILE_ROUTERS_WHITELIST="show ospf neigh,show version,show ip interface brief"
ssh_connect host=10.0.0.1 profile=ROUTERS connectionId=router1
# show ospf neigh        → allowed (profile whitelist)
# show running config    → blocked (profile blacklist)

PROFILE_<NAME>_DISABLE_PAGER=false 会关闭使用该配置文件的连接的分页抑制功能,覆盖全局的 SSH_DISABLE_PAGER 默认值。

用法:

Load connections from /path/to/connections.csv and connect to all

注意: 如果您愿意,仍然可以直接在 CSV/JSON 中提供密码——环境变量解析仅在密码字段缺失或为空时生效。

3. 网络设备类型

服务器支持不同的设备类型,并采用相应的连接处理方式:

设备类型

行为

使用场景

linux

标准 SSH exec 模式(默认)

Linux/Unix 服务器

cisco

持久化 shell,支持启用模式

Cisco IOS/IOS-XE 路由器和交换机

cisco_xe

持久化 shell(terminal length 0)

Cisco IOS-XE

cisco_xr

持久化 shell(terminal length 0)

Cisco IOS-XR

cisco_asa

持久化 shell(terminal length 0)

Cisco ASA 防火墙

cisco_nexus

持久化 shell(terminal length 0)

Cisco Nexus(NX-OS)

juniper

持久化 shell(set cli screen-length 0)

Juniper JunOS 设备

mikrotik

持久化 shell

MikroTik RouterOS

fortinet

持久化 shell(config system console / set output standard)

FortiGate / FortiOS 防火墙

paloalto

持久化 shell(set cli pager off)

Palo Alto PAN-OS 防火墙

sophos

持久化 shell(运行时自动处理分页)

Sophos XG/XGS(SFOS)防火墙

network

通用持久化 shell

其他网络设备

jump_shell

持久化 shell + 嵌套 CLI

由 ssh_connect_with_jump_command 内部使用

网络设备使用分配了 PTY 的持久化 shell 会话,而不是标准的 exec(),因为许多网络操作系统会在每次 exec 命令后关闭 SSH 通道。

4. 一次性命令(ssh_run)

ssh_connect + ssh_execute + ssh_disconnect 需要三次工具调用,而且中间两次要求模型逐字复制生成的 connectionId。ssh_run 将其合并为一次调用:

{
  "host": "10.1.2.15",
  "profile": "ROUTERS",
  "command": "show version"
}

连接、运行命令并关闭连接。返回命令输出——无需跟踪 connectionId。

失败时连接保持打开,以便您可以重试其他命令。结果是一个结构化对象(同时以 JSON 文本和 structuredContent 形式返回):

{
  "status": "error",
  "connectionId": "10_1_2_15_2026_08_12_sessionid_a1b2c3",
  "command": "show bogus",
  "error": "Command exited with code 2",
  "exitCode": 2,
  "output": "% Invalid input detected",
  "retry": "The SSH connection is still open. Call ssh_execute with this connectionId to run a different command, then ssh_disconnect when finished."
}

使用该 connectionId 重试 ssh_execute,然后执行 ssh_disconnect。被遗弃的连接由 SSH_IDLE_TIMEOUT(默认 120 秒)回收。

配置文件、白名单/黑名单、主机过滤、审计日志、分页器处理以及大输出卸载的行为均与 ssh_connect + ssh_execute 完全一致。被过滤器阻止的命令会在任何 SSH 会话建立之前被拒绝。

成功定义为:命令已执行且其退出码为 0 或不存在。持久化 shell 路径上的网络设备不报告退出码,因此除非执行本身失败,否则这些命令视为成功。在 Linux 上,非零退出码计为失败并保持连接打开。

带跳转的嵌套 CLI(ssh_run_with_jump)

与单次调用流程相同,但首先进入嵌套 CLI。jumpCommands 是一个按顺序尝试的列表,直到其中一个到达嵌套提示符:

{
  "host": "10.0.0.1",
  "username": "admin",
  "preset": "topex",
  "jumpCommands": ["telnet lh", "telnet 127.0.0.1"],
  "command": "view portsoncard *"
}

如果 telnet lh 未能到达提示符,则尝试 telnet 127.0.0.1。每次尝试都是一个新的连接,因此失败尝试产生的半开 telnet 不会破坏下一次尝试。如果所有候选都失败,错误信息会列出每个候选的返回结果。

所有候选共享同一个 jumpPromptPattern(直接提供或通过 preset 提供)。当候选需要不同的提示符模式时,请改用 ssh_connect_with_jump_command。当省略 jumpCommands 时,PROFILE_<NAME>_JUMP_COMMAND 提供单个候选。

5. 在多个连接上执行

使用 ssh_execute_on_multiple 在特定连接上运行命令:

{
  "command": "show version",
  "connectionIds": ["router1", "router2", "switch1"]
}

或者在所有连接上运行:

{
  "command": "show ip interface brief",
  "connectionIds": ["*"]
}

6. 跳转 Shell(通过 SSH 嵌套 CLI)

当您需要先 SSH 登录主机,然后在执行命令之前进入嵌套交互式 shell 时,请使用 ssh_connect_with_jump_command。这适用于以下场景:

  • 从 SSH 跳转主机 Telnet 到 Topex VoIP 网关

  • 在远程服务器上使用 FreeSWITCH fs_cli

  • 任何需要在 SSH 之后进行交互式会话的 CLI

工作原理:

SSH → open shell → send jump command (e.g. "telnet lh") → wait for nested prompt (e.g. "topexsw>") → ready

该 connectionId 上的所有后续 ssh_execute 命令都在嵌套 shell 内运行。

Topex 网关示例(使用预设):

{
  "host": "10.0.0.1",
  "username": "admin",
  "connectionId": "topex1",
  "preset": "topex",
  "jumpCommand": "telnet lh"
}

topex 预设自动填充 jumpPromptPattern: "topexsw>\\s*$" 和 jumpExitCommand: "quit"。您只需提供 jumpCommand。

然后在 Topex CLI 内执行命令:

{
  "command": "view portsoncard *",
  "connectionId": "topex1"
}

FreeSWITCH 示例(预设填充所有内容):

{
  "host": "10.0.0.5",
  "username": "root",
  "connectionId": "fs1",
  "preset": "freeswitch"
}

freeswitch 预设自动填充 jumpCommand: "fs_cli"、jumpPromptPattern: "freeswitch@...>" 和 jumpExitCommand: "/exit"。然后:

{
  "command": "sofia status",
  "connectionId": "fs1"
}

完全自定义(无预设):

{
  "host": "10.0.0.1",
  "username": "admin",
  "connectionId": "custom1",
  "jumpCommand": "telnet 192.168.1.100",
  "jumpPromptPattern": ">\\s*$",
  "jumpExitCommand": "quit",
  "jumpReadyTimeout": 8000
}

内置预设:

预设

jumpCommand

提示符模式

退出命令

freeswitch

fs_cli

freeswitch@...>

/exit

topex

(用户提供)

topexsw>

quit

预设可以被覆盖——任何显式提供的参数优先。

Shell 恢复: 如果 shell 断开,ssh_execute 会自动重新打开 shell 并重新进入跳转 shell。

断开连接: ssh_disconnect 会在关闭 SSH 连接之前优雅地向嵌套 CLI 发送退出命令。

7. 日志记录

通过环境变量设置日志级别:

变量

值

默认值

SSH_LOG_LEVEL

DEBUG、INFO、WARN、ERROR

INFO

SSH_LOG_FILE

日志文件路径

(无)

日志格式:

[2026-01-22T20:26:02.044Z] [INFO ] ✓ SSH connection established to 172.168.0.2:22
[2026-01-22T20:26:02.046Z] [DEBUG] ♥ Keepalive #1 sent to 172.168.0.2 | {"uptime":"10s"}
[2026-01-22T20:26:12.047Z] [WARN ] ⚠ CONNECTION CLOSED BY REMOTE HOST: router1

配置

环境变量

变量

值

默认值

描述

SSH_FILTER_MODE

whitelist、blacklist、disabled

blacklist

命令过滤模式

SSH_ALLOW_SUDO

true、false

true

允许 sudo 命令

SSH_LOG_BLOCKED

true、false

true

将被阻止的命令记录到 stderr

SSH_MCP_CONFIG

文件路径

-

配置文件 JSON 路径

SSH_WHITELIST

逗号分隔或 JSON

-

覆盖白名单命令

SSH_BLACKLIST

逗号分隔或 JSON

-

覆盖黑名单命令

SSH_DANGEROUS_PATTERNS

JSON 数组

-

覆盖危险正则模式

SSH_LOG_LEVEL

DEBUG、INFO、WARN、ERROR

INFO

日志级别

SSH_LOG_FILE

路径

-

日志文件

SSH_HOST_FILTER_MODE

whitelist、blacklist、disabled

disabled

主机过滤模式

SSH_HOST_WHITELIST

逗号分隔的 IP

-

允许的主机 IP 白名单

SSH_HOST_BLACKLIST

逗号分隔的 IP

-

禁止的主机 IP 黑名单

SSH_IDLE_TIMEOUT

秒

120

空闲连接超时

SSH_FAILED_CONNECTIONS_LOG

文件路径

./ssh-failed-connections.json

记录失败连接尝试的 JSON 文件

SSH_AUDIT_ENABLED

true、false

true

将每次命令会话审计(命令 + 完整输出)写入 JSONL

SSH_AUDIT_DIR

路径

./audit

每日 audit_YYYY-MM-DD.jsonl 文件的目录

SSH_ENABLE_LARGE_OUTPUT

true、false

false

将过大的命令输出卸载到上传端点,并返回 URI 而非内联文本

SSH_MAX_OUTPUT_LENGTH

整数(字符)

10000

超过此大小的输出将被卸载

SSH_FILE_UPLOAD_ENDPOINT

URL

-

大输出 POST 目标。接收 {content, filename},必须返回 {file_id, artifact_uri}

SSH_DISABLE_PAGER

true、false

true

在 shell 和 exec 中抑制交互式分页器(less/---(more)---)

SSH_DISABLE_PAGER_CMD_<DEVICETYPE>

字符串

按设备类型默认

覆盖特定设备类型(如 SSH_DISABLE_PAGER_CMD_CISCO)的禁用分页器命令

SSH_PAGER_REGEX

正则字符串

内置

覆盖用于检测分页器提示符的模式

SSH_PAGER_ADVANCE_KEY

字符串

" "(空格)

发送以翻到下一页分页器内容的按键

SSH_MAX_PAGER_PAGES

整数

1000

每条命令自动翻页的安全上限

任何遵循 <CONNECTIONID>_PASSWORD 约定的额外环境变量都会自动用于凭据解析(参见 凭据解析约定)。

MCP 配置示例

主机白名单/黑名单:

黑名单模式及自定义被阻止命令:

{
  "ssh_mcp": {
    "command": "ssh-mcp-server-secured",
    "args": [],
    "env": {
      "SSH_FILTER_MODE": "blacklist",
      "SSH_ALLOW_SUDO": "true",
      "SSH_LOG_BLOCKED": "true",
      "SSH_BLACKLIST": "rm,rmdir,mkfs,fdisk,shutdown,reboot,halt,poweroff,passwd,useradd,userdel,iptables,crontab,conf t,configure terminal"
    }
  }
}

白名单模式(严格——仅允许特定命令):

{
  "ssh_mcp": {
    "command": "ssh-mcp-server-secured",
    "args": [],
    "env": {
      "SSH_FILTER_MODE": "whitelist",
      "SSH_ALLOW_SUDO": "false",
      "SSH_LOG_BLOCKED": "true",
      "SSH_WHITELIST": "ls,cat,grep,tail,head,df,du,free,uptime,ps,systemctl,journalctl,docker,kubectl,ping,curl,dig,ss,netstat,show,display"
    }
  }
}

带凭据环境变量的网络操作:

{
  "ssh_mcp": {
    "command": "ssh-mcp-server-secured",
    "args": [],
    "env": {
      "SSH_FILTER_MODE": "blacklist",
      "SSH_ALLOW_SUDO": "true",
      "SSH_LOG_LEVEL": "DEBUG",
      "SSH_BLACKLIST": "conf t,configure terminal,rm,shutdown,reboot",
      "ROUTER1_PASSWORD": "admin123",
      "ROUTER1_ENABLE_PASSWORD": "enable123",
      "ROUTER2_PASSWORD": "pass123",
      "SERVER1_PASSWORD": "pass1234"
    }
  }
}

现在在聊天中您只需说 connect to 172.168.0.2 as admin connectionId=router1——无需暴露密码。

通过 npx(无需全局安装):

{
  "ssh_mcp": {
    "command": "npx",
    "args": ["@marian-craciunescu/ssh-mcp-server-secured"],
    "env": {
      "SSH_FILTER_MODE": "blacklist",
      "SSH_ALLOW_SUDO": "true"
    }
  }
}

配置文件

创建 config.json 或 ssh-mcp-config.json:

{
  "commandFilter": {
    "mode": "whitelist",
    "allowSudo": false,
    "logBlocked": true,
    "whitelist": [
      "ls", "cat", "grep", "df", "ps", "systemctl", "docker", "show", "ping"
    ],
    "blacklist": [
      "rm", "shutdown", "reboot", "passwd", "conf t", "configure terminal"
    ],
    "dangerousPatterns": [
      ";\\s*rm\\s+-rf",
      "curl.*\\|\\s*bash"
    ]
  }
}

过滤模式

黑名单模式(默认)

黑名单中的命令被阻止。其他所有命令均被允许。支持多词条目,如 configure terminal 和 conf t。

✓ ls -la
✓ docker ps
✓ show ip interface brief
✗ rm -rf /tmp/files       → Blocked: 'rm' is in blacklist
✗ configure terminal      → Blocked: 'configure terminal' is in blacklist
✗ shutdown now            → Blocked: 'shutdown' is in blacklist

白名单模式

仅允许白名单中的命令。其他所有命令均被阻止。

✓ ls -la                  → Allowed: 'ls' is whitelisted
✓ show version            → Allowed: 'show' is whitelisted
✗ vim /etc/hosts          → Blocked: 'vim' not in whitelist
✗ make install            → Blocked: 'make' not in whitelist

混合模式

两个列表同时生效,过滤器采用默认拒绝策略:命令必须匹配白名单条目才能运行。发生冲突时,最长的匹配条目胜出,无论它来自哪个列表。这就是为什么您可以允许一个宽泛的前缀、从中剔除危险的子集、然后再允许一个更窄的例外。

匹配基于前缀:当命令等于条目,或以条目开头后跟空格、制表符或换行符时,即视为匹配。比较时会将内容转为小写并去除首尾空白。

SSH_FILTER_MODE=mixed
SSH_WHITELIST=show, show running-config interface, show running-config | include, ping -c , ls -lha, terminal length 0
SSH_BLACKLIST=show running-config, conf t, configure terminal, reload, rm, shutdown, ping

最终决策:

✓ show version                            → 'show' (4) beats nothing
✓ show interfaces terse                   → 'show' (4) beats nothing
✗ show running-config                     → 'show running-config' (19) beats 'show' (4)
✓ show running-config interface Gi0/1     → 'show running-config interface' (29) beats 'show running-config' (19)
✓ show running-config | include hostname  → 'show running-config | include' (29) beats 'show running-config' (19)
✓ ping -c 4 8.8.8.8                       → 'ping -c' (7) beats 'ping' (4)
✗ ping 8.8.8.8                            → only 'ping' (4) matches, and it is blacklisted
✓ terminal length 0                       → whitelisted, so the server can disable its own pager
✓ ls -lha                                 → exact whitelist entry
✗ ls -la                                  → matches NEITHER list → blocked by deny-by-default
✗ reload                                  → blacklisted, no whitelist match
✗ rm -rf /tmp/x                           → 'rm' (2) blacklisted, no whitelist match

有两点需要了解:

  • ls -lha 被允许,但 ls -la 不被允许。 白名单条目是字面前缀,而非模式。在混合模式下,任何您未显式允许的内容都会被阻止,因此请列出您打算运行的确切命令形式。

  • 完全相同时白名单胜出。 如果同一个字符串同时出现在两个列表中,该命令被允许。

请将您的禁用分页器命令(terminal length 0、set cli screen-length 0)包含在白名单中。服务器在打开 shell 时会自行发出这些命令,而混合模式否则会阻止它们。

与黑名单和白名单模式不同,混合模式不会检查管道或命令链的各个分段——它只匹配完整的命令字符串。混合模式下的分段级保护来自危险模式列表,该列表首先运行且无法被覆盖:

✗ show version | rm -rf /   → Blocked: dangerous pattern /\|\s*rm/i

禁用模式

不进行命令过滤(请谨慎使用)。

命令验证顺序

  1. 检查过滤是否已禁用

  2. 检查 sudo 权限

  3. 检查危险模式(正则表达式)——始终优先,任何白名单都无法覆盖

  4. 在 mixed 模式下:对完整命令进行白名单和黑名单的最长匹配;若两者均不匹配则默认拒绝。在此结束。

  5. 对完整命令进行黑名单检查(支持多词)

  6. 从管道/命令链中提取基础命令

  7. 对每个基础命令进行黑名单/白名单检查

  8. 在全局结果之上应用按配置文件(按连接)的白名单/黑名单

使用 ssh_get_command_filter 查看当前生效的规则,并查询特定命令是否会被允许或阻止。

稳定的连接 ID

如果你没有向 ssh_connect 传递 connectionId(或传递 default),服务器会生成一个稳定的、结构化的 ID,并在连接响应中返回它:

<IP>_YYYY_MM_DD_sessionid_<6 random chars>

示例:10_0_0_1_2026_06_08_sessionid_a1b9f3

IP 中的点被替换为 _,因此该 ID 可以安全地用作环境变量前缀(用于 <PREFIX>_PASSWORD 解析)和文件名。请捕获返回的 connectionId,并在后续的 ssh_execute / ssh_disconnect 调用中复用。

会话审计(命令 + 输出)

每条已执行命令及其输出都会作为一行 JSONL 写入按日生成的文件中,与服务器诊断日志(SSH_LOG_FILE)分开:

<SSH_AUDIT_DIR>/audit_YYYY-MM-DD.jsonl

每条记录:

{"timestamp":"2026-06-08T11:07:12.569Z","connectionId":"10_0_0_1_2026_06_08_sessionid_a1b9f3","host":"10.0.0.1","command":"show version","exitCode":0,"output":"..."}

使用 SSH_AUDIT_ENABLED=false 禁用。

大输出卸载

当命令输出超过 SSH_MAX_OUTPUT_LENGTH 且 SSH_ENABLE_LARGE_OUTPUT=true 时,完整输出会被 POST 到 SSH_FILE_UPLOAD_ENDPOINT,调用方会收到一个包含返回的 artifact_uri 和 file_id 以及一小段预览的简短摘要——这样庞大的 show tech-support 输出就不会淹没模型上下文。端点接收 { "content": "...", "filename": "..." },必须返回 { "file_id": "...", "artifact_uri": "..." }。如果端点未设置或上传失败,输出将以内联方式返回作为回退。

分页器处理

交互式分页器(Linux less、Cisco/Juniper ---(more)---)否则会阻塞命令直到超时。服务器通过两种方式处理:

  • 预防——在打开 shell 时发送适合设备的禁用分页器命令(Cisco 使用 terminal length 0,Juniper 使用 set cli screen-length 0),在 Linux exec 时设置 SYSTEMD_PAGER=、PAGER=cat、GIT_PAGER=cat。

  • 检测——如果分页器提示符仍然出现,则自动前进(发送空格,上限由 SSH_MAX_PAGER_PAGES 控制)用于网络 shell,或发送 q 退出交互式 Linux 分页器,然后从输出中剥离提示符痕迹。

使用 SSH_DISABLE_PAGER=false 全局切换,使用 PROFILE_<NAME>_DISABLE_PAGER=false 按配置文件切换,使用 SSH_DISABLE_PAGER_CMD_<DEVICETYPE> 按设备类型覆盖命令,使用 SSH_PAGER_REGEX / SSH_PAGER_ADVANCE_KEY 覆盖检测。

危险模式

以下模式始终被阻止,无论过滤模式如何:

模式

示例

风险

Fork 炸弹

:(){ :|:& };:

系统崩溃

管道 rm

find . | rm

数据丢失

链式 rm

ls && rm -rf /

数据丢失

设备重定向

> /dev/sda

磁盘损坏

系统配置覆盖

> /etc/passwd

系统受损

远程代码执行

curl | bash

任意代码执行

递归 chmod 777

chmod -R 777 /

安全受损

可用工具

一次性(推荐)

工具

描述

ssh_run

连接、运行一条命令并在成功后关闭连接——一次调用,无需跟踪 connectionId。失败时连接保持打开,并返回其 connectionId,以便你可以使用 ssh_execute 重试不同的命令。必需:host、command。

ssh_run_with_jump

与 ssh_run 相同,但先进入嵌套 CLI。将 jumpCommands 作为列表传入,并按顺序尝试,直到其中一个到达嵌套提示符。必需:host、command。

连接管理

工具

描述

ssh_connect

打开持久连接并返回 connectionId。密码自动从 <CONNECTIONID>_PASSWORD 环境变量解析;支持 sshOptions 用于旧算法协商。

ssh_connect_with_jump_command

SSH 进入主机,然后通过单个跳转命令进入嵌套 CLI(telnet、fs_cli 等)。支持预设。当每个候选需要自己的提示符模式时使用此工具。

ssh_load_connections

从 CSV/JSON 文件加载连接(凭据按 connectionId 从环境变量解析)。

ssh_disconnect

断开一个连接。

ssh_disconnect_all

断开所有连接。

执行

工具

描述

ssh_execute

在现有连接上运行命令。必需:command、connectionId。

ssh_execute_on_multiple

在选定的连接上运行命令(["*"] 或 [] = 全部)。按顺序运行。

状态与内省

工具

描述

ssh_get_command_filter

显示适用于连接的命令过滤器(白名单/黑名单,全局 + 按配置文件,含优先级规则)和主机过滤器(允许/阻止的主机);可选地检查特定命令是否会被允许。

ssh_list_connections

列出活动连接及其状态。

ssh_check_connections

对所有连接进行健康检查(死套接字检测、shell 状态)。

ssh_failed_connections

列出最近的失败连接尝试(来自 failed-connections JSONL 日志)。

文件传输(SFTP)

工具

描述

ssh_upload_file

通过 SFTP 上传文件。

ssh_download_file

通过 SFTP 下载文件。

ssh_list_files

通过 SFTP 列出远程目录。

示例工作流

单条命令(一次调用)

→ ssh_run {
    host: "172.168.0.2",
    profile: "ROUTERS",
    command: "show version"
  }
  (connects, runs, closes; returns the output)

嵌套 CLI 中的单条命令(一次调用)

→ ssh_run_with_jump {
    host: "10.0.0.1",
    username: "admin",
    preset: "topex",
    jumpCommands: ["telnet lh", "telnet 127.0.0.1"],
    command: "view portsoncard *"
  }

失败后重试

1. → ssh_run { host: "172.168.0.2", profile: "ROUTERS", command: "show bogus" }
   ← { status: "error", connectionId: "172_168_0_2_..._sessionid_a1b2c3", exitCode: 2, ... }
     (connection left open)

2. → ssh_execute {
       command: "show interfaces terse",
       connectionId: "172_168_0_2_..._sessionid_a1b2c3"
     }

3. → ssh_disconnect { connectionId: "172_168_0_2_..._sessionid_a1b2c3" }

批量操作(持久连接)

1. Load connections from CSV (passwords auto-resolved from env vars)
   → ssh_load_connections { filePath: "devices.csv", connectAll: true }
   (ROUTER1_PASSWORD, ROUTER2_PASSWORD resolved automatically)

2. Execute show commands on all devices
   → ssh_execute_on_multiple {
       command: "show ip interface brief",
       connectionIds: ["*"]
     }

3. Execute a command on one specific router
   → ssh_execute {
       command: "show running-config | include hostname",
       connectionId: "router1"
     }

4. Check connection health
   → ssh_check_connections {}

5. Inspect why a command was blocked
   → ssh_get_command_filter {
       connectionId: "router1",
       command: "configure terminal"
     }

6. Disconnect all
   → ssh_disconnect_all {}

架构说明

Shell 缓冲区管理

每条命令执行前都会清除缓冲区。稳定性检测使用缓冲区在 3 × 500ms 内保持不变 = 命令完成。密码提示符在缓冲区最后 200 个字符中检测。

保活系统

SSH2 每 10 秒发送一次保活(keepaliveInterval: 10000)。3 次保活失败后,连接自动关闭(keepaliveCountMax: 3)。自定义间隔记录保活计数用于调试。

连接健康监控

服务器检测死连接(套接字已销毁)、跟踪网络设备的 shell 状态、自动清理死连接,并在网络设备的 shell 已关闭时尝试重新打开 shell。

跳转 Shell

当调用 ssh_connect_with_jump_command 时,服务器:(1) 打开 SSH 连接,(2) 打开 PTY shell,(3) 发送跳转命令(例如 telnet lh),(4) 每 300ms 轮询 shell 缓冲区以匹配预期的提示符正则表达式,(5) 将连接标记为 jump_shell 并设置 jumpShellActive: true。断开连接时,在关闭 SSH 会话前发送嵌套 CLI 退出命令。在 shell 恢复时,自动重新发送跳转命令。

环境变量凭据解析

创建连接时(通过 ssh_connect 或 ssh_load_connections),如果未提供密码,服务器会自动从环境变量中查找 <PREFIX>_PASSWORD,其中 <PREFIX> 是 connectionId 的大写形式,非字母数字字符替换为 _。相同的约定适用于 _ENABLE_PASSWORD 和 _USERNAME。显式提供的值始终优先。

与原版的比较

功能

zibdie/SSH-MCP-Server

本分支

基础 SSH/SFTP

✓

✓

命令白名单

✗

✓

命令黑名单

✗

✓

多词黑名单条目

✗

✓

危险模式检测

✗

✓

审计日志

✗

✓

命令验证工具

✗

✓

配置文件支持

✗

✓

网络设备类型(Cisco、Juniper、MikroTik)

✗

✓

Cisco enable 模式

✗

✓

跳转 shell(通过 SSH 的嵌套 CLI)

✗

✓

从 CSV/JSON 批量连接

✗

✓

多连接执行

✗

✓

环境变量凭据

✗

✓

连接健康监控

✗

✓

Keepalive 跟踪

✗

✓

host/hostname 兼容性

✗

✓

开发

# Clone
git clone https://github.com/marian-craciunescu/ssh-mcp-server-secured.git
cd ssh-mcp-server-secured

# Install dependencies
npm install

# Run in development mode
npm run dev

# Test with MCP Inspector
npx @modelcontextprotocol/inspector node index.js

安全考虑

  • 默认采用黑名单模式 — 在保持灵活性的同时提供保护

  • 始终检查危险模式 — 即使在禁用模式下也会检查

  • 默认启用审计日志 — 跟踪被阻止的尝试

  • 可限制 sudo — 在高安全环境中设置 SSH_ALLOW_SUDO=false

  • 凭据隔离 — 密码通过 connectionId 从环境变量解析,绝不会在聊天中键入或在工具调用中可见

许可证

MIT — 参见 LICENSE 文件

致谢

支持

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A secure remote server management tool based on MCP protocol, supporting SSH connections, command execution, and SFTP file transfers.
    20
    12 npm
    6
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    An MCP server for SSH/SCP operations with passwordless authentication, enabling remote command execution, file transfer, and session management.
    19
    16 npm
    MIT