5분 AI.
레포 딥다이브 · 2026-08-24

grok-bot-0.18-reconstructed (b-nnett/grok-bot-0.18-reconstructed) 딥다이브
— 출시된 데스크톱 앱 "바이너리"를 읽을 수 있는 소스로 되살린 프로젝트

이 저장소는 공개 배포된 Grok Bot 0.18.0 macOS 앱을 비공식 재구성한 리버스 엔지니어링·연구 프로젝트다. 컴파일된 앱을 뜯어 Electron·호스트·코디네이터·로컬 실행·프로토콜·렌더러 경계를 읽을 수 있는 TypeScript로 되살리고, 그 위에 실험 기능 몇 개를 얹었다 — Cursor·Claude Code·Codex·OpenRouter를 갈아 끼우는 추론 라우터, 원격 샌드박스 대신 쓰는 로컬 Docker VM, 로컬 사용량 집계, 그리고 원본 UI에 녹여 넣은 라우터 설정 화면이다. 핵심 발상은 하나다 — 원본 앱을 "복제 대상"이 아니라 체크섬으로 고정한 빌드 입력으로 취급한다.
(저장소: b-nnett/grok-bot-0.18-reconstructed · 대상 릴리스 Grok Bot 0.18.0 · 상위 번들 ID com.anysphere.sand · Electron 42.1.0 + Node 26.5.x · macOS Apple Silicon 전용 · TypeScript 소스 약 45만 줄(내가 클론해 직접 셈) · 원본 설치본 2종 Git LFS 보존 · 비공식·비제휴 연구물)
먼저 읽고 갈 것 — 이 문서의 성격
이건 "따라 만들어 배포하세요"가 아니라 "이렇게 뜯어보는 규율을 배우세요"다

이 저장소는 독점 소프트웨어 바이너리를 대상으로 한 재구성물이다. 저장소 자신도 NOTICE.md·PROVENANCE.md에서 "상위 소스코드 라이선스는 주장하지 않으며, 공개 재배포 전 저작권·상표·의존성·서비스 약관을 독립적으로 검토하라"고 못 박는다. 이 딥다이브는 학습 목적으로 프로젝트의 구조와 방법론을 해부할 뿐, 원본 앱의 재배포나 상표 도용을 권하지 않는다. 실제로 무언가를 빌드·배포할 생각이라면 권리 검토가 먼저다.

목차
  1. 프로젝트 한 줄 요약
  2. 왜 주목할 만한가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트
  7. 하드웨어·시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"출시된 앱을 뜯어 소스로 되살리고, 그 위에 '내 LLM 로그인을 갈아 끼우는' 라우터를 붙였다"

먼저 "Grok Bot"이 대체 뭔지부터 정리하고 가자. 이름만 보면 xAI의 챗봇 같지만, 뜯어 보면 정체가 드러난다.

단서말해 주는 것
제품명Grok Bot 0.18.0xAI 브랜드로 배포된 데스크톱 에이전트
번들 IDcom.anysphere.sand만든 곳은 Anysphere(=Cursor 제작사), 내부 코드네임은 "Sand"
DMG 주소downloads.cursor.com/grokbot/…Cursor의 배포 인프라를 그대로 탄다
프레임워크Electron 42.1.0웹 기술로 만든 데스크톱 앱

즉 Grok Bot은 Cursor를 만든 Anysphere의 "Sand" 에이전트 기술을 xAI 브랜드로 화이트라벨한 데스크톱 앱이다. 이 저장소는 그 앱의 공개 macOS 빌드를 받아 역컴파일·재구성한 결과물이고, 이름의 -reconstructed가 그 뜻이다.

용어
화이트라벨 (white-label)
A사가 만든 제품을 B사가 자기 브랜드를 붙여 파는 것. 여기선 Anysphere(Cursor)가 만든 "Sand" 데스크톱 에이전트가 "Grok Bot"이라는 xAI 브랜드로 나왔다. 번들 ID·배포 도메인 같은 내부 흔적이 원제작자를 드러낸다.

재구성의 결과물은 하이브리드 앱이다. 저장소가 스스로 정리한 표현을 옮기면 이렇다.

구성 요소이 저장소에서의 처리
애플리케이션 런타임(Electron main·host·coordinator)source/읽을 수 있는 소스에서 컴파일
출시된 렌더러(UI)원본의 minified 청크를 그대로 유지(해시 고정)
라우터 설정 화면원본 UI에 좁고 결정론적인 패치 한 겹만 추가
번들 정체성·서명별도 번들 ID + ad-hoc 서명(원본 서명은 승계하지 않음)
한눈에 이해하는 비유

"단종된 자동차를, 정비 매뉴얼 없이 실물 한 대만 놓고 도면부터 다시 그린 복원 프로젝트"

공장이 문을 닫아 설계도가 없다. 손에 있는 건 완성차 한 대뿐. 복원가는 차를 분해하며 "이 배선은 이런 신호를 보내는구나"를 알아내 엔진·변속기 도면을 새로 그린다(→ source/의 런타임 재구성). 하지만 차체 도장은 원본이 너무 정교해서, 다시 칠하는 대신 원래 껍데기를 그대로 얹는다(→ 출시 렌더러 유지). 대신 계기판에 스위치 하나만 새로 판다(→ 라우터 설정 패치).

그리고 복원가는 규칙을 세운다 — "실물에서 확인되지 않는 부품은 절대 상상으로 만들어 넣지 않는다." 이 규칙(증거 기반 재구성)이 이 프로젝트를 취미 클론과 구분 짓는 지점이다.

얹은 실험 기능의 백미는 추론 라우터다. 설정 → Router에서 새 대화가 어느 백엔드로 갈지 고른다.

