SpectaShell 아이콘 SpectaShell · 보안

보안

셸은 내 서버에, 키는 내 기기에.

SpectaShell은 SSH 클라이언트와, 서버에서 셸을 붙잡아 두는 작은 프로세스 하나로 이루어져 있습니다. 서버에는 내 SSH 로그인으로만 들어가고, 포트를 열지 않고, root를 쓰지 않습니다. 개인 키는 기기의 Secure Enclave 밖으로 나오지 않습니다. Calida Lab 서버가 내 터미널을 보는 일은 없습니다. 우리가 운영하는 서버는 알림 relay 하나뿐이고, 알림은 서버에서 HPKE로 봉인되어 내 기기만 열 수 있습니다. 아래는 이 말들이 구체적으로 무슨 뜻인지, 직접 확인하는 방법과 함께 정리한 것입니다.

하는 것

  • 내 SSH 로그인으로 서버에 접속
  • 허락한 서버에만 ~/.local/share/spectashell에 keeper 설치
  • authorized_keys에 내 기기의 공개 키 추가 (주석이 SpectaShell로 시작)
  • 호스트 목록 같은 설정을 내 iCloud 비공개 DB에 동기화
  • 알림을 켠 경우에만, 봉인한 알림을 relay로 전송

하지 않는 것

  • 서버나 기기에서 포트 열기
  • root, sudo 사용, 시스템 서비스 설치
  • .bashrc, .zshrc 같은 셸 설정 파일 수정
  • 개인 키, 비밀번호 저장이나 전송
  • 사용 통계, 분석, 추적
01 · keeper

서버에서 도는 것: spectashell-keeper

SSH로 연 셸은 그 연결의 자식 프로세스라서, 연결이 끊기면 SIGHUP을 받고 그 안의 작업도 같이 끝납니다. tmux나 screen이 이 문제를 서버 쪽 프로세스로 푸는 것처럼, SpectaShell은 셸마다 spectashell-keeper 프로세스를 하나씩 띄워 그 안에서 셸을 실행합니다. keeper가 PTY와 최근 출력을 쥐고 있으므로 연결이 끊기거나, 기기가 잠들거나, 앱을 종료해도 셸은 계속 돌고, 다시 붙으면 화면이 그대로 돌아옵니다.

내 Mac, iPad, iPhone

  • SpectaShell
  • Secure Enclave 안의 기기 키
→ssh user@host

내 서버, 내 계정으로

  • sshd → spectashell-keeper attach
  • keeper → 셸 (PTY)
  • Unix 소켓, 0700 디렉터리 안
  • 네트워크 포트 없음, root 없음
02 · 목록 키

Face ID 없이 셸 목록만 보는 키

기기 키는 로그인할 때마다 Face ID를 요구합니다. 그런데 앱의 Running 화면은 서버마다 어떤 셸이 돌고 있는지 수시로 확인해야 하므로, 기기마다 묻지 않고 서명하는 두 번째 키인 목록 키를 하나 더 둡니다. 이 키로 할 수 있는 일은 keeper 목록 조회 하나뿐이고, 그 제한은 앱이 아니라 서버의 sshd가 강제합니다.

목록 키의 ~/.ssh/authorized_keys 줄. restrict가 PTY 할당, 포트·에이전트·X11 포워딩을 모두 끄고, command=가 클라이언트가 무엇을 요청하든 이 명령만 실행하게 합니다. 셸도, 파일 전송도 열리지 않습니다.
restrict,command="$HOME/.local/share/spectashell/current/spectashell-keeper list" ecdsa-sha2-nistp256 AAAA… SpectaShell listing on iPhone
유출될 계정이
없습니다.

가입, 로그인, 비밀번호 DB가 없습니다.

동기화와 팀 공유는 기기에 이미 로그인된 Apple 계정의 iCloud로 합니다. Calida Lab에는 내 계정도, 호스트 목록도, 키도 없습니다. 유일하게 운영하는 알림 relay가 아는 것은 기기의 알림 토큰과 호스트들의 공개 키뿐입니다.

03 · keeper 검증

내 서버에서 도는 바이너리를 확인하는 방법

04 · 키와 인증

개인 키는 칩 안에, 비밀번호는 어디에도

기기 키 (ECDSA P-256)

기기마다 자기 키를 직접 만듭니다. iPhone, iPad, Apple 실리콘이나 T2 칩이 있는 Mac에서는 Secure Enclave 안에서 만들어져 개인 키를 꺼낼 방법이 없습니다. SpectaShell도, 기기를 가진 사람도 마찬가지입니다. 그런 칩이 없는 Mac에서는 동기화되지 않도록 표시한 키체인 항목에 둡니다. 서버가 키를 받아들일 때마다 Face ID, Touch ID 또는 기기 암호를 확인한 뒤 서명합니다. 외부 키를 가져오는(import) 기능은 없습니다.

묻는 에이전트 포워딩

