5분 AI.
REPO DEEP DIVE · AI 에이전트 · 브리지

Codex with ChatGPT — 생각은 ChatGPT, 손은 Codex

ChatGPT 웹 구독을 코딩 에이전트의 '계획·리뷰 두뇌'로 쓰는 읽기 전용 MCP 다리. 코드를 대화창에 붙여 넣지 않고, 쓰기 도구는 아예 만들지 않았다. TypeScript 5,071줄을 뜯어 본 한국어 공부자료.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어/시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

한 문장으로 이 프로젝트가 뭘 하는지부터.

Codex with ChatGPT = "생각은 이미 돈 낸 ChatGPT 웹에 시키고, 손은 Codex가 쓰게 만드는 중계 다리." 코딩 에이전트를 돌리면 계획 세우기·코드 리뷰 같은 생각에도 실행과 똑같은 토큰이 나간다. 그런데 매달 결제 중인 ChatGPT 웹 구독은 대개 남아돈다. 이 프로젝트는 그 남는 쪽으로 생각을 옮긴다 — ChatGPT가 계획을 세우고 결과를 검수하고, Codex는 파일을 고치고 테스트를 돌린다. 둘을 잇는 다리는 읽기만 되고, 쓰기·실행 도구는 아예 만들어 두지 않았다.

🧩 한 줄 비유

"설계도를 팩스로 통째로 보내는" 게 아니라, "설계자에게 현장 열람증만 주는" 것

보통은 코드를 복사해 ChatGPT 창에 붙여 넣는다. 붙여 넣는 순간 내 저장소 사본이 대화창으로 넘어가고, 길이 제한 때문에 필요한 부분만 잘라 보내니 설계자는 늘 조각난 정보로 판단한다.

C2C는 반대다. 코드를 보내지 않고 열람 권한만 준다. ChatGPT는 "그 파일 42~90행 보여 줘", "지금 git diff 뭐야"를 스스로 물어 가져간다. 대화창에 오가는 건 1KB도 안 되는 상태 쪽지뿐이고, 실제 내용은 별도 통로로만 흐른다. 열람증에는 복사기 사용 권한이 없다 — 읽을 수는 있어도 고칠 수는 없다.

용어
MCP (Model Context Protocol)
AI 모델이 외부 도구·데이터에 접근하는 표준 규약. 여기서는 ChatGPT가 내 PC의 작업 폴더를 읽는 통로로 쓰인다. 서버 쪽에 "파일 읽기" 도구만 만들어 두고 "파일 쓰기" 도구를 안 만들면, 모델은 아무리 시켜도 쓸 방법이 없다.
용어
컴퓨터 유즈 (Computer Use)
에이전트가 사람처럼 브라우저 화면을 조작하는 기능. 이 프로젝트에선 Codex가 ChatGPT 웹 페이지에 짧은 상태 메시지를 타이핑해 넣는 용도로만 쓴다. API 키도, 우회 프록시도 쓰지 않고 공식 웹 UI를 그대로 쓰는 이유가 이것이다.

만든 사람은 XiaoDuoYa, 라이선스는 MIT, 버전은 0.1.0이다. 별은 1.3천 개, 포크 141개(2026년 8월 30일 기준). 소스는 TypeScript 29개 파일 5,071줄에 테스트 12개 파일 1,806줄로, 규모로 보면 작은 편인데 보안 설계 문서만 별도로 한 장을 차지한다. README가 중국어·영어 병기이고, 설치조차 "이 문단을 Codex에게 그대로 복사해 주세요"로 시작하는 게 이 프로젝트의 성격을 잘 보여 준다.

2왜 주목받는가

비슷한 "ChatGPT 연동" 도구가 널린 판에서 무엇이 달랐나.

① 아끼는 대상이 명확하다 — 토큰이 아니라 비싼 토큰

"AI 비용 절감"을 내건 도구는 많지만 대개 캐시나 모델 다운그레이드 얘기다. 이 프로젝트는 작업을 종류별로 갈라 붙인다. 계획 수립·아키텍처 판단·코드 리뷰는 긴 맥락과 좋은 추론이 필요한데 출력량은 적다. 반대로 실제 편집은 출력이 많지만 판단은 덜 필요하다. 앞쪽을 정액제 웹 구독으로, 뒤쪽을 종량제 하네스로 보내는 배분이다.

② "읽기 전용"이 정책이 아니라 구조

많은 도구가 "쓰기는 확인 후에만"이라고 약속한다. 약속은 프롬프트 인젝션 한 방에 깨질 수 있다. C2C는 서버에 쓰기·삭제·셸 실행 도구를 코드로 만들어 두지 않았다. 보안 문서의 표현이 직설적이다 — "이 도구들은 서버에 존재하지 않으므로, 어떤 프롬프트 인젝션이나 스코프 버그나 UI 혼동으로도 활성화할 수 없다." 없는 기능은 뚫리지 않는다.

