클로드 코드 소스 유출: 가짜 도구, 불만의 정규 표현식, 언더커버 모드
source https://alex000kim.com/posts/2026-03-31-claude-code-source-leak/
역시 정규식 쓰는건 국룰이네요ㅋㅋㅋ
목차
안티 디스틸레이션: 모방 모델을 엿 먹이려고 가짜 툴 심기
Undercover 모드: AI가 자기 정체를 숨길 때
정규식으로 짜증 감지하기 (네, regex 맞습니다)
JS 런타임 아래에서 돌아가는 네이티브 클라이언트 증명
하루에 250,000번 버려지는 API 호출
KAIROS: 아직 공개되지 않은 자율 에이전트 모드
그 밖에 이것저것
그래서, 이게 왜 중요할까?
HN 토론은 여기에서 볼 수 있습니다: https://news.ycombinator.com/item?id=47586778
나는 Claude Code를 매일 쓴다. 그래서 오늘 좀 이른 시간에 Chaofan Shou가 Anthropic이 Claude Code npm 패키지에 .map 파일을 같이 실어 보냈다는 걸 발견했을 때, 그것도 CLI 툴의 읽기 쉬운 전체 소스 코드가 그대로 들어 있는 파일이었다는 걸 알았을 때, 바로 안을 들여다보고 싶어졌다. 패키지는 이후 내려갔지만, 그 전에 이미 코드가 널리 미러링됐고, 나도 그중 하나였고, Hacker News에서도 샅샅이 뜯겨 나갔다.
이번 건은 Anthropic이 일주일 새 두 번째로 사고를 낸 거다. 며칠 전 모델 스펙 유출이 있었으니 말이다. 트위터에서는 이제 내부에 일부러 이러는 사람이 있는 거 아니냐는 말까지 나오기 시작했다. 아마 그렇진 않겠지만, 어느 쪽이든 보기 좋은 그림은 아니다. 특히 타이밍이 묘하다. 불과 열흘 전만 해도 Anthropic은 OpenCode에 법적 압박을 넣어서, 서드파티 툴들이 Claude Code 내부 API를 써서 토큰당 과금 대신 구독 요금으로 Opus에 접근하던 길을 막기 위해 내장 Claude 인증을 제거하게 만들었다. 그 기나긴 소동을 떠올리면, 아래에서 나오는 내용들이 더 날카롭게 느껴진다.
그래서 아침 시간을 들여 HN 댓글과 유출된 소스를 쭉 읽어봤다. 아래는 내가 보기엔 얼마나 "매운지" 기준으로 대충 정리한 내용이다.
안티 디스틸레이션: 모방 모델을 엿 먹이려고 가짜 툴 심기
claude.ts (line 301-313)를 보면 ANTI_DISTILLATION_CC라는 플래그가 있다. 이게 켜지면 Claude Code는 API 요청에 anti_distillation: ['fake_tools']를 넣어 보낸다. 그러면 서버는 system prompt 안에 눈속임용 툴 정의를 몰래 주입한다.
아이디어는 이렇다. 누군가 Claude Code의 API 트래픽을 녹화해서 경쟁 모델 학습에 쓰고 있다면, 이 가짜 툴이 학습 데이터를 오염시키는 것이다. 이 기능은 GrowthBook feature flag(tengu_anti_distill_fake_tool_injection) 뒤에 걸려 있고, 퍼스트파티 CLI 세션에서만 활성화된다.
이건 HN에서도 사람들이 가장 먼저 눈치챈 것 중 하나였다.
여기에 더해 betas.ts (lines 279-298)에는 두 번째 안티 디스틸레이션 장치도 있다. 서버 측 connector-text summarization이다. 이 기능이 켜지면 API는 툴 호출 사이에 assistant가 생성한 텍스트를 버퍼링한 뒤 요약하고, 그 요약본에 암호학적 서명을 붙여 돌려준다. 다음 턴에서는 그 서명을 바탕으로 원문을 복원할 수 있다. 즉, API 트래픽을 녹화해도 온전한 추론 체인이 아니라 요약본만 얻게 된다.
이걸 우회하기가 얼마나 어려울까? 별로 어렵지 않다. claude.ts의 활성화 로직을 보면 가짜 툴 주입이 동작하려면 네 가지 조건이 모두 참이어야 한다. 컴파일 타임 플래그 ANTI_DISTILLATION_CC, cli entrypoint, 퍼스트파티 API provider, 그리고 tengu_anti_distill_fake_tool_injection GrowthBook 플래그가 true를 반환하는 것. 요청 본문이 API에 도달하기 전에 anti_distillation 필드를 벗겨내는 MITM 프록시만 있어도 이건 그냥 우회된다. 주입은 서버 측에서, 그것도 opt-in 방식으로 일어나기 때문이다.
shouldIncludeFirstPartyOnlyBetas() 함수도 CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS를 확인하니, 이 env var를 truthy 값으로 설정하면 전체 기능이 꺼진다. 게다가 서드파티 API provider를 쓰거나 CLI가 아니라 SDK entrypoint를 쓰는 경우에는 이 체크 자체가 아예 실행되지 않는다. connector-text summarization은 범위가 더 좁아서 Anthropic 내부 전용(USER_TYPE === 'ant')이다. 외부 사용자는 애초에 마주칠 일도 없다.
Claude Code 트래픽으로 진지하게 distillation을 하려는 사람이라면, 소스 한 시간만 읽어도 우회법은 금방 찾아낼 것이다. 진짜 방어 수단은 기술이 아니라 법률 쪽에 더 가까워 보인다.
Undercover 모드: AI가 자기 정체를 숨길 때
undercover.ts는 약 90줄짜리 파일인데, Claude Code가 내부 저장소가 아닌 곳에서 쓰일 때 Anthropic 내부 흔적을 모두 지우는 모드를 구현한다. 모델에게 "Capybara"나 "Tengu" 같은 내부 코드명, 내부 Slack 채널, 저장소 이름, 심지어 "Claude Code"라는 표현 자체도 절대 언급하지 말라고 지시한다.
line 15를 보면 이런 문구가 있다:
"There is NO force-OFF. This guards against model codename leaks."
CLAUDE_CODE_UNDERCOVER=1로 강제로 켤 수는 있지만, 강제로 끄는 방법은 없다. 외부 빌드에서는 함수 전체가 dead-code elimination을 거쳐 사실상 의미 없는 반환문만 남는다. 되돌릴 수 없는 일방통행문인 셈이다.
이 말은 Anthropic 직원이 오픈소스 프로젝트에 AI가 작성한 커밋이나 PR을 올려도, 그게 AI가 썼다는 흔적은 남지 않는다는 뜻이다. 내부 코드명을 숨기는 건 충분히 그럴듯하다. 하지만 AI가 적극적으로 사람인 척하게 만드는 건 또 다른 문제다.
정규식으로 짜증 감지하기 (네, regex 맞습니다)
userPromptKeywords.ts에는 사용자의 짜증을 감지하는 regex 패턴이 들어 있다:
/\b(wtf|wth|ffs|omfg|shit(ty|tiest)?|dumbass|horrible|awful|
piss(ed|ing)? off|piece of (shit|crap|junk)|what the (fuck|hell)|
fucking? (broken|useless|terrible|awful|horrible)|fuck you|
screw (this|you)|so frustrating|this sucks|damn it)\b/감정 분석에 regex를 쓰는 LLM 회사라니, 아이러니의 극치다. 그래도 한편으론 이해가 간다. 사용자가 툴에 욕하고 있는지만 확인하려고 LLM inference 호출을 한 번 더 태우는 것보다 regex가 훨씬 빠르고 훨씬 싸니까.
JS 런타임 아래에서 돌아가는 네이티브 클라이언트 증명
system.ts (lines 59-95)를 보면 API 요청에 cch=00000라는 placeholder가 들어 있다. 요청이 프로세스를 떠나기 직전에 Bun의 네이티브 HTTP 스택(Zig로 작성됨)이 이 다섯 개의 0을 계산된 해시 값으로 덮어쓴다. 서버는 그 해시를 검증해서, 이 요청이 조작된 클라이언트가 아니라 진짜 Claude Code 바이너리에서 온 것인지 확인한다.
길이가 같은 placeholder를 쓰는 이유는, 교체해도 Content-Length 헤더가 바뀌지 않고 버퍼를 다시 할당할 필요도 없게 하려는 것이다. 계산은 JavaScript 런타임 아래에서 이뤄지기 때문에 JS 레이어에서 도는 어떤 코드에서도 보이지 않는다. 한마디로 API 호출용 DRM을 HTTP 전송 레벨에서 구현해 둔 셈이다.
이게 바로 OpenCode와의 법적 충돌 뒤에 깔린 기술적 강제력이다. Anthropic은 서드파티 툴에게 자기들 API를 쓰지 말라고 요청만 하는 게 아니다. 바이너리 자체가 암호학적으로 "나는 진짜 Claude Code 클라이언트다"라고 증명한다. Anthropic의 법적 통지 이후 OpenCode 커뮤니티가 왜 session-stitching hacks와 auth plugin 같은 우회책으로 몰릴 수밖에 없었는지 궁금했다면, 이유가 바로 이거다.
그렇다고 이 증명이 완벽하게 빈틈없는 건 아니다. 전체 메커니즘은 컴파일 타임 feature flag인 NATIVE_CLIENT_ATTESTATION 뒤에 묶여 있고, cch=00000 플레이스홀더도 그 flag가 켜져 있을 때만 x-anthropic-billing-header에 주입된다. 헤더 자체도 CLAUDE_CODE_ATTRIBUTION_HEADER를 falsy 값으로 두면 통째로 꺼버릴 수 있고, GrowthBook killswitch인 tengu_attribution_header로 원격 비활성화하는 것도 가능하다. Zig 레벨의 hash 치환 역시 공식 Bun 바이너리 안에서만 동작한다. JS 번들을 다시 빌드해서 일반 Bun이나 Node에서 돌리면, 그 플레이스홀더는 그대로 남는다. 서버로 날아가는 건 문자 그대로 0 다섯 개다. 서버가 그걸 바로 거부하는지, 아니면 그냥 로그만 남기는지는 아직 알 수 없다. 다만 코드 주석에 서버 측 _parse_cc_header 함수가 "알 수 없는 추가 필드는 허용한다"고 적혀 있는 걸 보면, DRM 비슷한 시스템치고는 검증이 예상보다 느슨할 가능성이 있어 보인다. 버튼 한 번으로 뚫리는 수준은 아니지만, 그렇다고 집요한 서드파티 클라이언트를 오래 막아낼 만한 구조도 아니다.
하루에 250,000번 버려지는 API 호출
autoCompact.ts (lines 68-70)에 달린 주석:
“BQ 2026-03-10: 한 세션에서 50회 이상 연속 실패한 세션이 1,279개 있었고(최대 3,272회), 전 세계적으로 하루 약 25만 건의 API 호출이 낭비되고 있었다.”
해법은 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3였다. 세 번 연속 실패하면 그 세션에서는 compaction을 아예 꺼버린다. 하루 25만 건의 API 호출을 태우는 문제를 막는 데 필요한 게 코드 세 줄이었다는 뜻이다.
KAIROS: 아직 공개되지 않은 자율 에이전트 모드
코드베이스 전반에는 KAIROS라는 feature gate 뒤에 숨은 모드의 흔적이 깔려 있다. main.tsx의 코드 경로를 보면, 아직 공개되지 않은 자율 에이전트 모드로 보이고 대략 이런 요소들이 들어 있다:
“야간 메모리 증류”를 위한
/dream스킬append-only 방식의 일일 로그
GitHub webhook 구독
백그라운드 daemon worker
5분마다 실행되는 cron 기반 refresh
이번 유출에서 드러난 제품 로드맵 정보 가운데 가장 큰 건 아마 이 부분일 것이다.
구현은 여기저기 게이트로 막혀 있어서 실제로 얼마나 진행됐는지는 알 수 없다. 그래도 상시 실행되고 백그라운드에서 계속 돌아가는 에이전트의 뼈대가 이미 들어 있는 건 맞다.
그 밖에 이것저것
내일은 4월 1일이고, 소스 안에는 거의 확실하게 올해 만우절 농담으로 보이는 것도 들어 있다. buddy/companion.ts는 다마고치 스타일의 동료 시스템을 구현한다. 모든 사용자는 자기 user ID를 기반으로 Mulberry32 PRNG가 생성한 결정론적 생물 하나를 받는다. 종은 18종이고, 희귀도는 common부터 legendary까지 있으며, shiny 확률은 1%다. DEBUGGING이나 SNARK 같은 RPG 스탯도 붙는다. 종 이름은 빌드 시스템의 grep 검사에 안 걸리게 String.fromCharCode()로 인코딩돼 있다.
ink/screen.ts와 ink/optimizer.ts의 터미널 렌더링은 게임 엔진 쪽 기법을 꽤 노골적으로 가져다 쓴다.Int32Array 기반 ASCII 문자 풀, 비트마스크로 인코딩된 스타일 메타데이터, 커서 이동을 병합하고 hide/show 쌍을 상쇄하는 patch optimizer, 그리고 스스로 항목을 내보내는 line-width cache까지 들어 있다. 소스에는 “토큰 스트리밍 중stringWidth 호출을 약 50배 줄였다”고 적혀 있다. 얼핏 보면 과한데, 이 도구들이 토큰을 한 글자씩 흘려보낸다는 걸 생각하면 납득은 간다.
모든 bash 명령은 bashSecurity.ts를 거치며 번호가 붙은 23개의 보안 검사를 통과한다. Zsh 내장 명령 18개 차단, permission check에서 curl을 우회하게 만드는 Zsh equals expansion(=curl) 방어, unicode zero-width space 주입, IFS null-byte 주입, 그리고 HackerOne 리뷰에서 발견된 malformed token 우회까지 막는다. 이렇게까지 Zsh 위협 모델에 특화된 도구는 나도 다른 데서 못 봤다.
아키텍처의 많은 부분은 프롬프트 캐시의 경제성에 노골적으로 끌려가고 있다.promptCacheBreakDetection.ts는 캐시를 깨뜨리는 벡터를 14가지 추적하고, 모드 전환 때문에 캐시가 깨지지 않게 막는 “sticky latch”도 있다. 어떤 함수에는 DANGEROUS_uncachedSystemPromptSection()이라는 이름까지 붙어 있다. 토큰 하나하나에 돈을 내는 구조에서는, 캐시 무효화가 더 이상 컴퓨터 과학 농담이 아니라 회계 문제가 된다.
coordinatorMode.ts의 멀티 에이전트 coordinator도 흥미롭다. 오케스트레이션 알고리즘이 코드가 아니라 프롬프트이기 때문이다. 여기서는 worker agent를 시스템 프롬프트 지시문으로 관리한다. 예를 들면 “약한 결과물을 형식적으로 승인하지 말 것”, “후속 작업을 지시하기 전에 먼저 findings를 이해해야 한다. 이해 자체를 다른 worker에게 넘기지 말 것” 같은 문장들이다.
물론 코드베이스가 전부 매끈한 건 아니다. print.ts는 5,594줄짜리 파일이고, 그 안에는 3,167줄짜리 단일 함수 하나가 12단 중첩으로 들어 있다. HTTP에는 Axios를 쓰고 있는데, 바로 얼마 전 npm에서 Axios가 악성 버전으로 오염돼 원격 액세스 트로이 목마를 떨어뜨렸던 시점을 생각하면 이것도 꽤 묘하게 웃기다.
그래서 뭐가 문제냐고?
어떤 사람들은 이걸 두고 Google의 Gemini CLI나 OpenAI의 Codex도 이미 오픈소스 아니냐며 대수롭지 않게 본다. 하지만 그 회사들이 공개한 건 에이전트 SDK, 즉 툴킷이지, 대표 제품의 내부 배선을 통째로 보여주는 소스는 아니었다.
진짜 피해는 코드 그 자체가 아니다. feature flag들이다. KAIROS나 증류 방지 메커니즘처럼 경쟁사가 이제 보고 대응할 수 있는 제품 로드맵 정보가 새어 나간 게 더 크다. 코드는 리팩터링하면 된다. 하지만 전략적 서프라이즈는 한 번 새고 나면 다시 안 샌 상태로 되돌릴 수 없다.
그리고 여기서 더 기가 막힌 건 이 부분이다. Anthropic은 작년 말 Bun을 인수했고, Claude Code는 그 위에서 돌아간다. 그런데 3월 11일에 올라온 Bun 버그(oven-sh/bun#28001)에는, Bun 자체 문서에서는 꺼져 있어야 한다고 하는 source map이 production 모드에서도 제공된다는 내용이 있다. 이 이슈는 아직도 열려 있다. 만약 이게 유출의 원인이었다면, Anthropic은 자기들 제품의 소스 코드를 노출시키는 이미 알려진 버그를 자기들 툴체인으로 그대로 배포한 셈이다.
트위터 답글 하나를 빌리면 이렇다. “소스맵을 실수로 npm에 같이 실어 보냈다는 얘기는 원래는 말도 안 되게 들리지만, 코드베이스의 꽤 큰 부분을 아마 당신이 지금 배포하는 AI가 썼을 거라고 생각하는 순간 갑자기 충분히 그럴듯해진다.”
