Skip to main content
Glama

SSH MCP Server (Secured)

npm version CI/CD License: MIT

zibdie/SSH-MCP-Server의 보안 강화 포크로, 명령어 화이트리스트/블랙리스트 필터링, 네트워크 장치 지원, 대량 연결 관리를 통해 MCP(Model Context Protocol)로 안전하게 원격 서버를 관리할 수 있습니다.

주요 기능

  • 원샷 실행: ssh_run은 단일 도구 호출로 연결, 명령 실행, 연결 종료를 모두 처리합니다 — 전달할 connectionId가 없습니다

  • 명령어 화이트리스트/블랙리스트: 실행할 수 있는 명령어를 제어합니다

  • 위험 패턴 탐지: 포크 폭탄, 명령어 주입, 파괴적 패턴을 차단합니다

  • 네트워크 장치 지원: Cisco, Juniper, MikroTik, FortiGate, Palo Alto, Sophos — 지속형 셸 세션과 자동 페이저 억제 지원

  • 점프 셸 지원: SSH로 호스트에 접속한 후 중첩 CLI(telnet으로 호스트 접속, FreeSWITCH fs_cli 등)로 진입합니다 — 명령은 중첩 셸 안에서 실행되며, 순서가 지정된 점프 명령 폴백 목록을 사용합니다

  • 대량 연결 관리: 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를 사용하여 호스트에 연결합니다. host, username, 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

비밀번호용 환경 변수

enable 비밀번호용 환경 변수

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

선택적으로 username이 제공되지 않은 경우 <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, 볼트 등을 통해 주입되며) 채팅이나 도구 호출에 절대 나타나지 않습니다. 도구 호출에서 비밀번호가 명시적으로 제공되면 환경 변수보다 우선합니다.

레거시 장치용 SSH 옵션

기본값이 아닌 알고리즘(ssh -o에 해당)이 필요한 구형 장치에 연결할 때는 sshOptions 매개변수를 사용하세요:

자연어로는:

Connect to 10.0.0.1 port 2222 as user, connectionId old-switch, with KexAlgorithms +diffie-hellman-group-exchange-sha1 and 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 기본값에 추가됩니다. + 없이 값을 지정하면 기본값을 완전히 대체합니다.

Option

SSH2 equivalent

사용 사례

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

지속형 셸, enable 모드 지원

Cisco IOS/IOS-XE 라우터 및 스위치

cisco_xe

지속형 셸 (terminal length 0)

Cisco IOS-XE

cisco_xr

지속형 셸 (terminal length 0)

Cisco IOS-XR

cisco_asa

지속형 셸 (terminal length 0)

Cisco ASA 방화벽

cisco_nexus

지속형 셸 (terminal length 0)

Cisco Nexus (NX-OS)

juniper

지속형 셸 (set cli screen-length 0)

Juniper JunOS 장치

mikrotik

지속형 셸

MikroTik RouterOS

fortinet

지속형 셸 (config system console / set output standard)

FortiGate / FortiOS 방화벽

paloalto

지속형 셸 (set cli pager off)

Palo Alto PAN-OS 방화벽

sophos

지속형 셸 (런타임 시 페이저 자동 처리)

Sophos XG/XGS (SFOS) 방화벽

network

일반 지속형 셸

기타 네트워크 장치

jump_shell

지속형 셸 + 중첩 CLI

ssh_connect_with_jump_command에서 내부적으로 사용

네트워크 장치는 표준 exec() 대신 PTY가 할당된 지속형 셸 세션을 사용합니다. 많은 네트워크 운영 체제가 각 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이거나 없음입니다. 영구 셸 경로의 네트워크 장치는 종료 코드를 보고하지 않으므로, 실행 자체가 실패하지 않는 한 해당 명령은 성공으로 간주됩니다. Linux에서 0이 아닌 종료 코드는 실패로 간주되며 연결이 유지됩니다.

중첩 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를 대신 사용하세요. PROFILE_<NAME>_JUMP_COMMAND는 jumpCommands가 생략된 경우 단일 후보를 제공합니다.

5. 여러 연결에서 실행

ssh_execute_on_multiple을 사용하여 특정 연결에서 명령을 실행합니다:

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

또는 모든 연결에서 실행:

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

6. 점프 셸(SSH를 통한 중첩 CLI)

호스트에 SSH로 접속한 후 명령을 실행하기 전에 중첩 대화형 셸에 진입해야 하는 경우 ssh_connect_with_jump_command를 사용하세요. 이는 다음과 같은 시나리오를 다룹니다:

  • SSH 점프 호스트에서 Topex VoIP 게이트웨이로 Telnet

  • 원격 서버의 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 명령은 중첩 셸 내에서 실행됩니다.

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

프리셋은 재정의할 수 있습니다 — 명시적으로 제공된 매개변수가 우선합니다.

셸 복구: 셸이 끊기면 ssh_execute가 자동으로 셸을 다시 열고 점프 셸에 다시 진입합니다.

연결 해제: 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

/var/log/ssh-failed.jsonl

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

셸 및 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)을 화이트리스트에 포함하세요. 서버는 셸을 열 때 자체적으로 이를 실행하며, 혼합 모드에서는 그렇지 않으면 차단됩니다.