③ 리뷰가 자기 신고에 기대지 않는다

에이전트 협업에서 흔한 실패가 "테스트 다 통과했습니다"라는 실행자의 자기 보고를 검수자가 그대로 믿는 것이다. C2C는 Codex가 작업을 끝내면 EXECUTED 쪽지에 "바뀐 파일 4개, 테스트 27개 통과" 같은 숫자만 적어 보내고, ChatGPT는 그걸 믿는 대신 MCP로 실제 git diff와 실행 기록을 직접 당겨 확인한다. 프로토콜 문서가 이 절차를 명시적으로 요구한다.

④ 사용자에게 기술 용어를 안 보여 주기로 작정했다

동봉된 Codex 스킬 문서(skill/SKILL.md, 555줄)의 황금률 2번은 "사용자에게 MCP·OAuth·PKCE·터널·포트·localhost를 보여 주지 말 것"이다. 4번은 "실패하면 c2c doctor를 돌려 조용히 고치고, 로그인·캡차·2단계 인증처럼 사람이 꼭 해야 하는 일만 그것도 한 번에 하나씩 요청할 것". 설치 안내가 "터미널을 여세요"가 아니라 "이 문단을 에이전트에게 붙여 넣으세요"인 것도 같은 방향이다.

⑤ 문서가 코드만큼 촘촘하다

docs/에 아키텍처·프로토콜·보안·문제해결 4종 487줄이 있고, 특히 보안 문서는 위협 13가지를 표로 늘어놓고 각각의 대응을 적었다. 개인 프로젝트 규모에서 위협 모델을 이 정도로 쓰는 경우는 드물다.

주의
비용을 아끼는 대신, 자동화 대상이 내 ChatGPT 계정이다

이 구조는 에이전트가 사용자의 ChatGPT 웹 세션을 자동 조작하는 것을 전제로 한다. 편의는 크지만 OpenAI 이용약관과의 관계는 사용자가 스스로 판단할 몫이고, 계정 단위 제재 위험을 프로젝트가 대신 져 주지 않는다. 업무용 계정으로 붙이기 전에 이 점을 먼저 저울질하는 게 맞다.

3기술 스택 전체 지도

무엇으로 만들어졌나 — 의존성이 다섯 개뿐이다.

런타임 · 언어
항목왜 이걸 골랐나
TypeScript 5.9소스 전량MCP 도구 스키마·OAuth 상태 머신처럼 모양이 틀리면 바로 사고인 부분이라 타입이 값을 한다
Node.js ≥ 20필수 조건ESM 기본("type":"module"), node:cryptotimingSafeEqual 등 표준 API를 그대로 씀
pnpm 11.24패키지 매니저packageManager 필드로 고정 — 설치 안내가 corepack pnpm인 이유
런타임 의존성 — 전부 다섯 개다
패키지역할
@modelcontextprotocol/sdkMCP 서버 구현. 도구 등록과 Streamable HTTP 전송을 담당
express ^5.1브리지 HTTP 서버. /mcp·OAuth 엔드포인트·/health·관리 API를 얹는다
zod ^3.25도구 입력 스키마. 외부에서 들어오는 값의 1차 방어선
ignore ^7.0gitignore 문법 매칭기. 민감 파일 차단·잡음 폴더 제외·.c2cignore가 전부 이걸 씀
commander ^14c2c CLI. 명령 20개 남짓과 --json 출력

눈여겨볼 건 없는 것들이다. 데이터베이스가 없다(상태는 OS 앱 디렉터리의 JSON 파일). 인증 라이브러리가 없다(OAuth 2.1 인가 서버를 src/auth/ 721줄로 직접 구현했다). 프런트엔드 프레임워크가 없다(페어링 화면은 서버가 뱉는 HTML 한 장). 외부에 열리는 서비스를 만들면서 의존성을 다섯 개로 묶어 둔 것 자체가 공격 표면 관리다.

용어
OAuth 2.1 · PKCE · DCR
OAuth는 비밀번호를 주지 않고 권한만 넘기는 표준. PKCE는 중간에서 인가 코드를 가로채도 못 쓰게 막는 장치로, C2C는 안전한 S256 방식만 받고 약한 plain은 거부한다. DCR(동적 클라이언트 등록)은 ChatGPT가 사전 협의 없이 스스로 클라이언트로 등록하는 절차다 — 사람이 키를 복사해 붙일 일이 없어지는 이유.
개발 · 검증 도구
항목내용
vitest ^3.2테스트 12개 파일, describe 27개 · it 119개. 경로 탈출·OAuth·페어링·MCP 왕복이 전부 대상
tsx빌드 없이 개발 실행 (pnpm dev)
cloudflared외부 의존 바이너리. 공개 주소를 여는 터널 (npm 패키지가 아니라 별도 설치)
ripgrep있으면 검색에 쓰고 없으면 Node 구현으로 자동 대체 — 필수가 아니다

