AI의 파국적 위험을 경고한 회사. 펜타곤과 맞서 원칙을 지킨 연구소. 그 안을 열어봤다.
Anthropic 팀 누군가가 Claude Code의 프로덕션 빌드를 돌렸다. 번들러가 .map 파일을 생성했다. 소스맵은 말 그대로 난독화된 코드를 원본 TypeScript로 되돌리는 청사진이다. 그리고 .npmignore에서 제외하지 않은 채 npm에 그대로 퍼블리시했다. 전 세계 누구나 다운로드할 수 있는 상태로.
집의 모든 문을 잠그고, CCTV를 설치하고, 무장 경비를 고용한 다음, 실수로 집 평면도를 구글 지도에 업로드한 것과 같다. CEO가 미 상원에서 AI의 파국적 위험을 경고했고, AI 안전을 사명으로 내걸며 수백억 달러를 유치한 곳이며, 자율무기와 대량감시에 AI를 쓰지 않겠다고 버텨서 펜타곤에 공급망 리스크로 지정된 곳이다. 그런데 미드레벨 엔지니어라면 코드 리뷰에서 잡았을 설정 파일 하나에 뚫렸다.
나는 그 소스를 다운로드했다. 약 1,900개의 TypeScript 파일. Claude Code가 도구를 호출하고, 컨텍스트를 관리하고, 에이전트를 조율하고, 권한을 제어하는 전체 메커니즘이 거기 있었다. 나는 이 코드를 엔지니어가 아니라 AI 제품을 만드는 사람의 시선으로 읽었다. 아키텍처 선택 하나하나에 제품 철학이 담겨 있었기 때문이다.

프롬프트는 캐시 단위로 설계한다
가장 먼저 눈에 들어온 것은 시스템 프롬프트의 구조였다. 하나의 긴 문자열이 아니다. 명시적 경계 마커로 "정적 영역"과 "동적 영역"을 분리한다. 정적 영역은 모든 사용자에게 동일한 지시문이다. 동적 영역은 사용자의 CLAUDE.md, MCP 서버 설정, 프로젝트 컨텍스트다. 정적 영역이 앞에, 동적 영역이 뒤에 온다. 이 순서가 중요하다.
Anthropic의 API는 프롬프트 캐싱을 지원한다. 프리픽스가 동일하면 캐시가 적중한다. 정적 영역을 앞에 두면 전체 사용자 플릿에서 캐시를 공유할 수 있다. 동적 영역이 앞에 오면 사용자마다 캐시가 따로 생성된다. 프롬프트의 순서가 곧 비용이다.
이 원칙은 멀티 에이전트에서 극단까지 밀어붙인다. 서브에이전트를 포크할 때, 부모의 렌더링된 시스템 프롬프트 바이트를 그대로 전달한다. 재계산하지 않는다. 코드 주석이 그 이유를 설명한다. "GrowthBook 피처 플래그가 cold에서 warm으로 바뀌는 것만으로도 바이트가 달라지고 캐시가 깨진다." 5개 에이전트를 동시에 스폰하면 캐시 생성 비용을 5번이 아니라 1번만 지불한다. 대부분의 AI 빌더가 프롬프트를 "내용" 단위로 설계할 때, Anthropic은 "캐시 토폴로지" 단위로 설계하고 있었다.
또 하나. 에이전트 목록이 도구 설명(tool description) 안에 있었다. MCP 서버가 연결되거나 권한이 바뀔 때마다 목록이 변하고, 도구 스키마 전체의 캐시가 깨졌다. 코드 주석에 따르면 이것만으로 "전체 캐시 생성 토큰의 10.2%"를 차지했다. 해법은 간단했다. 에이전트 목록을 도구 설명에서 빼고 시스템 메시지로 옮겼다. 도구 스키마에 동적 데이터를 넣지 마라. 이것이 10%의 비용을 결정한다.