블랙리스트 및 화이트리스트 모드와 달리 혼합 모드는 파이프 또는 체인의 개별 세그먼트를 검사하지 않습니다 — 전체 명령 문자열만 일치시킵니다. 혼합 모드의 세그먼트 수준 보호는 먼저 실행되며 재정의할 수 없는 위험 패턴 목록에서 제공됩니다:

✗ 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 호출에 재사용하십시오.

세션 감사(명령 + 출력)

실행된 모든 명령과 그 출력은 서버 진단(SSH_LOG_FILE)과 별도로 일별 파일에 단일 JSONL 라인으로 기록됩니다:

<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인 경우, 전체 출력이 SSH_FILE_UPLOAD_ENDPOINT로 POST되고 호출자는 반환된 artifact_uri와 file_id 및 작은 미리보기를 포함한 짧은 스텁을 받습니다 — 따라서 대용량 show tech-support가 모델 컨텍스트를 넘치게 하지 않습니다. 엔드포인트는 { "content": "...", "filename": "..." }를 수신하고 { "file_id": "...", "artifact_uri": "..." }를 반환해야 합니다. 엔드포인트가 설정되지 않았거나 업로드가 실패하면 출력이 인라인으로 대체 반환됩니다.

페이저 처리

대화형 페이저(Linux less, Cisco/Juniper ---(more)---)는 그대로 두면 명령이 타임아웃될 때까지 차단합니다. 서버는 두 가지 방식으로 처리합니다:

  • 예방 — 셸을 열 때 장치에 적합한 페이저 비활성화 명령을 전송하고(Cisco는 terminal length 0, Juniper는 set cli screen-length 0), Linux exec에서는 SYSTEMD_PAGER=, PAGER=cat, GIT_PAGER=cat을 설정합니다.

  • 감지 — 페이저 프롬프트가 여전히 나타나면 네트워크 셸에서는 자동으로 진행(스페이스 전송, SSH_MAX_PAGER_PAGES로 제한)하거나, 대화형 Linux 페이저를 종료하기 위해 q를 전송한 다음 출력에서 프롬프트 아티팩트를 제거합니다.

SSH_DISABLE_PAGER=false로 전역 토글, PROFILE_<NAME>_DISABLE_PAGER=false로 프로필별 토글, SSH_DISABLE_PAGER_CMD_<DEVICETYPE>로 장치 유형별 명령 재정의, SSH_PAGER_REGEX / SSH_PAGER_ADVANCE_KEY로 감지 재정의가 가능합니다.

위험 패턴

이 패턴들은 필터 모드와 관계없이 항상 차단됩니다:

패턴

예시

위험

포크 폭탄

:(){ :|:& };:

시스템 충돌

파이프 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

모든 연결의 상태를 확인합니다(죽은 소켓 감지, 셸 상태).

ssh_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 {}

아키텍처 참고 사항

셸 버퍼 관리

각 명령 전에 버퍼가 지워집니다. 안정성 감지는 버퍼가 3 × 500ms 동안 변경되지 않음 = 명령 완료로 판단합니다. 비밀번호 프롬프트는 버퍼의 마지막 200자에서 감지됩니다.

Keepalive 시스템

SSH2는 10초마다 keepalive를 전송합니다(keepaliveInterval: 10000). keepalive가 3회 실패하면 연결이 자동으로 닫힙니다(keepaliveCountMax: 3). 사용자 정의 간격은 디버깅을 위해 keepalive 횟수를 기록합니다.

연결 상태 모니터링

서버는 죽은 연결(소켓 파괴)을 감지하고, 네트워크 장치의 셸 상태를 추적하며, 죽은 연결을 자동 정리하고, 네트워크 장치에서 셸이 닫힌 경우 셸 재개를 시도합니다.

점프 셸

ssh_connect_with_jump_command가 호출되면 서버는: (1) SSH 연결을 열고, (2) PTY 셸을 열고, (3) 점프 명령(예: telnet lh)을 전송하고, (4) 300ms마다 셸 버퍼를 폴링하여 예상 프롬프트 정규식을 확인하고, (5) 연결을 jumpShellActive: true인 jump_shell로 표시합니다. 연결 해제 시 SSH 세션을 닫기 전에 중첩 CLI 종료 명령이 전송됩니다. 셸 복구 시 점프 명령이 자동으로 재전송됩니다.

환경 변수 자격 증명 해석

연결이 생성될 때(ssh_connect 또는 ssh_load_connections를 통해) 비밀번호가 제공되지 않으면 서버는 환경 변수에서 <PREFIX>_PASSWORD를 자동으로 조회합니다. 여기서 <PREFIX>는 connectionId를 대문자로 변환하고 영숫자가 아닌 문자를 _로 대체한 값입니다. 동일한 규칙이 _ENABLE_PASSWORD와 _USERNAME에도 적용됩니다. 명시적으로 제공된 값이 항상 우선합니다.

원본과의 비교

기능

zibdie/SSH-MCP-Server

이 포크

기본 SSH/SFTP

✓

✓

명령어 화이트리스트

✗

✓

명령어 블랙리스트

✗

✓

다중 단어 블랙리스트 항목

✗

✓

위험 패턴 감지

✗

✓

감사 로깅

✗

✓

명령어 검증 도구

✗

✓

구성 파일 지원

✗

✓

네트워크 장치 유형 (Cisco, Juniper, MikroTik)

✗

✓

Cisco enable 모드

✗

✓

점프 셸 (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