서버에 에이전트 포워딩을 켜면, 그 서버가 다른 곳에 로그인하거나 git 커밋에 서명할 때 내 기기 키를 쓸 수 있습니다. 서명 요청마다 어느 서버가 무엇에 쓰려는지 보여 주고 따로 생체 인증을 받으며, 다른 요청에 한 승인을 재사용하지 않습니다. 한 번 취소하면 그 연결의 이후 요청은 거부합니다. 포워딩된 에이전트로 키를 추가하거나 지우는 것도 막습니다.

Mac의 ~/.ssh는 읽기만

Mac에서는 ~/.ssh/config, ~/.ssh의 키, SSH 에이전트, known_hosts를 ssh처럼 읽습니다. 수정하거나 복사하거나 업로드하지 않습니다. 단, Mac SSH 에이전트의 키는 ssh가 하듯 묻지 않고 포워딩되므로, 믿는 서버에서만 포워딩을 켜세요.

비밀번호는 저장하지 않음

비밀번호, 패스프레이즈, OTP는 물어서 로그인에 넘기고 바로 잊습니다. 대화상자에도 "Not stored anywhere"라고 적혀 있습니다. 입력칸은 시스템 자동 완성을 지원해서 암호 앱이나 1Password가 비밀번호와 일회용 코드를 채울 수 있습니다. 보관은 그 비밀번호 관리자가 하고, SpectaShell은 하지 않습니다. 비밀번호로 한 번 로그인하면 기기 키를 서버에 추가하자고 제안해서, 다음부터는 키로 들어가게 합니다.

호스트 키 검증

OpenSSH와 같은 방식입니다. 처음 접속할 때 키 종류와 지문을 보여 주고 신뢰할지 묻습니다. 신뢰한 키는 다른 기기에도 동기화됩니다. 키가 바뀌면 모든 기기에서 접속을 거부하고, 일부러 Forget Old Key…를 해야만 새 키를 받습니다. Mac에서는 기존 ~/.ssh/known_hosts도 그대로 신뢰합니다.

기기를 잃어버렸을 때

다른 기기에서 Keys를 열고 잃어버린 기기 옆의 Remove from Every Host…를 누르면, 호스트 목록의 모든 서버에 접속해 그 기기의 키를 지웁니다. 그때 꺼져 있던 서버는 다음에 내 기기가 로그인할 때 지웁니다. 그 키는 다시 올라가지 않고, 그 기기로 가던 알림도 멈춥니다.

05 · 알림 relay

우리 서버 하나, 그리고 그 서버가 못 보는 것

서버의 에이전트가 승인을 기다리는데 어느 기기도 그 셸을 보고 있지 않으면, keeper가 iPhone에 알림을 보낼 수 있습니다(설정 ▸ Notifications ▸ Notify This Device). 문제는 Apple이 앱의 푸시 키를 가진 쪽에서만 알림을 받는다는 점입니다. 그 키를 사용자 서버마다 나눠 줄 수는 없으므로, 중간에 Calida Lab의 relay(push.calidalab.ai)가 있습니다. relay는 내용을 볼 수 없게 설계했습니다.

내 서버의 keeper

  • 알림을 내 기기의 공개 키로 봉인
  • 호스트 고유 Ed25519 키로 요청 서명
→HTTPS

relay → APNs

  • 서명 확인 후 봉인된 바이트를 그대로 전달
  • 내용을 저장하지 않음
→APNs

내 iPhone

  • 기기의 개인 키로 열기
  • 호스트 이름은 기기의 기록에서

relay가 아는 것

  • 내 기기의 알림 토큰
  • 그 기기에 알림을 보내는 호스트들의 공개 키 (그래서 호스트 수)
  • 호스트마다 언제, 얼마나 자주 보내는지
  • 크기가 모두 같은 봉인된 바이트
  • 요청의 IP 주소. 기록하지 않습니다. 앞단의 Cloudflare도 같은 것을 봅니다.

relay가 절대 모르는 것

  • 알림 내용
  • 어느 서버인지 (이름, 주소)
  • 내 명령이나 셸에 출력된 내용
  • 어떤 파일도
  • 서명하거나 여는 개인 키 어느 것도
06 · 데이터

동기화되는 것, 기기에만 남는 것

내 iCloud 비공개 DB로 동기화

  • 호스트와 그룹, 스니펫, 트리거
  • 저장한 포트 포워딩, 즐겨찾기 폴더
  • 추가한 클라우드 (자격 증명 제외)
  • 신뢰한 호스트 키, 내 기기들의 공개 키
  • 터미널 폰트와 테마
  • 알림 규칙, 알림용 호스트 공개 키

기기 밖으로 나가지 않음

  • 개인 키 (기기 키, 목록 키)
  • 비밀번호, 패스프레이즈, OTP (아예 저장 안 함)
  • 셸 출력
  • 명령 기록 (토큰이 섞일 수 있어 이 기기에만 30일)
  • 창과 탭, 글자 크기 같은 이 기기만의 설정