제공자인증 방식도구 지원
Cursor(기본)기존 Grok Bot/Cursor 세션네이티브 Grok Bot 도구·플러그인
Claude Code이미 로그인된 로컬 Claude Code라우팅된 Grok Bot MCP 도구
Codex이미 로그인된 로컬 ChatGPT/CodexDirect Responses 트랜스포트 + Grok Bot 도구
OpenRouter데스크톱 비밀 저장소에 넣은 API 키Grok Bot 도구 실행 루프

Claude Code·Codex는 별도 API 키가 필요 없다 — 이미 로컬에 로그인돼 있는 세션을 그대로 재사용한다. 이 "남의 로그인 재활용"이 어떻게 가능한지가 4장의 핵심 볼거리다.

2왜 주목할 만한가

"재구성의 '방법론'이 결과물만큼 값지다 — 추측 금지 · 체크섬 고정 · 원본 최소 훼손"

"Electron 앱 뜯어봤다"류 글은 흔하다. 이 저장소가 공부 자료로서 특별한 건 결과물이 아니라 규율 때문이다. 네 가지가 겹친다.

① "증거 기반 재구성"이라는 성문화된 규칙

PROVENANCE.mdEvidence-only reconstruction rule이라는 절이 있다. 요지는 이렇다 — 되살린 소스는 "검사 가능한 아티팩트 앵커가 최소 하나 이상 뒷받침하는 동작"만 표현할 수 있다. 앵커의 예: 방출된 코드나 소스 경로 마커, 추출된 소스맵, 출시된 문자열·에셋·CSS, 렌더러 DOM 시그니처, IPC/RPC 계약, 또는 실행 중인 런타임의 반복 관측.

쉽게 말하면

법정에서 "정황상 그랬을 것"은 증거로 안 쳐 준다. 이 프로젝트도 똑같다. "이 화면은 이렇게 생겼을 것 같다"로 UI를 지어내는 걸 릴리스를 막는 결함(release-blocking defect)으로 규정한다. 타입체크·빌드가 통과하는 것만으로는 출처 증명이 안 되고, 증거가 부족하면 "추측"이 아니라 "미확인(unmapped)"으로 남긴다.

이 규율은 매니페스트에까지 박제돼 있다. manifests/reconstruction/renderer-closure.json은 재구성한 프런트엔드가 출시 번들을 "기능 손실 없이" 대체할 수 있음을 증명하면서도, 703개의 JSX 런타임 후보는 1차 증거에 연결되지 않아 "기능으로도, 서드파티 면제로도 취급하지 않는다"고 정직하게 남겨 둔다. 모르는 걸 안다고 하지 않는 태도가 파일로 강제된다.

② 결정론적 재현 툴체인

이 프로젝트는 "소스를 읽었다"에서 멈추지 않고, 그 소스를 다시 동작하는 앱으로 빌드한다. 그것도 재현 가능하게. 원본 DMG를 SHA-256으로 고정해 내려받아 검증하고, 그 안의 app.asar도 다시 해시로 확인한 뒤, 재구성 런타임을 컴파일해 얹고, 새 번들 ID로 ad-hoc 서명한다. "실물을 안 건드리고 도면만으로 같은 차를 다시 조립할 수 있는가"를 실제로 증명하는 셈이다(자세히는 4장).

③ 이미 로그인된 CLI 세션을 재활용하는 라우터

대부분의 "멀티 LLM" 앱은 제공자마다 API 키를 새로 받는다. 이 프로젝트는 다르다. 사용자의 로컬 머신에 이미 설치·로그인된 Claude CodeCodex를 찾아내 그 인증을 그대로 쓴다. Claude Code는 공식 @anthropic-ai/claude-agent-sdk로 호출하고, Codex는 로컬 로그인의 OAuth 토큰으로 ChatGPT 백엔드를 직접 친다. 키 관리 없이 "내가 이미 쓰는 도구"를 데스크톱 UI 안으로 끌어오는 구성이다.

④ "무엇을 새로 만들고, 무엇을 원본에서 남기는가"의 경계가 명확

이 저장소는 UI를 통째로 재현하려는 무모함을 피했다. 출시된 렌더러는 소스맵 없는 minified 프로덕션 번들이었고, 저자는 "그걸 같은 완성도로 재현하는 건 주말 프로젝트로 될 일이 아니다"라고 솔직히 적는다. 그래서 런타임·제어 평면만 재구성하고 렌더러는 해시로 고정한 채 유지하되, 라우터 설정만 최소 패치로 얹었다. 오픈코어처럼 경계를 긋는 판단 자체가 배울 점이다.

배울 점
"정직한 불완전함"이 과장된 완결성보다 낫다

이 프로젝트의 문서들은 한계를 숨기지 않는다 — "단일 pinned 릴리스만 대상", "외부 제공자 세션에 의존", "미래 버전 호환 보장 안 함", "renderer는 부분 재구성", "npm audit 경고가 남아 있음". 리버스 엔지니어링·재구성 작업에서 신뢰는 "완벽하다"는 주장이 아니라 "어디까지 확인했고 어디부터 모르는지"를 정밀하게 표시하는 것에서 나온다. 이 저장소 전체가 그 시범이다.

3기술 스택 전체 지도

"Electron(껍데기) + Node 런타임(두뇌) + ConnectRPC(신경) + AI SDK 3종(입) + 재현 툴체인(공장)"

이 저장소는 크게 세 덩어리다 — ① source/의 앱 런타임(재구성), ② frontend/의 렌더러 재구성 워크스페이스, ③ scripts/의 부트스트랩·빌드·검증 툴체인. 먼저 소스 규모를 직접 세어 보면(shallow clone 후 실측):