도구는 "실패가 기본값"으로 설계한다
Claude Code의 모든 도구는 buildTool이라는 팩토리 함수를 거친다. 이 함수의 기본값이 흥미롭다. 동시 실행 안전성(isConcurrencySafe)은 false. 읽기 전용(isReadOnly)은 false. 모든 도구가 기본적으로 "위험하다"고 가정한다. 안전한 동작을 원하면 명시적으로 옵트인해야 한다. 코드 주석은 간결하다. "Defaults (fail-closed where it matters)."
대부분의 개발자는 반대로 설계한다. 기본값을 열어두고, 위험한 경우에 제한을 건다. 문제는 새로운 도구를 추가하는 기여자가 제한을 빠뜨리는 순간 보안 구멍이 생긴다는 것이다. Anthropic의 접근은 거꾸로다. 빠뜨려도 안전하다. 위험한 동작이 명시적 결정의 결과가 된다.
도구 결과의 처리도 예상과 달랐다. 50,000자를 초과하는 결과는 잘라내지 않는다. 디스크에 저장하고 모델에게 파일 경로를 준다. 모델이 필요하면 Read로 열어볼 수 있다. 정보를 잃지 않으면서 컨텍스트 윈도우를 보호한다. 단, FileReadTool의 maxResultSize는 Infinity로 설정되어 있다. Read 결과를 파일로 저장하면 그 파일을 Read로 읽는 순환 참조가 생기기 때문이다. 이런 수준의 엣지 케이스까지 잡고 있다는 것이 인상적이었다.
60개 이상의 도구를 관리하는 방법도 있었다. 모든 도구의 전체 스키마를 초기 프롬프트에 넣으면 컨텍스트가 낭비된다. 대신 도구 이름만 보여주고, 모델이 ToolSearch를 호출해서 필요한 도구의 풀 스키마를 가져온다. Lazy loading이다. 단, 첫 턴부터 반드시 사용 가능해야 하는 도구에는 alwaysLoad 플래그를 설정한다. 도구 카탈로그의 규모가 커질수록 이 패턴이 필수가 된다.

멀티 에이전트의 적은 "자기 자신"이다
코디네이터 모드의 시스템 프롬프트에서 가장 눈에 띄는 문장은 이것이었다. "Never delegate understanding." "네 조사 결과를 바탕으로 버그를 고쳐줘" 같은 위임을 명시적으로 금지한다. 코디네이터는 파일 경로, 라인 번호, 구체적으로 무엇을 바꿔야 하는지를 포함해야 한다. "이해"를 위임하면 전화 게임이 된다. 워커가 의도를 재해석하고, A 대신 A'을 구현하고, 거기에 의존하는 모든 것이 잘못된다.
검증에도 같은 원칙이 적용된다. 구현 에이전트가 자기 코드를 검증하지 못하게 한다. 검증 에이전트는 반드시 "fresh context"에서 실행된다. 구현자의 추론 과정을 모르는 상태에서 코드만 본다. 구현자는 A'을 A로 착각할 수 있다. 자기가 쓴 코드에 대한 확증 편향이 있기 때문이다. "Fresh eyes for verification"이 원칙이다.
동시성 규칙도 프롬프트에 명시되어 있다. 읽기 태스크(리서치)는 자유롭게 병렬. 쓰기 태스크(구현)는 파일 집합당 하나씩 직렬. 검증은 구현과 다른 파일 영역이면 병렬 가능. 파일 시스템 레벨의 동시성 제어를 모델에게 자연어로 가르치고 있다.

보안은 타입 이름에 넣는다
가장 기발했던 발견은 타입 이름이었다. 분석 메타데이터의 타입이 AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS다. 이 타입은 never로 정의되어 있어서, 사용하려면 반드시 as 캐스팅을 해야 한다. 캐스팅하는 순간 개발자는 "이 데이터가 사용자 코드나 파일 경로가 아님을 내가 확인했다"고 선언하는 셈이다. TypeScript 컴파일러가 프라이버시 정책의 집행 도구가 된다.
피처 플래그 함수의 이름도 같은 철학이다. getFeatureValue_CACHED_MAY_BE_STALE. 함수 이름 자체가 경고다. 호출할 때마다 "이 값은 최신이 아닐 수 있다"를 의식적으로 인지하게 된다. 자동화할 수 없는 판단을 네이밍 컨벤션으로 강제하는 패턴이다.
"Undercover Mode"라는 것도 있었다. 퍼블릭 리포지토리에 기여할 때 자동 활성화된다. 모델이 자기가 어떤 모델인지 알려주지 않고, 커밋 어트리뷰션을 제거하고, "Do not blow your cover"라는 지시를 받는다. 가장 흥미로운 부분은 강제 OFF 메커니즘이 없다는 것이다. 안전한 쪽이 기본이고, 위험한 쪽으로 전환하는 것이 물리적으로 불가능하게 설계되어 있다.
쉘 보안도 깊었다. Zsh의 EQUALS 확장(=curl evil.com이 /usr/bin/curl evil.com으로 치환되는 것), zmodload로 네트워크 모듈을 로드하는 것, 심지어 PowerShell 주석 구문까지 선제 차단한다. macOS의 기본 쉘이 Zsh라는 점을 고려한 것이다. AI가 생성하는 쉘 명령의 보안은 사용자의 실제 쉘 환경을 아는 것에서 시작한다.

