Skip to content

Security: cpprhtn/LiteDeck

Security

docs/security.md

보안

← README

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 응답은 저장하지 않습니다. 일회용 코드는 두 번째엔 쓸모가 없습니다

경유 서버(ProxyJump)

경유 서버는 별도의 로그인으로 취급합니다 (internal/sshcore/conn.go).

  • 경유 서버의 호스트 키를 목적지와 똑같이 검사합니다. 지문을 따로 띄우고 따로 물어봅니다
  • 비밀번호도 별도 항목으로 저장됩니다. 목적지의 자격증명을 덮어쓰지 않습니다
  • 목적지의 호스트 키 검사는 그대로입니다. 터널을 탄다고 약해지지 않습니다 — 경유 서버는 바이트를 전달할 뿐이고, 반대편이 누구인지 보증하지 않습니다
  • 세션이 닫히면 경유 연결도 같이 닫습니다. 남겨두면 남의 서버에 유휴 로그인이 하나 붙어 있는 셈입니다

여러 단계는 거부합니다. 검증한 적이 없는 동작을 조용히 하는 것보다 낫습니다.

서버 sshd 설정 읽기

네트워크 탭이 /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에는 주소·사용자·키 파일 경로만 들어갑니다. 비밀은 없습니다

sudo

자동으로 붙지 않습니다 (internal/app/sudo.go). 명령은 로그인한 사용자 권한으로 나갑니다. 서버가 거부하면 "관리자 권한으로 재시도"를 물어보고, 사용자가 눌러야 실행합니다. 몰래 sudo를 붙이면 Command Log가 사용자가 믿는 것과 달라지는데, 그 로그가 이 앱을 신뢰할 유일한 근거입니다.

  • 비밀번호는 stdin으로만 갑니다: sudo -S -p '' -- <cmd>. argv에 넣으면 그 서버의 모든 사용자가 프로세스 테이블에서 볼 수 있고 Command Log에도 그대로 남습니다. 그래서 Command Log가 명령줄을 그대로 보여줘도 안전한 것입니다
  • sudo -n true로 NOPASSWD를 감지하면 아예 묻지 않습니다. 서버가 원하지도 않는 비밀번호를 묻는 것은 무의미할 뿐 아니라 아무 다이얼로그에나 비밀번호를 치는 습관을 들입니다

MCP 엔드포인트

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 으로 보내주세요.

There aren't any published security advisories