영역파일 / 줄 수맡은 일
source/packages/852개 / 337,194줄재구성된 내부 패키지 모노레포(agent·chat-inference·mcp-core·redaction…)
source/host/471개 / 64,835줄추론·도구·MCP·설정·턴 실행 (앱의 두뇌)
source/shared/165개 / 16,195줄공유 계약·설정·프로토콜·제공자 헬퍼
source/electron-main/184개 / 16,348줄데스크톱 수명주기·설정·인증·박스 커넥터·RPC 핸들러
source/node-agent-coordinator/24개 / 3,386줄트랜스크립트 라우팅·스트리밍·라우팅 MCP 브리지
source/electron-preload/16개 / 1,622줄UI에 노출되는 좁은 신뢰 브리지
frontend/약 280개(.ts/.tsx)읽을 수 있는 React 렌더러 재구성 + 디자인 워크스페이스

줄 수의 대부분(packages/ 33만 줄)은 재구성된 내부 라이브러리들이라는 걸 기억해 두자. 앱의 "새로 쓴 두뇌"는 host/·electron-main/·coordinator/ 쪽이다.

런타임 — Electron 42.1 + Node 26.5

package.json의 의존성은 이 앱이 무엇을 하는지 그대로 보여 준다. 성격을 말해 주는 것만 골라 보면:

패키지버전역할
electron42.1.0데스크톱 런타임(원본과 ABI 일치가 관건)
@anthropic-ai/claude-agent-sdk^0.1.76Claude Code 제공자 호출
@ai-sdk/openai + ai1.3 / 4.3.17OpenRouter 제공자 스트리밍(streamText)
@connectrpc/connect(-node) + @bufbuild/protobuf1.6.1 / 1.10.1coordinator ↔ host RPC + protobuf
@tiptap/* (약 30개)3.14.0리치 텍스트 에디터(멘션·플러그인 입력)
tree-sitter + web-tree-sitter0.21코드 파싱(네이티브 + WASM 양쪽)
@statsig/js-client^3.31실험·피처 플래그
@opentelemetry/* (약 10개)2.x추적·메트릭(재구성 빌드에선 기본 off)
undici · ws5.29 / 8.20HTTP 클라이언트 · WebSocket
pdfjs-dist · @jimp/* · katexPDF·이미지·수식 처리(첨부·렌더)

빌드·재구성 툴체인 — devDependencies가 곧 "공장"

이 저장소의 개성은 devDependencies에 더 진하게 드러난다. 앱을 "굴리는" 것과 "되살리는" 도구가 섞여 있다.

도구버전재구성에서의 쓰임
@electron/asar4.2.1출시 app.asar 해체·재포장
esbuild0.28.2재구성 런타임(TS) 번들링
acorn + acorn-walk8.x출시 렌더러 JS를 AST로 정적 분석(패치 지점 찾기)
vite + @vitejs/plugin-react8.2.1 / 6.0.5frontend/ 렌더러 재구성 빌드
typescript7.0.2렌더러/런타임 두 프로젝트 타입체크
node-gyp · node-addon-apitree-sitter 등 네이티브 애드온 Electron ABI로 재빌드
sirv3.0.2정적 서빙(스모크·개발)
배울 점
"파서로 코드를 고친다" — 문자열 치환이 아니라 AST 패치

출시된 렌더러는 사람이 못 읽는 minified 코드다. 여기에 설정 화면을 끼워 넣으려고 acorn(자바스크립트 파서)을 쓴다. 정규식으로 문자열을 바꾸면 minify가 바뀔 때마다 깨지지만, AST(구문 트리) 수준에서 특정 지점을 찾아 삽입하면 훨씬 견고하고, 무엇을 바꿨는지 해시로 기록·검증할 수 있다. "코드를 데이터가 아니라 구조로 다룬다"는 발상은 코드모드(codemod)·린터·번들러가 공유하는 핵심 기법이다.

원본 설치본 2종은 Git LFS로 저장소에 보존된다(.gitattributes*.dmg·*.exe를 LFS로 지정). 다만 추출한 앱 본체·빌드 산출물·크레덴셜·대형 포렌식 작업물은 커밋하지 않는다.gitignore.cache·.build·dist·src/app/dist·recovered·recovery를 막는다. 저장소엔 "검토된 재구성 + 재현 도구"만 남는다.

4아키텍처 심화 분석

"런타임은 갈아 끼우고, 렌더러는 해시로 고정하고, 검증은 세 겹으로"

런타임 경계 — 신호가 흐르는 다섯 층

실행 중 앱은 다섯 개의 경계로 나뉜다. 위에서 아래로 신뢰 수준이 좁아진다.

┌──────────────── 출시된 렌더러 (React 19, minified) ────────────────┐ │ 원본 UI 그대로 + [라우터 설정] 패치 한 겹 │ └───────────────┬────────────────────────────────────▲───────────────┘ │ preload 브리지 (좁은 신뢰 통로) │ 이벤트/스트림 ▼ │ ┌──────────────── Electron main ────────────────────────────────────┐ │ 설정 · 비밀 · 인증 · 플러그인 수명주기 │ │ ├─ 원격 box 커넥터 ─────────────┐ │ │ └─ 소유형 로컬 Docker 커넥터 ────┤ (설정에서 택1) │ └───────────────────────────────────┼────────────────────────────────┘ ▼ ┌──────────────── coordinator + host ───────────────────────────────┐ │ 트랜스크립트 라우팅 · 스트리밍 · 리액션 · 라우팅 MCP 브리지 │ │ inference router │ │ ┌───────────────┬───────────────┬──────────────┐ │ │ Cursor Claude Code Codex OpenRouter │ │ (원격 기본) (Agent SDK) (Direct Responses) (streamText) │ │ │ │ │ Grok Bot MCP 도구 (라우팅) │ └────────────────────────────────────────────────────────────────────┘

coordinator와 host는 ConnectRPC + protobuf로 대화한다. 이 RPC 계약이 재구성의 앵커 중 하나다 — 출시 바이너리에서 관측된 메서드·메시지 형태가 소스를 되살릴 근거가 된다.

설계의 중심축 ① — 원본을 "빌드 입력"으로 고정하는 파이프라인

이 프로젝트에서 가장 배울 게 많은 부분이다. npm run bootstrap → check → package → verify 네 단계가 "실물을 안 건드리고 도면만으로 같은 앱을 다시 조립"하는 흐름을 이룬다.

① bootstrap 원본 DMG 확보 → SHA-256 검증 → hdiutil 마운트 → ditto로 .app 추출 → 버전(0.18.0) 확인 → app.asar SHA-256 재검증 → dist/만 src/app/dist 로 추출 = "불변의 증거 기준선"(절대 수정 안 함, 읽고 해시만) │ ② check tsc(렌더러+런타임) + node --test → 감사 3종 실행(렌더러 클로저·UI 출처·런타임 조성) │ ③ package esbuild로 clean 런타임 컴파일(결정론적 배너) → 렌더러는 바이트 그대로 유지 + 해시 인벤토리 작성 → [라우터 설정] 좁은 패치 주입(패치 전후 해시 기록) → app.asar 재포장(무결성 스냅샷·롤백) → ditto 복사 → xattr -cr(격리 제거) → asar 교체 → plutil로 Info.plist 재작성(새 번들 ID·URL스킴, ElectronAsarIntegrity 제거) → ad-hoc 서명 │ ④ verify 포장된 .app을 다시 열어 내장 매니페스트와 전 파일 해시를 독립 재대조 + 서명·정체성 확인

세 겹의 방어가 겹쳐 있다는 게 핵심이다. 입력은 해시로 고정하고(DMG·app.asar·원본 셸), 산출물은 전부 해시로 기록하고(reconstruction-build.json 등), 마지막에 포장물을 다시 열어 그 기록과 독립적으로 재대조한다. 여기서 "해시 기록"은 문서가 아니라 합격 판정 그 자체다 — 검증기가 그 기록을 계약으로 삼아 어긋나면 빌드를 떨어뜨린다.

용어
ASAR
Electron 앱이 소스 파일들을 하나로 묶는 아카이브 포맷(app.asar). tar 비슷하게 여러 파일을 이어 붙이고 오프셋으로 찾는다. 이 프로젝트는 원본 asar를 해체(extract)해 증거로 읽고, 재구성 런타임으로 재포장(pack)한다. 포장 시 @electron/asar의 무결성 해시를 대조해 중간 변조를 잡는다.

설계의 중심축 ② — 렌더러는 "다시 안 그리고" AST로 한 지점만 판다

UI를 통째로 재현하지 않는 대신, minified 렌더러 청크에 라우터 설정 패널만 끼워 넣는다. 방식이 정교하다. 정규식 치환이 아니라 exact-once 앵커 수술이다.

// scripts/lib/router-renderer-patch.mjs (개념)
// ① 설정 탭 레지스트리가 든 청크는 "정확히 하나"여야 한다
// ② 패널 앵커가 든 청크도 "정확히 하나"여야 한다
// replaceExactlyOnce: 앵커가 없거나(missing) 여럿이면(ambiguous) 던진다

// 세 지점만 건드린다:
//  - const wDn=[...]  설정 탭 배열에 {id:"router",label:"Router"} 삽입
//  - x==="router" ? a.jsx(RRouterPanel,{}) ...  패널 스위치
//  - function Sa(s){ 앞에 RRouterPanel/RBoxRuntime 등 (이미 minify된) 컴포넌트 prepend
배울 점
"조용히 잘못 붙느니 시끄럽게 실패하라"

패치가 앵커를 딱 한 번만 찾아야 적용된다는 규칙이 핵심이다. 원본이 다음 버전에서 조금이라도 바뀌면 앵커가 사라지거나 둘로 늘어 패치가 즉시 예외로 죽는다 — 엉뚱한 곳에 잘못 붙어 조용히 망가지는 것보다 훨씬 낫다. 게다가 삽입 코드가 원본 번들의 minify된 식별자(a.jsx·de.useState 등)를 그대로 재사용해 원본과 "합쳐지되 대체하지 않는다". 패치 전후 청크의 바이트 수·SHA-256이 renderer-router-extension.json에 기록돼 검증 때 재확인된다.

설계의 중심축 ③ — "서명은 안 베끼고, 구조가 같다는 것만 증명"

가장 영리한 트릭이 여기 있다. 재구성 셸이 정품과 구조적으로 동일함을 증명하되, 정품의 서명된 바이너리를 복사하지는 않는다. macos-shell-invariant.mjs는 arm64 Mach-O를 직접 파싱해 __LINKEDIT 오프셋과 코드서명 로드 커맨드를 0으로 지운 뒤 나머지를 해시한다.

쉽게 말하면

"이 지폐가 진짜와 같은 판형·용지·도안인가"는 확인하되, 일련번호와 위조방지 홀로그램(=서명) 부분만 가리고 대조하는 것과 같다. 그래서 reconstructedHash !== officialShellHash(전체는 다름 — 서명이 없으니까)이면서도 서명 부분을 뺀 구조는 일치함을 증명할 수 있다. 정품 서명을 승계하지 않고 ad-hoc 서명만 붙이면서도 "껍데기는 동일한 42.1 런타임"임을 검증하는, 재구성 특유의 문제를 정확히 푼 방법이다.

설계의 중심축 ④ — 추론 라우터의 실제 동작

이제 1장에서 예고한 "남의 로그인 재활용"을 뜯어보자. 라우터는 제공자별 어댑터로 나뉘고, 로컬 트랜스크립트를 별도로 관리하며, 에이전트별 직렬 큐로 턴을 처리한다.

// inference-router.ts — 턴 번호를 원격+로컬 양쪽에서 계산해 충돌 방지
const turn = Math.max(remoteTurn, localTurn) + 1;
const userEntry = { id: `t${turn}u`, role: "user", content: prompt, ... };

// 활동 표시(“생각 중…”)를 250ms 간격으로 펄스해 유지
const pulse = setInterval(publishRunning, 250);
// 출시된 가상 트랜스크립트가 활동 행을 만드는 데 ~350ms 걸리므로
// 첫 토큰 전에 1200ms를 의도적으로 기다려 “작성 중” 상태가 눈에 보이게 한다
await new Promise(r => setTimeout(r, 1_200));

이 두 줄(250ms 펄스 · 1200ms 지연)은 기능이 아니라 "원본 UI의 착시를 존중하는" 코드다. 원본 렌더러는 첫 어시스턴트 토큰이 오는 순간 활동 행을 지우도록 만들어져 있는데, 직접 제공자는 그 첫 토큰이 너무 빨리 와서 "작성 중" 상태가 인지되지 않는다. 그래서 로컬 상태를 잠깐 권위 있게 유지한다 — 증거 기반 재구성이 UX 디테일까지 파고든 사례다.

Codex — 로컬 로그인으로 ChatGPT 백엔드를 직접 친다

Codex 제공자는 CLI 바이너리를 실행 경로에 두지 않는다. 대신 ~/.codex/auth.json의 OAuth 토큰을 읽어 chatgpt.com/backend-api/codex/responses직접 호출한다. 파일 권한이 남에게 열려 있으면(mode & 0o077) 거부하고, 401이 오면 리프레시 토큰으로 조용히 재발급한다.

// provider-session.ts — 401이면 refresh 후 1회 재시도
let result = await perform();          // Bearer + ChatGPT-Account-Id 헤더
if (result.status !== 401) return result;
credentials = await refreshCodexCredentials(credentials);  // auth.openai.com/oauth/token
return await perform();

Claude Code — 공식 Agent SDK로, MCP 도구까지 라우팅

Claude Code 제공자는 @anthropic-ai/claude-agent-sdkquery()를 쓴다. 여기에 Grok Bot의 플러그인 도구를 붙이는 방식이 재밌다 — 루프백 HTTP MCP 브리지를 띄우고 그 URL만 SDK에 넘긴다.

// routed-mcp-bridge.ts — 127.0.0.1에 임의 포트 + 비밀 경로
const secret = randomUUID();
server.listen(0, "127.0.0.1", ...);   // /mcp/<secret> 만 응답, 그 외 404
return { url: `http://127.0.0.1:${port}/mcp/${secret}`, close };

브리지는 도구 이름·설명을 훑어 읽기 전용 휴리스틱(read/search/list/get… 은 있고 send/create/delete… 는 없으면 read-only)으로 MCP annotations(readOnlyHint·destructiveHint)를 자동으로 채운다. Claude Code가 도구의 위험도를 판단할 근거를 만들어 주는 것이다.

설계의 중심축 ⑤ — 원격 대신 쓰는 로컬 Docker 샌드박스

원본 앱은 코드 실행을 원격 "box"에 맡긴다. 이 프로젝트는 그 자리에 소유형 로컬 컨테이너를 끼운다. 안전장치가 촘촘하다.

장치구현
콘텐츠 주소화 런타임host 런타임을 SHA-256으로 해시해 <sha>-<sha>/ 경로에 스테이징. 바이트가 다르면 거부
소유권 확인컨테이너 라벨 com.grok-bot.local-vm=1 없으면 건드리지 않음(남의 컨테이너 보호)
루프백 전용모든 포트를 127.0.0.1에만 바인딩
인증 read-only 마운트~/.codex·~/.claude읽기 전용으로만 마운트
스키마 버전 게이트런타임 해시·스키마 버전이 안 맞으면 컨테이너를 강제 교체
게이트웨이 준비 대기토큰 인증된 /health가 뜰 때까지 최대 3분, 아니면 로그와 함께 실패

설계의 중심축 ⑥ — 정직한 안전 봉합선(seam)들

재구성 빌드는 원본의 자동 업데이트·Sentry·텔레메트리를 패키징 경계에서 끈다. 방식은 멱등한 환경변수 prepend 하나다.

// build-asar.mjs — electron-main 폴백 앞에 붙는 가드(멱등)
process.env.SAND_DISABLE_UPDATES  ??= "1";
process.env.SAND_DISABLE_SENTRY   ??= "1";
process.env.SAND_DISABLE_TELEMETRY??= "1";

런타임을 "clean 소스에서 컴파일"로 승격하는 것도 fail-closed다. electron-main은 16개, host는 8개의 바인딩 슬롯을 선언해 두고, 각 슬롯이 원본 main.cjs의 특정 줄·문자열(앵커)과 clean 소스 모듈에 모두 연결될 때만 clean으로 인정한다. 하나라도 미연결이면 "증거 부족"으로 남기고 원본 바이트 폴백(+가드)을 쓴다 — 그래서 electron-main은 기본 clean, host는 기본 폴백이다. 이 경계는 숨겨진 구멍이 아니라 문서화된 상태다.

배울 점 — 이 저장소 전체를 관통하는 원칙
"모르는 건 모른다고 파일에 적는다"

렌더러 클로저 감사는 clean 프런트엔드가 출시 번들을 기능 손실 없이 대체할 수 있음을 증명하면서도(308 모듈·275 도달·5 기능면·11 라우트·발견 0), 703개의 JSX 후보는 1차 증거에 연결되지 않았다고 명시하고 이를 "복구됨"으로도 "누락"으로도 세지 않는다. 이 "불확실성을 정직하게 집계"하는 태도가 UI 출처 감사(audit-ui-provenance.mjs: 모든 화면 문자열·셀렉터·라우트가 증거 앵커를 가져야 함)와 함께, 추측을 릴리스 결함으로 취급하는 규칙을 기계적으로 강제한다.

5디렉토리 구조 해부

"소스(재구성) · 프런트엔드(재구성 워크스페이스) · 스크립트(공장) · 매니페스트(계약) · 아카이브(보존)"
grok-bot-0.18-reconstructed/ ├── source/ ← 앱 런타임 재구성 (읽을 수 있는 TS) │ ├── electron-main/ 데스크톱 수명주기·설정·인증·box 커넥터·RPC │ │ └── box/local-docker-host-connector.ts ← 로컬 Docker 샌드박스 │ ├── electron-preload/ UI에 노출되는 좁은 신뢰 브리지 │ ├── host/ 추론·도구·MCP·턴 실행 (두뇌, 471파일) │ │ └── extensions/inference/ provider-session.ts · codex-direct-responses.ts │ ├── node-agent-coordinator/ 트랜스크립트 라우팅·스트리밍 │ │ ├── inference-router.ts ← 라우터 본체 │ │ └── routed-mcp-bridge.ts ← 루프백 MCP 브리지 │ ├── shared/ 공유 계약·설정·프로토콜·제공자 헬퍼 │ │ └── node/inference-router-local.ts ← CLI 로그인 탐지 │ ├── packages/ 재구성된 내부 패키지 모노레포(852파일) │ ├── box-exec-daemon/ · local-exec-daemon/ 실행 데몬 │ └── internal/ ├── frontend/ ← 렌더러 재구성 + 디자인 워크스페이스 │ └── src/{production, recovered, dev}/ 패치·증거복구·개발셸 ├── scripts/ ← 부트스트랩·빌드·패치·검증 (.mjs 20개) │ ├── bootstrap-runtime.mjs · package-macos.mjs · verify.mjs │ ├── audit-{renderer-closure,runtime-composition,ui-provenance}.mjs │ └── lib/ config·clean-build·asar-integrity· │ router-renderer-patch·macos-shell-invariant ├── manifests/reconstruction/ ← 빌드가 계약으로 삼는 JSON 3종 ├── research-archives/original/0.18.0/ ← 원본 설치본(Git LFS) + 해시 목록 ├── patches/ 서드파티 패치(@connectrpc/connect) ├── tests/ 8개 회귀 테스트(.test.mjs) ├── docs/{ARCHITECTURE,PUBLISHING}.md ├── PROVENANCE.md · NOTICE.md · SECURITY.md · CONTRIBUTING.md · README.md └── package.json · .gitattributes(LFS) · .node-version

구조를 읽는 열쇠는 세 가지 성격의 폴더가 섞여 있다는 점이다.

성격폴더본질
재구성 결과물source/ · frontend/바이너리에서 되살린 "읽을 수 있는 소스"
재현 도구(공장)scripts/ · patches/소스 + 원본 → 동작하는 앱으로 조립·검증
증거·계약manifests/ · research-archives/ · tests/무엇이 clean이고 무엇이 폴백인지, 해시가 맞는지 박제

frontend/src/의 세 하위 폴더가 특히 재구성 철학을 보여 준다 — production/(원본에 얹는 좁은 패치 컴포넌트), recovered/(증거로 되살린 기능들, features/settings·terminal·permissions 등 폴더별 정리), dev/(DOM 인스펙터가 든 개발 셸). "확인된 것"과 "패치한 것"과 "실험 중인 것"이 폴더로 분리돼 있다.

6학습 포인트

"이 저장소에서 실제로 가져갈 수 있는 기술 — 리버스 엔지니어링 규율부터 라우터 설계까지"

① 리버스 엔지니어링을 "규율 있는 공학"으로 하는 법

가장 큰 배움은 코드가 아니라 태도다. 증거 앵커 없이는 아무것도 주장하지 않기, 불확실성을 파일에 집계하기, "타입체크 통과 ≠ 출처 증명"으로 선 긋기, 추측을 릴리스 결함으로 취급하기. 이 규칙들이 감사 스크립트로 기계화돼 있어, 원칙이 말이 아니라 CI로 강제되는 걸 직접 볼 수 있다.

② Electron 앱의 해부학

app.asar 구조, main/preload/renderer 3층 분리, preload가 왜 "좁은 신뢰 브리지"인지, Info.plist의 번들 정체성(CFBundleIdentifier·CFBundleURLTypes·ElectronAsarIntegrity), 그리고 네이티브 애드온을 Electron ABI에 맞춰 재빌드하는 이유. 데스크톱 앱을 만들거나 분석할 때 그대로 쓰이는 지식이다.

③ 결정론적·재현 가능 빌드

체크섬 고정 입력, 해시 기록 산출물, 독립 재검증의 3단 구조. import.meta.url을 배너 상수로 define해 ESM 소스를 바이트 재현 가능한 CJS로 뽑는 트릭, ASAR 재포장 시 무결성 스냅샷·롤백, git 트리 오브젝트 비교로 "무손실 공개"를 증명하는 법까지. 공급망 신뢰가 화두인 지금 값진 패턴이다.

④ 멀티 제공자 추론 라우터 설계

제공자별 어댑터(각기 다른 스트리밍 API를 하나의 fullStream으로 통일), 에이전트별 직렬 큐, 로컬/원격 트랜스크립트 병합, 턴 번호 충돌 회피, OAuth 토큰 재사용과 401 리프레시, 루프백 MCP 브리지. "여러 LLM을 한 UI에 붙이는" 앱을 만든다면 이 파일들이 교과서다.

실습 아이디어 (가볍게)

먼저 손에 익힐 것

ⓐ 아무 Electron 앱(VS Code 등)의 app.asarnpx @electron/asar extract로 풀어 구조를 눈으로 본다. ⓑ inference-router.ts의 턴 번호 계산과 250ms/1200ms 타이밍을 읽고 "왜 이렇게까지 하나"를 스스로 설명해 본다. ⓒ routed-mcp-bridge.ts의 read-only 휴리스틱 정규식을 종이에 적고 오탐/미탐 사례를 찾아본다.

7하드웨어·시스템 요구사항

"macOS Apple Silicon 전용 · Node 26.5 · Git LFS 필수 · Docker는 옵션"
항목요구사항비고
OS·CPUmacOS + Apple Silicon(arm64)단일 pinned 릴리스(0.18.0/arm64)만 대상. 패키징은 macOS에서만 가능
Node.js26.5.x (>=26.5.0 <27).node-version로 고정. esbuild 타깃은 node22
Xcode CLT필요네이티브 애드온(tree-sitter) 재빌드·코드 서명 도구
Git LFS필수원본 설치본(DMG 149MB·EXE 120MB)이 LFS 포인터로 들어 있음. git lfs pull 안 하면 부트스트랩이 공개 URL로 폴백
디스크수 GB 여유원본 앱 추출본 + Electron 캐시 + 빌드 산출물
Docker옵션로컬 샌드박스를 켤 때만. Docker Desktop 등 실행 중이어야 함
GPU불필요추론을 직접 하지 않고 외부 제공자(Cursor·Claude·Codex·OpenRouter)를 호출
제공자 인증선택적Claude Code·Codex 라우팅은 해당 CLI가 로컬에 로그인돼 있어야. OpenRouter는 API 키
주의
"단일 릴리스 타깃"의 의미

이 재구성은 정확히 Grok Bot 0.18.0 / macOS arm64 하나에 맞춰져 있다. 다른 버전·플랫폼에서는 앵커·해시가 어긋나 부트스트랩부터 실패한다. 이건 결함이 아니라 의도된 범위다 — "미래 버전 호환을 약속하지 않는다"고 명시돼 있다. 재구성 프로젝트는 원래 이렇게 한 지점을 깊게 판다.

8직접 해볼 수 있는 실습 과제

"난이도 5단계 — asar 뜯어 읽기부터 나만의 '증거 기반 재구성'까지"
전제 — 반드시 먼저 읽을 것
남의 독점 바이너리를 재배포하지 말 것

아래 과제는 학습·분석 목적이다. 앱을 뜯어 읽고 이해하는 것과, 재구성물을 배포·상표 도용하는 것은 전혀 다른 문제다. 이 저장소도 "상위 라이선스는 주장하지 않으며 재배포 전 권리 검토 필수"라고 못 박는다. 실험은 본인 소유 기기에서, 실제 크레덴셜은 쓰지 말고(테스트 계정), 결과물을 공개 배포하지 말 것.

과제 1난이도 ★☆☆☆☆ · 1~2시간

부트스트랩·검증 파이프라인을 코드로 추적하기

이 저장소를 클론(macOS 아니어도 됨 — 읽기만)해 scripts/bootstrap-runtime.mjspackage-macos.mjsverify.mjs를 순서대로 읽는다. 목표는 실행이 아니라 "해시가 어디서 고정되고 어디서 재대조되는지"를 화살표로 그려 보는 것. DMG SHA-256 → app.asar SHA-256 → 산출물 해시 기록 → verify의 재해시까지 한 장의 다이어그램으로 정리해 보라.

과제 2난이도 ★★☆☆☆ · 반나절

아무 Electron 앱의 asar를 뜯어 "증거"를 수집하기

본인이 쓰는 오픈소스 Electron 앱(예: VS Code)의 app.asarnpx @electron/asar extract로 풀고, main/preload/renderer 경계를 찾아본다. 그다음 이 저장소의 evidence-only 규칙을 흉내 내어, "이 화면 문자열은 어느 파일의 몇 번째 줄에서 나오는가"를 3개만 앵커로 기록해 본다. 리버스 엔지니어링의 "증거 앵커"가 실제로 어떤 모양인지 감이 온다.

과제 3난이도 ★★★☆☆ · 1~2일

라우터에 새 제공자 어댑터 붙이기(개념 구현)

provider-session.tscodexExecutor/claudeExecutor/openRouterExecutor서로 다른 스트리밍 API를 어떻게 같은 fullStream 인터페이스로 통일하는지 정리한 뒤, 로컬 ollama(OpenAI 호환 /v1)를 향한 다섯 번째 어댑터를 스케치한다. 실제 앱 빌드 없이 별도 Node 스크립트로 어댑터만 떼어 스트리밍이 도는지 확인해도 좋다. 확장: onUsage 콜백으로 토큰 사용량 집계가 어떻게 흐르는지 따라가 본다.

과제 4난이도 ★★★★☆ · 2~3일

라우팅 MCP 브리지의 read-only 휴리스틱 개선·테스트

routed-mcp-bridge.tsisReadOnly() 정규식은 이름·설명에 read/search/get이 있고 send/create/delete가 없으면 읽기 전용으로 본다. 이 휴리스틱이 틀리는 경우("read_and_delete", "get_or_create" 같은 이름)를 10개 만들어 표로 정리하고, 오탐을 줄이는 개선안을 node --test로 검증하는 테스트를 작성한다. MCP annotations(readOnlyHint·destructiveHint)가 왜 중요한지 체감할 수 있다.

과제 5난이도 ★★★★★ · 1~2주

나만의 미니 "증거 기반 재구성" 만들기

아주 작은 오픈소스 Electron 앱을 골라, 이 저장소의 방법론을 축소해 재현한다 — ⓐ 원본을 SHA-256으로 고정해 "빌드 입력"으로 삼고, ⓑ 한 파일만 clean 소스로 다시 써서 얹고, ⓒ 나머지는 원본 바이트 그대로 유지하며, ⓓ 산출물 해시를 JSON에 기록해 ⓔ 별도 스크립트로 재대조한다. "무엇을 재구성하고 무엇을 유지하는가"의 경계를 직접 그어 보는 게 목표다. 여기까지 하면 리버스 엔지니어링·재현 빌드·공급망 무결성을 한 바퀴 다 도는 셈이다.

9관련 기술 심화 학습 로드맵

"6주 — Electron 내부부터 리버스 엔지니어링 방법론까지, 주마다 만들 것이 하나씩"
1주차

Electron 아키텍처와 ASAR

읽기: Electron 공식 process model · @electron/asar · 이 저장소의 source/electron-main/main.ts·electron-preload/.
개념: main/renderer/preload 3층, contextIsolation, IPC, asar 패킹·무결성, 네이티브 애드온 ABI.
만들 것: preload로 좁은 API만 노출하는 최소 Electron 앱. asar로 패킹하고 풀어 본다.

2주차

코드 서명·패키징·Gatekeeper

읽기: Apple 코드 서명 개요 · scripts/package-macos.mjs · lib/macos-shell-invariant.mjs.
개념: ad-hoc 서명 vs Developer ID/공증, codesign --deep --strict, quarantine xattr, Info.plist 번들 정체성, Mach-O 로드 커맨드.
만들 것: 1주차 앱을 ad-hoc 서명하고 검증한다. Mach-O에서 코드서명 섹션을 찾아본다.

3주차

결정론적·재현 가능 빌드

읽기: scripts/lib/clean-build.mjs·asar-integrity.mjs·verify.mjs · Reproducible Builds 프로젝트 문서.
개념: 체크섬 고정 입력, 해시 기록 산출물, esbuild 결정론, git 트리 오브젝트 비교.
만들 것: 산출물마다 SHA-256을 기록하는 JSON을 뽑고, 그 기록을 재대조하는 별도 검증 스크립트를 붙인다.

4주차

AI SDK와 스트리밍 제공자 추상화

읽기: Vercel AI SDK(streamText) · Anthropic Claude Agent SDK · provider-session.ts.
개념: SSE 파싱, 델타 스트리밍, 도구 호출 루프, 사용량 집계, 서로 다른 API를 하나의 인터페이스로 통일하기.
만들 것: OpenAI 호환 엔드포인트 2개(예: OpenRouter + 로컬 ollama)를 같은 인터페이스로 호출하는 미니 라우터.

5주차

MCP와 도구 라우팅

읽기: MCP 공식 명세 · routed-mcp-bridge.ts · packages/mcp-core.
개념: MCP의 tools/resources/prompts, initialize·tools/list·tools/call 흐름, 도구 annotations, 신뢰 경계와 read-only 판정.
만들 것: 루프백 HTTP로 도는 최소 MCP 서버를 만들고, 도구 몇 개를 붙여 Claude Code나 다른 MCP 클라이언트에서 호출해 본다.

6주차

리버스 엔지니어링 방법론과 출처 관리

읽기: 이 저장소의 PROVENANCE.md·audit-*.mjs 3종 · 소프트웨어 포렌식·SBOM 자료.
개념: 증거 앵커, 불확실성 집계, fail-closed 승격, 저작권·상표·서비스 약관의 경계.
만들 것: 1~5주차 결과물을 묶어, "무엇을 재구성하고 무엇을 유지하며 무엇을 모르는지"를 매니페스트로 문서화한다. 여기까지가 이 저장소가 시범 보이는 전체 그림이다.

10핵심 키워드 사전

"이 문서와 저장소에서 반복해 나오는 말들"
용어
재구성(reconstruction)소스가 없는 컴파일된 앱을, 관측된 증거를 근거로 읽을 수 있는 소스로 되살리는 것. "복제"와 달리 원본을 사양·증거로 취급한다
증거 앵커(artifact anchor)재구성한 동작을 뒷받침하는 검사 가능한 근거. 방출 코드 마커·소스맵·문자열·DOM 시그니처·IPC/RPC 계약·런타임 관측 등
화이트라벨A사 제품을 B사 브랜드로 파는 것. Grok Bot = Anysphere(Cursor)의 "Sand"를 xAI 브랜드로 낸 앱
ElectronChromium + Node.js로 웹 기술로 데스크톱 앱을 만드는 프레임워크. main·renderer·preload 3층
ASARElectron이 앱 소스를 하나로 묶는 아카이브(app.asar). 해체해 읽고 재구성본으로 재포장한다
preloadrenderer(웹)와 main(노드) 사이의 좁은 신뢰 브리지. 여기로만 제한된 API가 오간다
ConnectRPC / protobufcoordinator↔host 사이의 타입 있는 RPC + 이진 직렬화. 재구성의 앵커가 되는 계약
추론 라우터새 대화를 어느 백엔드(Cursor/Claude/Codex/OpenRouter)로 보낼지 고르는 계층
Direct ResponsesCodex 제공자가 로컬 OAuth 토큰으로 ChatGPT 백엔드를 직접 치는 트랜스포트. CLI 바이너리를 거치지 않음
MCPModel Context Protocol. LLM 에이전트가 외부 도구에 붙는 표준. 여기선 루프백 브리지로 라우팅
콘텐츠 주소화(content-addressed)내용의 해시를 경로·식별자로 쓰는 것. 로컬 Docker 런타임을 <sha> 경로에 두어 변조를 막는다
ad-hoc 서명인증서 없이 로컬에서만 붙이는 코드 서명. 원본의 Developer ID/공증은 승계하지 않는다
Gatekeeper / quarantinemacOS의 실행 검문. 다운로드 파일에 붙는 quarantine xattr를 xattr -cr로 지워야 새 서명이 통과
Mach-OmacOS 실행 파일 포맷. 서명 부분을 뺀 구조만 해시해 "구조 동일"을 증명하는 트릭에 쓰인다
fail-closed조건이 완전히 충족될 때만 통과, 아니면 안전한 기본값으로 떨어지는 설계. clean 승격이 이 방식
결정론적 빌드같은 입력이면 언제 빌드해도 바이트가 같은 산출물. 해시 대조·재현의 전제
Git LFSLarge File Storage. 큰 바이너리(DMG·EXE)를 포인터로만 커밋하고 실물은 별도 저장. 포인터의 oid가 곧 SHA-256
오픈코어핵심은 열고 일부 기능은 상용으로 남기는 모델. 원본의 private/ 조건부 별칭에서 흔적이 보인다

11참고 링크

원본을 직접 확인하고 싶을 때