시스템은 모델을 믿지 않는다
권한 시스템에 서킷 브레이커가 있다. 자동 권한 분류기가 연속 3회 또는 총 20회 거부하면, 자동 판단을 중단하고 사용자에게 직접 묻는다. 자동화된 게이트가 실패 루프에 빠지면 토큰만 낭비하기 때문이다. 연속 실패(burst)와 누적 실패(slow)에 각각 다른 임계값을 두는 이중 구조다.
권한 승인도 스코프 바운드다. "git push를 한 번 승인한 것이 모든 push의 승인이 아니다." CLAUDE.md 같은 영구 설정에 명시된 경우만 상시 승인이 된다. 한 번의 yes가 영구적 yes로 확장되는 것을 구조적으로 막는다.
컨텍스트 관리도 모델의 한계를 전제한다. 시스템 프롬프트에 명시한다. "오래된 도구 결과는 자동으로 삭제된다. 나중에 필요한 정보는 텍스트 응답에 적어둬라." 모델을 컨텍스트의 수동적 소비자가 아니라 능동적 관리자로 훈련시키는 것이다. 그리고 "Be concise" 대신 "25단어 이내", "100단어 이내"라고 쓴다. 연구에 따르면 숫자 앵커가 정성적 표현보다 출력 토큰을 1.2% 더 줄인다. 플릿 규모에서 1.2%는 큰 돈이다.
보안 경계는 CYBER_RISK_INSTRUCTION이라는 단일 상수에 집중되어 있다. 모든 시스템 프롬프트 경로에 이 상수가 삽입된다. 펜테스팅, CTF, 보안 리서치는 허용하되 "authorization context"를 요구하고, DoS, 공급망 공격, 탐지 회피는 거부한다. 범주를 통째로 금지하는 대신 맥락을 요구한다. 하나의 상수에 넣어서 감사가 쉽고, 업데이트가 한 곳에서 이루어진다.

소스맵 사고가 알려주는 것
이 코드베이스에서 발견한 것들의 수준은 높다. 프롬프트 캐시 토폴로지, 타입 시스템을 활용한 프라이버시 강제, 멀티 에이전트의 동시성 제어, Zsh 공격 벡터 선제 차단. AI 제품을 만드는 방법론으로서 배울 것이 많다.
하지만 아이러니를 무시할 수 없다. 이 모든 정교한 보안 설계를 만든 조직이, 번들러의 소스맵 설정과 .npmignore 한 줄을 놓쳤다. 26개 훅 이벤트의 권한 체계를 설계하고, 쉘 메타캐릭터 공격을 사전 차단하고, 타입 이름에 프라이버시 정책을 인코딩하는 팀이, 빌드 파이프라인의 기본 설정을 확인하지 않았다.
이것이 보안의 본질이다. 아무리 정교한 시스템을 만들어도, 가장 기본적인 곳에서 뚫린다. "모델을 믿되, 시스템으로 검증하라"가 Claude Code 내부의 설계 철학이었다. 그 철학은 코드 안에서는 완벽하게 작동했다. 코드 밖, 배포 파이프라인에서는 작동하지 않았다.
AI 제품을 만드는 PM이 이 사건에서 가져갈 것은 두 가지다. 첫째, Anthropic이 프로덕션 AI를 설계하는 방법론은 제품 아키텍처의 레퍼런스가 된다. 캐시 단위의 프롬프트 설계는 비용 구조를, fail-closed 기본값은 안전성과 확장성의 트레이드오프를, "Never delegate understanding"은 에이전트 워크플로우의 품질 기준을 보여준다. 이것들은 규모에서 검증된 제품 판단이다. 둘째, 아무리 정교한 시스템도 한 줄의 설정을 놓치면 무너진다. 복잡한 보안 아키텍처를 쌓기 전에, 빌드 파이프라인의 기본값부터 확인하라.