07 · 네트워크

SpectaShell이 접속하는 곳 전부

08 · 서버에 남는 것

설치된 것 확인하고 지우기

SpectaShell이 서버에 두는 것은 아래가 전부입니다. 서버의 다른 곳은 건드리지 않습니다.

SpectaShell이 서버에 두는 것. 지우려면 먼저 그 서버의 셸을 모두 닫으세요.
~/.local/share/spectashell/           # keeper 바이너리 (빌드별 디렉터리)와 셸 통합 스크립트
~/.local/share/spectashell-push/      # 알림을 켠 경우에만: 받는 기기마다 파일 하나
/tmp/spectashell-$(id -u)/            # Linux: keeper 소켓과 메타데이터
~/Library/Application Support/SpectaShell/keepers/   # Mac 서버: 같은 것
~/.ssh/authorized_keys                # 주석이 "SpectaShell"(초기 빌드는 "sdTerm")로 시작하는 줄
09 · 자주 묻는 질문

보안 Q&A

Calida Lab이 내 셸, 서버 목록, 키를 볼 수 있나요?

아니요. 셸은 내 기기와 내 서버 사이 SSH 안에서만 오가고, 동기화 데이터는 내 iCloud 비공개 DB에, 개인 키와 비밀번호는 기기 안에만 있습니다. Calida Lab이 받는 것은 알림을 켰을 때 relay를 지나가는 봉인된 알림뿐이고, 이를 열 키는 내 기기에만 있습니다.

서버에 포트를 열거나 root가 필요한가요?

둘 다 아닙니다. keeper는 내 계정으로 홈 디렉터리에서 실행되고, 0700 디렉터리의 Unix 소켓만 씁니다. 들어오는 길은 내 SSH 로그인 하나뿐이고, 시스템 전체에 설치하는 것이 없습니다.

같은 서버의 다른 사용자가 내 셸에 들어올 수 있나요?

다른 일반 사용자는 못 들어옵니다. 내 계정과 root는 가능하며, tmux나 screen과 같습니다. 관리자를 믿을 수 없는 서버에서는 셸에 비밀을 두지 마세요.

에이전트 포워딩을 켜도 되나요?

SSH 에이전트 포워딩은 프로토콜 특성상 그 서버의 root도 포워딩된 에이전트를 쓸 수 있습니다. SpectaShell은 기기 키를 쓸 때마다 생체 인증으로 따로 묻기 때문에 몰래 쓰일 수는 없지만, Mac SSH 에이전트의 키는 ssh처럼 묻지 않고 넘어갑니다. 믿는 서버에서만 켜세요.

Apple 계정이 탈취되면 어떻게 되나요?

동기화된 호스트 목록과 스니펫은 넘어갑니다. 하지만 키와 비밀번호는 동기화되지 않으므로 서버에 로그인할 수는 없고, 새 기기의 목록 키도 내 기존 기기가 승인하기 전에는 서버에 올라가지 않습니다. 스니펫에 비밀번호나 토큰을 넣지 마세요.

팀원과 그룹을 공유하면 무엇이 공유되나요?

그룹 공유는 Apple 계정 사이의 CloudKit 공유이고, 서버 이름과 주소만 오갑니다. 키나 비밀번호는 오가지 않으며, 공유만으로는 아무도 로그인할 수 없습니다. 누가 로그인할 수 있는지는 서버의 authorized_keys가 정합니다. 팀원을 내 계정으로 들여보내면 그 계정의 셸을 모두 보고 조작할 수 있으니, 사람마다 계정을 따로 주세요.

Mac 앱은 왜 샌드박스가 아닌가요?

Mac에서 여는 로컬 셸은 내 파일에 닿아야 하는데, 샌드박스 앱의 셸은 그럴 수 없기 때문입니다. 그래서 iTerm2나 Ghostty처럼 Developer ID로 서명하고 Apple 공증을 받아 직접 배포합니다. iPad와 iPhone 앱은 App Store 앱이라 샌드박스 안에서 돌고, 로컬 셸 없이 서버의 셸만 엽니다.

iPhone을 잃어버렸어요.

다른 기기에서 Keys를 열고 잃어버린 기기 옆의 Remove from Every Host…를 누르세요. 모든 서버에서 그 기기의 키를 지우고, 그 기기로 가던 알림도 멈춥니다. 나의 찾기에서 기기를 지우고 Apple 계정에서도 제거하세요.

서버에서 SpectaShell을 완전히 지우려면요?

그 서버의 셸을 모두 닫은 뒤, 08 · 서버에 남는 것의 디렉터리를 지우고 ~/.ssh/authorized_keys에서 주석이 SpectaShell(초기 빌드는 sdTerm)로 시작하는 줄을 지우면 됩니다.

보안 문제 제보
돌아가기개요 → 출시 알림 받기