📌 README는 "vitest: 76 tests"라고 적었지만, 실제 테스트 파일을 열어 세면 it( 블록이 119개다. 문서가 쓰인 뒤 테스트가 늘어난 것으로 보인다 — 레포를 평가할 때 README 숫자보다 소스를 직접 세는 편이 안전하다는 흔한 사례다.

4아키텍처 심화 분석

이 프로젝트의 진짜 아이디어는 "두 개의 평면을 절대 섞지 않는다"이다.

제어 평면과 데이터 평면의 분리

프로토콜 문서 첫 세 줄이 설계 전부를 요약한다.

// docs/protocol.md 첫머리
Control plane: Computer Use (tiny structured messages typed into the ChatGPT UI).
Data plane: MCP (ChatGPT pulls files, diffs, search results itself).

Never mix the two: control messages carry state, never content.
제어 평면은 컴퓨터 유즈(ChatGPT 화면에 타이핑되는 작은 구조화 메시지), 데이터 평면은 MCP(ChatGPT가 파일·diff·검색 결과를 직접 당겨 감). 둘을 절대 섞지 말 것 — 제어 메시지는 상태를 나르지, 내용을 나르지 않는다.

제어 평면은 ChatGPT 대화창에 타이핑되는 짧은 쪽지다. 여기엔 "지금 어느 단계인가, 몇 번째 반복인가, 목표가 뭔가"만 들어가고 1KB를 넘지 않는다. 데이터 평면은 MCP 연결이고, 파일 내용·diff·검색 결과는 오직 이쪽으로만 흐른다. 왜 이렇게 나눴는지는 섞였을 때를 생각하면 분명하다 — 대화창에 diff를 붙이기 시작하면 맥락 창이 금세 차고, 잘라 붙이는 과정에서 정보가 왜곡되고, 무엇보다 대화 기록에 코드 사본이 남는다.

┌───────────────────────────┐ │ ChatGPT Web │ │ 생각 · 계획 · 리뷰 │ └──────────┬──────────▲─────┘ │ │ MCP │ │ Computer Use 데이터 평면 │ │ 제어 평면 (<1KB) (내용) ▼ │ (상태만) ┌─────────────────────┐ │ C2C 브리지 │ 127.0.0.1 에만 바인딩 │ 읽기 전용 MCP 8종 │ OAuth 2.1 + 1회용 페어링 코드 │ OAuth 인가 서버 │ Cloudflare 터널 │ 터널 · 관리 API │ └──────────┬──────────┘ │ 읽기만 ▼ ┌─────────────────────┐ ┌──────────────────┐ │ 내 작업 폴더 │◀───────│ Codex 하네스 │ └─────────────────────┘ 편집 │ 셸 · 테스트 · git │ └──────────────────┘

상태 머신 — 아홉 개의 상태

두 에이전트가 주고받는 쪽지는 [C2C]로 시작하고 헤더에 상태를 적는다. 흐름은 이렇다.

INIT → PLAN → EXECUTING → EXECUTED → REVIEW → PLAN | DONE | BLOCKED | ERROR
상태보내는 쪽
INITCodex새 작업. "연결된 작업 폴더를 살펴보고 계획을 세워 달라"
PLANChatGPT다음 회차에 실행할 계획. 근거·행동·건드릴 파일·테스트·성공 기준
EXECUTINGCodex(선택) 실행 중
EXECUTEDCodex회차 종료. 메타데이터만 — 바뀐 파일 수, 테스트 결과
REVIEWChatGPT(암묵) MCP로 직접 들여다보는 중
DONE / BLOCKEDChatGPT성공 기준 충족 / 진행 불가(사유 포함)
ERROR양쪽프로토콜·인프라 실패
HANDOFFCodex대화가 길어졌을 때 후속 대화로 넘기는 인수인계 브리핑

프로토콜 문서가 계획에 대고 못을 박은 문장이 하나 있다 — "계획은 유한하고 구체적이며 실행 가능해야 한다. 40단계짜리 대서사시가 아니라." 리뷰 단계에서 계획으로 되돌아가는 고리가 있으니, 한 회차를 작게 유지하는 게 이 구조의 전제다.

인증 — 사람이 만지는 비밀은 딱 하나

공개 주소가 열리는 구조라 인증이 핵심인데, 설계 목표가 뚜렷하다. 모델은 장기 자격증명을 절대 보지 않는다. 사람과 모델이 다루는 비밀은 1회용 페어링 코드 하나뿐이고, 진짜 토큰은 ChatGPT 클라이언트와 브리지의 OAuth 엔드포인트 사이에서만 오간다.

장치실제 값
페어링 코드혼동 글자를 뺀 31자 알파벳(ABCDEFGHJKMNPQRSTUVWXYZ23456789)에서 8자 — 약 40비트
코드 방어유효 5분 · 시도 5회 · IP당 분당 10회 · 1회 쓰면 폐기 · 한도 초과 시 세션 파기
인가 코드5분, 1회용, 클라이언트+리다이렉트 URI에 묶임
액세스 토큰1시간
리프레시 토큰30일, 쓸 때마다 회전 — 옛 것 재사용은 실패
저장 방식토큰은 원문이 아니라 SHA-256 해시만 저장. 상태 파일을 훔쳐도 쓸 수 있는 토큰이 안 나온다

여기에 균등 분포까지 신경 썼다. 코드 생성기가 난수 바이트를 % 31로 접지 않고, 256을 31로 나눈 배수를 넘는 바이트는 버리고 다시 뽑는다. 나머지 연산만 쓰면 앞쪽 글자가 미세하게 자주 나오는 편향이 생기는데, 그걸 차단한 것이다. 40비트짜리 5분 코드에 이 정도까지 하는 건 과할 수도 있지만 암호학 코드를 대하는 태도는 드러난다.

포트와 터널 — 사용자가 몰라도 되게

브리지는 48765 포트를 선호하고 127.0.0.1에만 바인딩한다(0.0.0.0은 거부). 포트가 이미 쓰이고 있으면 /health를 찔러 그게 같은 작업 폴더의 c2c 브리지인지 확인하고, 맞으면 재사용하고 아니면 임의 포트로 물러선다. 공개 주소는 Cloudflare 임시 터널이 기본인데 재시작마다 주소가 바뀌므로, c2c doctor가 그 사실을 감지해 ChatGPT 커넥터를 지웠다 다시 만들라고 스킬에 지시한다. 도메인이 있는 사용자는 고정 호스트명을 한 번 정해 이 반복을 없앨 수 있다.

잘 만든 부분
터널 공급자가 인터페이스로 추상화돼 있다

tunnel/TunnelProvider 인터페이스를 두고 Cloudflare 임시·고정 두 구현을 붙였다. 아키텍처 문서의 표현으로는 "비즈니스 로직은 벤더 중립"이다. Cloudflare에 묶인 프로젝트가 되기 쉬운데 교체 지점을 미리 파 둔 것이고, 실제로 고정 터널 준비가 실패하면 임시 터널로 자동 강등되는 폴백도 들어 있다.

5디렉토리 구조 해부

폴더 이름이 곧 책임이다 — 12개 모듈, 각각 하는 일이 하나씩.

codex-with-chatgpt/ ├── src/ TypeScript 29파일 · 5,071줄 │ ├── cli/ 1,057줄 c2c 명령 20여 개. --json 이 어디서나 나온다 │ ├── workspace/ 1,003줄 ★ 경로 봉쇄 · 민감파일 정책 · 검색 · git │ ├── tunnel/ 753줄 TunnelProvider + Cloudflare 임시/고정 구현 │ ├── auth/ 721줄 OAuth 2.1 인가 서버 (직접 구현) │ ├── bridge/ 333줄 Express 조립 · 루프백 리스너 · 관리 API │ ├── mcp/ 318줄 ★ 읽기 전용 도구 8개, 그게 전부 │ ├── config/ 294줄 OS 규약 상태 디렉터리 · 포트 · 커넥터 이름 │ ├── session/ 178줄 대화 모드(long-chat / project) 상태 │ ├── pairing/ 162줄 1회용 코드 생성·검증·한도 │ ├── process/ 115줄 데몬 기동/재사용 · 헬스체크 · 정상 종료 │ ├── logger/ 86줄 ★ 비밀 마스킹 로거 │ └── execution/ 48줄 JSONL 실행 기록 (c2c record 가 쓰고 도구가 읽음) ├── tests/ 1,806줄 12파일 · it 119개 ├── docs/ 487줄 architecture · protocol · security · troubleshooting ├── skill/ 555줄 ★ Codex 에이전트에게 주는 운영 매뉴얼 (SKILL.md) ├── bin/c2c.js 실행 진입점 └── examples/ c2c.json · c2cignore.example

줄 수 분포가 이 프로젝트의 무게 중심을 드러낸다. 가장 큰 모듈이 CLI(1,057줄)이고 그 다음이 workspace(1,003줄)다. 즉 코드의 절반 가까이가 "모델에게 무엇을 어떻게 보여 줄 것인가"와 "사람이 어떻게 조작하는가"에 들어갔다. 정작 MCP 도구 구현은 318줄로 가장 얇은 축에 속한다 — 도구가 얇을 수 있는 이유는 위험한 판단을 전부 workspace 층에서 끝내 놓기 때문이다.

workspace/ — 이 레포에서 가장 중요한 폴더

파일 하나를 읽는 요청이 여기서 세 개의 관문을 지난다.

관문하는 일
① 경로 봉쇄
manager.ts
존재하는 가장 깊은 조상까지 realpath로 정규화한 뒤 작업 폴더 안인지 검사. 심볼릭 링크·..·절대경로·역슬래시·널 바이트가 전부 막히고, macOS/Windows에선 대소문자 무시로 비교한다
② 민감 파일
ignore.ts
기본 거부 목록에 걸리면 ACCESS_DENIED_SENSITIVE_FILE. 읽기·목록·검색이 같은 관문을 지난다
③ 분량 상한읽기는 기본 400줄·최대 2,000줄, 기본 256KB·최대 1MB. diff는 바이트 오프셋 페이지네이션에 총량 64MB 상한

거부 목록이 구체적이다 — .env.env.*(단 .env.example예외로 허용), *.pem·*.key·*.p12·*.pfx·*.jks·*.keystore, SSH 키 4종(id_rsa·id_ed25519·id_ecdsa·id_dsa)과 그 변형, .ssh/·.aws/·.gnupg/·.cloudflared/, .npmrc·.netrc·.git-credentials, 키체인 파일, credentials.json·service-account*.json·secrets.json, 그리고 브라우저 쿠키 DB까지. 사용자는 .c2cignore로 자기 규칙을 더할 수 있지만 기본 목록을 뺄 수는 없다.

쉽게 말하면

도서관에 열람증을 준다고 금고실 열쇠까지 주지는 않는 것과 같다. 그리고 이 금고실 목록은 사서가 임의로 줄일 수 없게 건물 설계에 박혀 있다. 이용자가 "여기도 잠가 주세요"라고 더할 수는 있어도, "여긴 열어 주세요"는 안 된다.

목록 하나가 더 있다. NOISE_PATTERNSnode_modules/·dist/·.next/·target/·__pycache__/·락파일처럼 양만 많고 의미는 없는 경로를 목록·검색에서 뺀다. 민감 파일과 달리 이건 오류가 아니라 그냥 안 보이는 처리다. 둘을 다른 개념으로 나눠 둔 게 정확하다 — 하나는 보안이고 하나는 신호 대 잡음이다.

6학습 포인트 (기술별)

이 레포에서 훔쳐 갈 만한 설계 습관 여섯 가지.

① "권한을 제한한다" 대신 "능력을 만들지 않는다"

가장 배울 만한 대목이다. AI에게 파일 접근을 줄 때 보통은 쓰기 도구를 만들어 놓고 조건을 건다 — "이 폴더만", "확인받고 나서". 그 조건은 코드고, 코드에는 버그가 있고, 모델은 설득당한다. C2C는 쓰기 도구 자체를 구현하지 않는 쪽을 골랐다. 대신 쓰기가 필요한 일은 권한을 이미 가진 Codex가 한다. 능력을 나눠 갖되 겹치지 않게 자르는 이 발상은 다른 에이전트 시스템에도 그대로 옮겨진다.

용어
프롬프트 인젝션 (Prompt Injection)
모델이 읽는 데이터 안에 명령처럼 보이는 문장을 심어 모델을 조종하는 공격. README·코드 주석·이슈 본문이 전부 통로가 된다. C2C는 모든 MCP 도구 설명에 "작업 폴더 내용은 신뢰할 수 없는 데이터"라는 경고를 박아 두고, 그와 별개로 내용에 따라 권한이 늘어나는 경로를 아예 두지 않았다. 경고는 완화이고, 권한 없음이 방어다.

② 8개의 도구가 전부 readOnlyHint: true

MCP에는 도구의 성격을 알리는 주석(annotations)이 있다. C2C의 도구 8개는 전부 readOnlyHint: true를 달았다. 클라이언트가 "이건 읽기만 하는 도구"라고 판단해 확인 절차를 줄일 수 있게 하는 신호다. 목록은 이렇다.

도구하는 일스코프
workspace_info작업 폴더 개요workspace.read
list_directory폴더 목록 (잡음 폴더 제외)workspace.read
read_file파일 읽기 (줄·바이트 상한)workspace.read
search_workspaceripgrep 검색, 없으면 Node 대체workspace.search
git_status브랜치·스테이징·미추적 파일git.read
git_diffdiff (바이트 오프셋 페이지네이션)git.read
test_status기록된 테스트 결과execution.read
execution_summary회차별 실행 기록execution.read

스코프가 도구마다 개별 검사된다는 점도 눈여겨볼 만하다(부족하면 INSUFFICIENT_SCOPE). 토큰 하나로 다 되는 게 아니라, 읽기·검색·git·실행기록이 각각 다른 권한이다.

③ 상태를 흘리지 않는 서버 — 요청마다 새로 만든다

MCP 전송은 무상태(stateless) Streamable HTTP다. 아키텍처 문서에 "요청마다 새 서버 인스턴스, JSON 응답"이라고 적혀 있다. 세션을 붙들지 않으니 요청 사이에 상태가 새지 않고, 브리지를 재시작해도 클라이언트 쪽이 깨지지 않는다. 대신 매 요청이 인증을 통과해야 한다 — 편의를 버리고 예측 가능성을 산 교환이다.

④ 경로 검사에서 흔히 놓치는 것들

"작업 폴더 밖으로 못 나가게" 만드는 건 쉬워 보이는데 실제론 함정이 많다. C2C의 처리를 그대로 배울 만하다.

함정대응
심볼릭 링크로 밖을 가리키기realpath링크를 푼 뒤 포함 검사. 파일 링크·디렉터리 링크 둘 다 테스트에 있다
아직 없는 경로라 realpath가 실패존재하는 가장 깊은 조상까지만 정규화하고 나머지를 이어 붙인다
macOS·Windows의 대소문자그 두 플랫폼에서만 소문자로 낮춰 비교 (리눅스에선 구분 유지)
역슬래시·널 바이트·절대경로전부 거부, 각각 테스트 있음

⑤ 로그가 비밀을 지운다

logger/는 86줄짜리 작은 모듈인데 하는 일이 분명하다 — 토큰 앞자리, Bearer 헤더, 토큰처럼 생긴 파라미터, 페어링 코드 모양의 문자열을 기록 전에 지운다. 디버그 로그가 자격증명 유출 경로가 되는 사고는 실제로 흔하다. 작은 모듈 하나로 그 계열을 통째로 닫아 둔 셈이다.

⑥ 로컬 전용 API를 진짜 로컬로 만드는 법

관리 API는 루프백에서만 받는데, 거기서 한 걸음 더 간다. cf-connecting-ip·x-forwarded-for 같은 프록시 헤더가 붙은 요청은 거부한다 — 터널을 통해 들어온 요청이 루프백인 척하는 걸 막는 장치다. 게다가 인증 없는 탐색에는 401이 아니라 404를 돌려준다. "여기 뭔가 있다"는 사실 자체를 안 알려 주는 것이다.

잘 만든 부분
/health가 작업 폴더 경로 대신 소금 친 해시를 내놓는다

포트 충돌을 판별하려면 "이 브리지가 어느 폴더 것인가"를 알아야 하는데, 경로를 그대로 노출하면 공개 엔드포인트에서 내 PC의 디렉터리 구조가 새어 나간다. 그래서 솔트를 섞은 해시만 준다. 같은 폴더인지 비교는 되지만 원래 경로는 복원되지 않는다.

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

모델을 돌리는 게 아니라 중계만 하므로 요구사항이 거의 없다.

항목요구비고
Node.js20 이상필수. package.jsonengines에 명시
git필수git_status·git_diff가 실제 git을 부른다
cloudflared공개 연결에 필요자동 감지. 스킬이 대신 설치해 준다
ripgrep선택없으면 Node 구현으로 자동 대체. C2C_DISABLE_RG=1로 강제 비활성화도 가능
GPU불필요추론은 전부 ChatGPT 서버와 Codex가 한다
메모리·CPU사실상 무시 가능Express 서버 하나 + cloudflared 프로세스
OSmacOS · Windows · Linux대소문자·상태 디렉터리 분기가 코드에 들어 있다
네트워크아웃바운드만터널이 바깥으로 연결을 건다 — 방화벽에 포트를 열 필요가 없다

진짜 비용은 하드웨어가 아니라 계정이다. ChatGPT 유료 구독(계획·리뷰용)과 Codex를 돌릴 수단이 둘 다 있어야 하고, 고정 주소를 원하면 Cloudflare 계정과 그 위에 올린 도메인이 추가로 필요하다. 상태 파일은 OS 규약 디렉터리(macOS 기준 ~/Library/Application Support/codex-with-chatgpt)에 디렉터리 0700 · 파일 0600으로 저장되고, 프로젝트 폴더 안에는 아무것도 남기지 않는다.

주의
v1은 토큰 해시를 파일에 둔다 — OS 키체인이 아니다

보안 문서가 스스로 적어 둔 한계다. 원문 토큰은 어디에도 안 쓰이고 해시만 남지만, 클라이언트 등록 정보와 토큰 해시가 파일 기반이라는 점은 키체인 저장보다 약하다. 키체인 연동은 v2 과제로 남아 있다. 한계를 문서에 먼저 적어 둔 것 자체는 신뢰 요소이지만, 공유 PC에서 쓸 계획이라면 이 줄을 반드시 읽고 넘어가야 한다.

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

읽기만 해서는 안 남는다 — 손으로 확인하는 순서.

과제 1난이도 ★☆☆

민감 파일 차단이 진짜 되는지 확인하기

테스트 폴더에 .env(가짜 값)와 .env.example을 나란히 두고 붙인 뒤, ChatGPT에게 두 파일을 각각 읽어 달라고 한다. .envACCESS_DENIED_SENSITIVE_FILE로 거부되고 .env.example은 읽혀야 한다.

볼 것: 거부가 목록·검색에서도 일관되게 걸리는가. 파일 읽기만 막고 search_workspace로는 내용이 새는 도구가 세상에 꽤 있다.
과제 2난이도 ★☆☆

제어 메시지가 정말 1KB 미만인지 세어 보기

실제 작업을 한 회차 돌린 뒤 ChatGPT 대화창에 쌓인 [C2C] 메시지를 복사해 글자 수를 센다. docs/protocol.md의 예시 형식과 비교한다.

핵심: 이 프로젝트의 절약 논리가 성립하려면 대화창에 코드가 안 올라와야 한다. 실제로 그런지는 세어 보면 1분 만에 판정된다.
과제 3난이도 ★★☆

경로 탈출 테스트를 읽고 하나 더 추가하기

tests/workspace.test.ts(it 20개)를 정독한 뒤, 저자가 안 막았을 법한 경로를 하나 상상해 테스트로 추가한다. 유니코드 정규화, 윈도우 8.3 단축 이름, 하드링크 같은 것들.

이게 이 레포에서 가장 배울 게 많은 과제다. 경로 검사는 "됐다"고 느낀 뒤에 뚫린다. 남의 테스트에 하나를 더 얹어 보면 그 감각이 생긴다.
과제 4난이도 ★★☆

토큰 없이 /mcp를 찔러 보기

터널 주소를 알아낸 상태에서 curl/mcp에 요청한다. 401이 나오고 WWW-Authenticate 헤더가 리소스 메타데이터 주소를 가리키는지 본다. 다른 작업 폴더의 토큰으로 찌르면 403이 나와야 한다.

배울 점: MCP 인가 흐름이 "401 → 메타데이터 발견 → 동적 등록 → 인가"로 자동 협상된다는 걸 눈으로 보게 된다. 요즘 MCP 서버를 직접 만들 때 그대로 쓰는 패턴이다.
과제 5난이도 ★★★

같은 구조를 다른 하네스에 옮겨 보기

skill/SKILL.md는 Codex 전용으로 쓰였다. 이 555줄을 다른 코딩 에이전트의 규약으로 옮기고, [C2C] 상태 머신을 그대로 태워 본다. 브리지는 손댈 필요가 없어야 정상이다 — 하네스 의존이 스킬 문서에만 있다면.

이 과제가 곧 설계 검증이다. 브리지를 안 고치고 옮겨진다면 제어/데이터 평면 분리가 실제로 잘 된 것이고, 여기저기 고쳐야 한다면 경계가 샜다는 뜻이다.

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

이 레포를 이해하려고 파고들면 자연히 닿는 것들.

1단계 · MCP 자체

먼저 MCP 사양을 읽고 도구·리소스·프롬프트의 차이를 잡는다. 그다음 도구 주석(readOnlyHint·destructiveHint·idempotentHint)이 왜 있는지 본다. C2C처럼 읽기 전용 서버를 하나 직접 만들어 보면 "도구를 안 만드는 것이 곧 권한 통제"라는 감각이 손에 붙는다.

2단계 · MCP 인가 규격

C2C가 구현한 건 MCP Authorization 흐름 그대로다. 관련 RFC를 순서대로 읽으면 코드가 훨씬 잘 읽힌다 — RFC 8414(인가 서버 메타데이터) · RFC 9728(보호 자원 메타데이터) · RFC 7591(동적 클라이언트 등록) · RFC 7636(PKCE) · RFC 7009(토큰 폐기). src/auth/oauth.ts 363줄이 이 목록의 구현체다.

3단계 · 에이전트 보안

프롬프트 인젝션 문헌과 OWASP LLM Top 10을 읽고, C2C의 위협 표 13줄과 대조해 본다. "완화했다"와 "구조적으로 불가능하게 했다"의 차이를 사례로 구분할 수 있게 되는 게 목표다.

4단계 · 멀티 에이전트 프로토콜

[C2C]사람이 읽을 수 있는 텍스트 상태 머신이다. 같은 문제를 JSON 스키마나 함수 호출로 푸는 방식과 비교해 보면 트레이드오프가 보인다 — 텍스트는 웹 UI에 타이핑할 수 있고 사람이 감사하기 쉽지만, 파싱이 느슨하고 검증이 약하다. 전송 수단이 대화창일 때 왜 텍스트가 이기는지가 이 프로젝트의 답이다.

5단계 · 터널링과 로컬 서비스 노출

Cloudflare 임시 터널 · 고정 호스트명 터널 · ngrok · SSH 역터널을 비교한다. 공통 질문 하나로 정리된다 — "주소를 아는 것만으로 뭘 할 수 있는가?" C2C의 답은 "아무것도"이고, 그 답을 만드는 게 OAuth 층이다.

10핵심 키워드 사전

문서와 코드에서 반복해 나오는 말들.

키워드
C2CCodex to ChatGPT. 이 프로젝트의 약칭이자 CLI 명령 이름(c2c), 그리고 제어 메시지의 머리표([C2C])
브리지 (Bridge)내 PC에서 도는 작은 서버. MCP 서버 + OAuth 인가 서버 + 페어링 + 터널 관리자를 한 프로세스에 담았다
제어 평면 / 데이터 평면네트워크 장비 설계에서 온 말. 여기선 상태만 나르는 통로(대화창)와 내용을 나르는 통로(MCP)를 가리킨다
워크스페이스 (Workspace)작업 폴더. 이 프로젝트에선 권한의 최소 단위다 — 브리지 하나 = 폴더 하나 = 토큰 하나
페어링 코드브라우저에 딱 한 번 입력하는 8자리 임시 코드. 사람과 모델이 만지는 유일한 비밀
PKCE (S256)인가 코드를 가로채도 못 쓰게 하는 장치. 약한 plain 방식은 거부된다
DCR동적 클라이언트 등록. 클라이언트가 사전 협의 없이 스스로 등록하는 절차 (RFC 7591)
스코프 (Scope)권한 딱지. workspace.read·workspace.search·git.read·execution.read·offline_access 다섯 종
경로 봉쇄 (Containment)요청 경로가 작업 폴더 안에 있는지 정규화 후 검사하는 것. 심볼릭 링크를 푼 다음 비교하는 게 핵심
.c2cignore사용자가 추가로 가릴 파일을 적는 파일. gitignore 문법. 기본 차단 목록을 해제할 수는 없다
Quick Tunnel / Named TunnelCloudflare 터널 두 종류. 임시(주소가 매번 바뀜) vs 고정 호스트명(도메인 필요)
c2c doctor진단·자가 수리 명령. 터널 주소가 바뀌었으면 커넥터를 다시 만들라고 스킬에 알린다
HANDOFF대화가 길어졌을 때 후속 대화로 넘기는 인수인계 상태. 맥락 창 한계를 프로토콜 안에서 다룬 장치
long-chat / project대화 재사용 모드 두 가지. 폴더당 대화 하나(구방식) vs 폴더당 ChatGPT 프로젝트 하나(신규 기본값)
실행 기록 (Execution record)c2c record가 JSONL로 남기는 회차 결과. ChatGPT가 execution_summary·test_status로 읽어 자기 보고를 검증한다

11참고 링크

원문과, 옆에 두고 볼 것들.

구분링크왜 보는가
원본 레포XiaoDuoYa/codex-with-chatgpt별 1.3천 · 포크 141 · MIT · v0.1.0
보안 문서docs/security.md이 레포에서 가장 먼저 읽을 문서. 위협 13가지와 대응이 표로 정리돼 있다
프로토콜 문서docs/protocol.md[C2C] 메시지 형식과 상태 머신 전문
아키텍처 문서docs/architecture.md모듈별 책임과 요청 생애주기(MCP 호출·인가·포트·터널)
스킬 문서skill/SKILL.md555줄. 에이전트에게 주는 운영 매뉴얼을 어떻게 쓰는지의 좋은 표본
중국어 READMEREADME.zh-CN.md원저자의 설명이 더 자세한 대목이 있다
MCP 사양modelcontextprotocol.io도구 주석과 인가 흐름의 원문 규격
MCP TypeScript SDKmodelcontextprotocol/typescript-sdksrc/mcp/server.ts 318줄을 읽기 전에 훑어 둘 것
Cloudflare TunnelCloudflare 문서임시 터널과 고정 호스트명의 차이
ignore 패키지kaelzhang/node-ignore민감 파일 정책이 통째로 이 라이브러리 위에 서 있다
정리하면

이 레포의 값어치는 "ChatGPT 구독을 재활용한다"는 영리한 요령에만 있지 않다. 진짜 배울 것은 두 AI에게 능력을 어떻게 나눠 줄 것인가에 대한 답이다 — 하나에게는 판단을, 다른 하나에게는 실행을 주고, 판단하는 쪽에는 실행 도구를 애초에 만들어 주지 않는다. 코드 5천 줄 중 절반이 "무엇을 못 하게 할 것인가"에 쓰였다는 사실이 이 프로젝트의 성격을 요약한다. 다만 자동화 대상이 내 ChatGPT 계정이라는 점은 도입 전에 스스로 판단해야 할 몫으로 남는다.