SSH로 운영 서버에 붙는 도구라, 아래 내용은 설계 의도가 아니라 지금 코드가 실제로 하는 일입니다. 해당 파일을 같이 적어두었으니 직접 확인하셔도 됩니다.
합니다. OpenSSH 형식 known_hosts로 검증합니다 (internal/sshcore/hostkey.go).
- 처음 보는 호스트라면 주소·키 종류·SHA256 지문을 띄우고 사용자가 고릅니다: 거부 / 이번만 / 항상 신뢰. "항상"을 골랐을 때만 파일에 기록합니다
- 기록된 키와 다른 키가 오면 연결을 끊습니다. 전면 경고가 뜨고, 그 지점을 넘어가는 선택지는 코드 어디에도 없습니다. 사용자에게 묻지도 않습니다. 중간자 공격의 서명이기 때문입니다
- 파일 권한은 디렉터리
0700, 파일0600
Important
LiteDeck은 ~/.ssh/known_hosts를 쓰지 않습니다. 자기 파일
(<OS 설정 디렉터리>/litedeck/known_hosts)을 따로 씁니다. 그래서 ssh로 이미 접속해 본
서버라도 LiteDeck에서는 처음 한 번 다시 물어봅니다. 형식이 같으므로 기존 줄을 복사해 넣으면
그 과정을 건너뛸 수 있습니다.
ssh-agent · 개인키 파일(패스프레이즈 지원) · 비밀번호 · keyboard-interactive(2FA·OTP)를 지원하고, 시도 순서는 호스트마다 직접 정합니다.
- ssh-agent를 맨 앞에 두는 걸 권합니다. 키가 이 프로세스 안으로 들어오지 않습니다
- OTP·2FA 응답은 저장하지 않습니다. 일회용 코드는 두 번째엔 쓸모가 없습니다
경유 서버는 별도의 로그인으로 취급합니다 (internal/sshcore/conn.go).
- 경유 서버의 호스트 키를 목적지와 똑같이 검사합니다. 지문을 따로 띄우고 따로 물어봅니다
- 비밀번호도 별도 항목으로 저장됩니다. 목적지의 자격증명을 덮어쓰지 않습니다
- 목적지의 호스트 키 검사는 그대로입니다. 터널을 탄다고 약해지지 않습니다 — 경유 서버는 바이트를 전달할 뿐이고, 반대편이 누구인지 보증하지 않습니다
- 세션이 닫히면 경유 연결도 같이 닫습니다. 남겨두면 남의 서버에 유휴 로그인이 하나 붙어 있는 셈입니다
여러 단계는 거부합니다. 검증한 적이 없는 동작을 조용히 하는 것보다 낫습니다.
네트워크 탭이 /etc/ssh/sshd_config 와 그 Include 대상을 SFTP로 읽습니다
(internal/app/sshdconfig.go). 읽기만 하고, 명령을 실행하지
않으며, root를 요구하지 않습니다. 아무것도 바꾸지 않습니다 — 고치는 기능은 없습니다.
한계는 이렇습니다: 파일에 적힌 값만 압니다. sshd의 컴파일 기본값은 배포판마다 달라서 추측하지 않고, 이 계정이 못 읽은 파일이 있으면 그 사실을 표시합니다.
저장한다면 OS 자격증명 저장소에만 들어갑니다. macOS 키체인, Windows 자격 증명 관리자,
Linux Secret Service (internal/secret/secret.go).
- LiteDeck 자기 파일에는 비밀을 절대 쓰지 않습니다. 자격증명 저장소를 못 찾으면 더 약한 형식으로 떨어지는 게 아니라 저장을 포기합니다. 매번 묻고, "기억하기" 체크박스 자체를 띄우지 않습니다. 지킬 수 없는 약속을 UI에 두지 않으려는 것입니다
- 저장은 프롬프트마다 선택입니다. 호스트마다 저장된 비밀번호 지우기가 있습니다
- 서버에서 비밀번호가 바뀌어 sudo 인증이 실패하면 저장분을 스스로 지웁니다. 안 그러면 키체인이 다이얼로그보다 먼저 답해서, 사용자가 고칠 방법 없이 영원히 실패합니다
hosts.json에는 주소·사용자·키 파일 경로만 들어갑니다. 비밀은 없습니다
자동으로 붙지 않습니다 (internal/app/sudo.go). 명령은 로그인한
사용자 권한으로 나갑니다. 서버가 거부하면 "관리자 권한으로 재시도"를 물어보고, 사용자가
눌러야 실행합니다. 몰래 sudo를 붙이면 Command Log가 사용자가 믿는 것과 달라지는데,
그 로그가 이 앱을 신뢰할 유일한 근거입니다.
- 비밀번호는 stdin으로만 갑니다:
sudo -S -p '' -- <cmd>. argv에 넣으면 그 서버의 모든 사용자가 프로세스 테이블에서 볼 수 있고 Command Log에도 그대로 남습니다. 그래서 Command Log가 명령줄을 그대로 보여줘도 안전한 것입니다 sudo -n true로 NOPASSWD를 감지하면 아예 묻지 않습니다. 서버가 원하지도 않는 비밀번호를 묻는 것은 무의미할 뿐 아니라 아무 다이얼로그에나 비밀번호를 치는 습관을 들입니다
MCP 연동을 켜면 로컬에 HTTP 엔드포인트가 하나 열립니다. 그것 하나가 공유된 모든
서버를 대변하므로, 여기 적는 것이 그 기능의 안전을 좌우합니다
(internal/mcp/http.go).
127.0.0.1에만 바인딩합니다. 다른 인터페이스로 여는 설정을 두지 않았습니다. 옵션이 없다는 것 자체가 이 엔드포인트를 안전하게 만드는 유일한 근거이기 때문입니다- Bearer 토큰. 설정에 저장되고, 상수 시간으로 비교하며, 재발급 버튼으로만 바뀝니다. 앱이 마음대로 돌리면 붙여넣어 둔 클라이언트 설정이 사용자가 고르지 않은 순간에 깨집니다
- Origin 검사. 브라우저 페이지가 루프백으로 POST 할 수 있고, DNS 리바인딩이 그걸 동일 출처처럼 보이게 만듭니다. MCP 명세가 이걸 요구하는 이유이고, 실제 MCP 클라이언트는 Origin을 안 보냅니다
- 레이트 리밋 초당 1.5회·순간 8회. 예의가 아니라 기능입니다. Exec 채널이 3개뿐이라 에이전트 버스트는 서버를 느리게 하는 게 아니라 사용자가 보고 있는 GUI를 굶깁니다
- 서버별 opt-in, 파일 삭제는 또 따로. 호스트를 등록해도, 읽기를 공유해도 삭제 권한은 안 생깁니다
- 결재 정책은 앱이 소유합니다. 그걸 바꾸는 도구도, 완화하는 파라미터도 없습니다. 모델이 자기 승인을 요청할 방법이 없습니다. 프로토콜에 클라이언트 의도를 실을 자리가 없다는 것을 Claude Code 2.1.22로 실측해 확인했습니다
- 되돌리기 사본은 이 컴퓨터에 쌓이고 24시간 뒤 사라집니다. 서버에는 아무것도 안 남습니다
정직하게 적어둘 것:
- "안 묻기"를 켜면 prompt injection이 이깁니다. 서버 로그나 파일에 심긴 지시문이 모델을 조종할 수 있고, 그 모드에서는 사람이 볼 기회가 없습니다. 남는 것은 방어가 아니라 귀속과 폭발 반경입니다: Command Log 전량 기록, 모드의 시한부 만료, 호스트별 적용
- 되돌릴 수 있는 것은 파일뿐입니다. 서비스 재시작이나 프로세스 종료는 사본이 없습니다. 다시 시작하면 되는 종류라 넣었고, 되돌릴 수 없는 것(임의 명령 실행, 디렉터리 재귀 삭제, 컨테이너·이미지 삭제)은 아예 제공하지 않습니다
- 자격증명은 MCP 계층이 만지지 않습니다. 토큰은 이 엔드포인트에만 쓰이고, SSH 자격증명은 위에 적은 대로 OS 키체인에 있습니다
- 비밀번호가 메모리에 남는 시간을 보장하지 못합니다. Go 문자열은 불변이고 런타임이 언제든
복사할 수 있어 원하는 시점에 지울 수 없습니다.
x/crypto/ssh도 비밀번호를 문자열로 받습니다. 쓰고 나면 가비지가 되어 다음 수집 때 회수되지만, 요청 즉시 0으로 덮는 동작은 구현돼 있지 않습니다. 코어 덤프나 힙 덤프에서는 보일 수 있습니다 - 릴리스 바이너리는 서명돼 있지 않습니다. SHA256 체크섬은 서명이 아닙니다
- Linux 서버 검증은 아직 컨테이너뿐입니다. 자세한 것은 지원 범위에 있습니다
- 전송 이어받기의 무결성 검사는 이음매를 봅니다. 이미 받은 구간 전체가 아니라, 이어붙일 지점 앞 64KB 입니다. 길이가 정확히 같고 그 창 바깥만 달라진 원본은 통과할 수 있습니다 (기능 자세히)
- 감사 로그 같은 건 없습니다. Command Log는 이 컴퓨터에만 남고 어디로도 가지 않습니다. 클라이언트가 쓰는 로그는 감사가 아닙니다
- 의존성 취약점은
main푸시와 모든 PR에서govulncheck ./...로 확인합니다
취약점을 발견하셨다면 공개 이슈 대신 cpprhtn@naver.com 으로 보내주세요.