Claude Code 소스 코드가 npm으로 유출된 이야기

|Operation Risk|11분 읽기

패치할 곳이 없는 게 아니라, 막을 곳이 없었던 사고

지난 3월 31일, Anthropic의 Claude Code 전체 소스코드가 npm에 올라간 소스맵을 통해 그대로 읽을 수 있는 상태로 공개되어 있었습니다. 누가 해킹한 것도, 내부자가 빼돌린 것도 아니었어요. 정식 배포된 패키지 안에 디버깅용 파일이 같이 들어가 있었을 뿐입니다.

유출된 저장소는 순식간에 스타 1,100개, 포크 1,900개를 넘겼습니다. 그런데 막상 안을 들여다보니 단순히 '코드가 새어나왔다' 정도로 넘길 일이 아니더라고요. 생각보다 훨씬 정교하게 짜인 시스템이었습니다.

숫자로 보는 Claude Code의 규모

코드베이스 규모부터 짚고 가죠.

  • 1,900개의 TypeScript 파일
  • 512,000줄 이상의 코드
  • 40여 개의 내장 도구
  • 50여 개의 슬래시 명령

'CLI 도구 하나' 수준이 아닙니다. Bun 런타임 위에서 돌아가고, 터미널 UI는 React with Ink로 만들었더군요. 전체가 모듈식 도구 기반 아키텍처로 설계되어 있었습니다.

터미널에서 도는 명령어 도구를 50만 줄 규모로 짠다는 건, 이걸 단발성 유틸이 아니라 오래 운영하고 확장할 플랫폼으로 봤다는 뜻이에요. 코드 라인 수보다 그 의도가 더 눈에 들어왔습니다.

아키텍처에서 눈에 띈 부분들

도구 시스템

가장 인상적이었던 건 플러그인 방식의 도구 아키텍처였습니다. 파일 읽기, bash 실행, 웹 페치, LSP 통합 같은 기능이 각각 독립된 도구로 떨어져 있고, 도구마다 개별 권한을 들고 있어요. 기본 도구 정의만 해도 TypeScript 29,000줄이었습니다.

interface Tool {
  name: string;
  permissions: PermissionGate;
  execute(context: ToolContext): Promise<ToolResult>;
}

권한을 도구 인터페이스 안에 박아둔 구조가 눈에 띕니다. 기능을 추가할 때마다 "이 도구가 무엇을 할 수 있는가"를 강제로 선언하게 만드는 거죠. MSA를 운영하면서 권한을 사후에 끼워 넣다가 고생한 경험이 있는 사람이라면, 이게 왜 처음부터 인터페이스 레벨에 있어야 하는지 바로 알 겁니다.

쿼리 엔진

46,000줄짜리 쿼리 엔진이 모든 LLM API 호출과 스트리밍, 캐싱을 담당합니다. 코드베이스에서 가장 큰 단일 모듈이었어요. 결국 이 도구의 심장은 모델과 주고받는 파이프라인이고, 거기에 가장 많은 코드가 몰려 있다는 게 솔직하게 느껴집니다.

멀티 에이전트 오케스트레이션

복잡한 작업을 위해 하위 에이전트를 생성하는 "스웜" 시스템도 들어 있었습니다. 각 에이전트가 고유한 도구 권한을 가진 별도 컨텍스트에서 도는 방식이에요. 사람으로 치면 작업을 쪼개서 권한이 제한된 워커에게 나눠주는 구조인데, 분산 시스템에서 워커 격리를 설계하는 방식과 거의 닮아 있었습니다.

기술 선택을 보면서 든 생각

개발팀의 선택을 따라가다 보면 몇 가지가 눈에 들어옵니다.

런타임으로 Bun 선택: 빠른 시작 시간과 데드 코드 제거를 노리고 Node 대신 Bun을 택했더군요. CLI는 매번 새로 뜨는 도구라 콜드 스타트가 곧 체감 성능입니다. 여기서 Node가 아니라 Bun을 골랐다는 건 시작 속도를 꽤 진지하게 본 거예요.

터미널에서 React 사용: Ink로 터미널 UI를 컴포넌트 기반으로 짠 건 과감한 선택입니다. 상태 관리까지 끌고 와서 웹앱처럼 동작하는 터미널 인터페이스를 만든 거죠. 유지보수 관점에서 보면 이해가 갑니다. 터미널 출력 로직을 절차적으로 짜다 보면 금방 손대기 무서운 코드가 되거든요.

Zod v4로 검증: 모든 도구 입력, API 응답, 설정 파일에 스키마 검증이 걸려 있었습니다. 외부에서 들어오는 값과 모델이 뱉는 값을 전부 믿지 않겠다는 태도인데, LLM 응답이 비결정적이라는 걸 생각하면 합리적인 방어선입니다.

그래서, 어쩌다 이런 일이

원인은 허무할 만큼 단순했습니다. npm 패키지에 소스맵 파일이 같이 들어가 버린 거예요.

소스맵은 원래 디버깅용입니다. 최소화·번들링된 코드를 원본 소스에 매핑해주는 역할이죠. 개발 단계에서는 고마운 물건이에요. 문제는 이게 프로덕션 npm 배포에 섞여 들어가는 순간, 사실상 전체 코드베이스를 읽기 좋은 형태로 같이 배포하는 셈이 된다는 겁니다.

보안 사고라고 부르기도 애매해요. 취약점을 뚫고 들어온 게 아니라, 내보내면 안 되는 파일을 그냥 같이 내보낸 거니까요. 빌드 파이프라인의 마지막 칸에서 한 줄이 빠진 결과입니다. 더 나은 코드를 작성하도록 돕는 도구가, 정작 자기 빌드 설정에서 발이 걸렸다는 게 좀 씁쓸한 대목이고요.

개발자라면 챙겨야 할 지점

이번 일에서 건질 게 몇 가지 있습니다.

  1. npm 배포 전 패키지 내용물 확인: npm pack --dry-run으로 실제 포함되는 파일 목록을 눈으로 한 번 보는 습관. 이거 하나면 이번 같은 사고는 대부분 걸러집니다.

  2. 소스맵은 소스코드와 같은 등급으로 취급: 의도한 게 아니라면 프로덕션 패키지에 절대 넣지 말 것. '디버깅 편의'라는 명목으로 무심코 켜두는 옵션이 가장 위험합니다.

  3. 아키텍처 자체는 따로 볼 가치가 있음: 유출 경위와 별개로, Claude Code의 도구 시스템과 권한 게이트, 멀티 에이전트 패턴은 AI 기반 도구를 만드는 사람들에게 좋은 레퍼런스입니다.

표면은 유출, 구조는 다른 이야기

유출 자체는 분명 아쉬운 사고입니다. 하지만 공개된 코드를 통해 AI 코딩 도구의 기준선이 어디까지 올라왔는지를 확인할 수 있었어요. 단순한 API 래퍼가 아니라, 프로덕션급 개발자 경험을 만들기 위해 상당한 엔지니어링이 들어가 있더군요.

권한 시스템, 멀티 에이전트 오케스트레이션, IDE 브리지, 지속적 메모리 관리. 이 정도 구성요소가 이제 이 분야에서 기본값처럼 자리 잡아가는 분위기입니다.

그런데 내가 끝까지 머릿속에 남은 건 정교한 아키텍처가 아니라, 빌드 설정 한 줄이었습니다. 512만 줄을 그렇게 공들여 짜놓고도, 결국 무너진 칸은 배포 직전의 사소한 설정이었거든요. 운영을 오래 하다 보면 이런 장면을 반복해서 봅니다. 시스템을 무너뜨리는 건 대개 가장 복잡한 부분이 아니라, 모두가 당연하다고 믿고 아무도 다시 안 보는 부분이에요. 이번 사건은 그걸 한 번 더 확인시켜준 셈입니다.

#Claude Code#소스코드 유출#npm#소스맵#개발보안