Skip to content

Code Review Report

Woojin Kim edited this page Jun 20, 2026 · 1 revision

코드 리뷰 보고서 (2026-06-20)

이 문서는 Code-Review 요약 페이지의 상세 동반 보고서다. 5개 관점(유지보수성·안정성· 보안·LLM 친화성·동시작업 대응)에서 코드 전체를 정독 검수한 결과를 모든 발견을 빠짐없이 수록한다(요약본은 일부만 발췌).


1. 검수 범위 (Scope)

  • 대상: 지금까지 작성된 코드 전체 — pytmuxlib/ + 플러그인 + 테스트, 약 64k LOC / 151개 .py 파일.
    • pytmuxlib/*.py 약 34k LOC
    • 플러그인 약 13.4k LOC
    • 테스트 약 25k LOC
  • 목적: 유지보수성·안정성·보안·LLM 친화성·동시작업 대응의 5개 관점에서 정독 검수하고, 각 관점별 개선 가능성을 발견·평가한다.
  • 표기 규약:
    • 심각도 H / M / L (보안은 Critical / High / Med / Low).
    • 파일:라인 표기는 검수 시점 기준이며, 이후 코드 변경으로 달라질 수 있다.
    • [검증됨] = 인용한 코드를 직접 읽어 확인함.

2. 검수 방법론 (Methodology)

  • 5개 관점별로 코드를 독립 정독(코드 직접 읽기 우선) + grep / wc / AST 기반 정량 측정 병행.
  • 데이터 흐름 추적으로 read-then-write·fan-out 등 경로별 위험 식별.
  • 선행 내부 감사 문서(보안 리뷰 F1F8, 코드 감사 S1S6, 개선 기회 목록)의 결론과 대조하여 이미 적용된 완화책은 재보고하지 않고, 적용 상태 검증 + net-new 발견에 집중.
  • 미수행: 동적 익스플로잇, 런타임 벤치마크. 개선 적용 시 게이트는 전체 테스트 스위트 통과 (python3 tests/run.py)이며 보안 건은 회귀 테스트 추가가 필수.
  • macOS / 실 PTY / 실 Claude 패널은 driver 검증 불가 영역(환경 제약).

3. 처리 현황 (Resolution Status)

본 보고서의 발견은 후속 스프린트에서 차례대로 수정 완료했다(각 수정 = 회귀 테스트 추가 + 전체 스위트 green). 테스트 수는 약 846 → 874로 증가(874 passed, 0 failed).

완료 — 모든 High·Medium 발견(전 기능 버그) 해결

관점 처리한 발견
보안 NEW-1 NEST 자동 승격 위조 차단(provenance 토큰 + 비로컬 endpoint 직결 금지)
안정성 H1 _UnixPty.write EAGAIN · H2 ptyhostclient 콜백 격리 · H3 ConPTY spawn 누수 · H4 _remote_transport 프로세스 reap · H5 재연결 first-status · M1 _on_eof executor · M2 keepalive · M3 스레드 join · M4 복원 부분실패 정리 · M5 원자적 상태 쓰기 · M7 host spawn 로깅
동시성 H-1 / H-2 송신 fan-out 직렬화 + 느린 소비자 가드(+M6) · M-1 / M-2 릴레이 직렬화 + autorename 가드
유지보수성 1-3 usage_refresh_sec 영속 버그 + 대칭 테스트 · 1-9 죽은 코드 · 1-4 i18n 한글 노출 · 1-8 오버레이 dedup · 1-7 stale 주석/숫자 · 1-6 harness running_server() 컨텍스트 매니저
LLM 친화성 4-2 포인터 주석 + 4-3 루트 온보딩 문서 · 4-B 모델 화이트리스트 외부화

후속 자율 스프린트 — 거대 리팩터 1건 + cosmetic 1건 해소

  • 1-1 · 4-1 (client.py God 파일 분할): 응집 믹스인 3모듈 추출 — clientconn.py(재접속· 재시작 버전), clientcmd.py(명령), clientio.py(입력·렌더). 거동 불변·위치만 분리(self 경유 MRO), import 헤더 원본 복제(over-import + noqa)로 이름 해석 누락 차단. 4017 → 1621줄(−2396), 874 green.
  • 4-4 (타입 힌트, 가치 부분): 값 반환 9개 훅에 반환 타입 명시. void 디스패처 16훅은 None 반환이 자명하여 zero-signal churn 회피로 제외.

보류 유지(재조사로 사유 강화, 미적용) — 순수 cleanup·기능 이득 0·driver 검증 불가

  • 1-10 (pyte 경로 폐기)재조사로 순손해 확정: model.py의 화면 버퍼가 class _BCEScreen(_BCEMixin, pyte.Screen)pyte.Screen 기반이다. vt_parser = native|pyte는 토크나이저 front-end만 바꾸고 Screen back-end는 항상 pyte. 따라서 "pyte 경로 폐기"는 pyte 의존을 제거하지 못하고(model.py가 계속 필요) 동등성 오라클(native ≡ pyte)만 잃는다 — 데드코드 제거 이득 없이 커버리지만 손실. won't-fix 격상.
  • 1-2 (_handle_cmd 디스패치 테이블)재조사 확인: 72개 action 분기, fall-through(공통 broadcast tail) ↔ early-return(자체 응답)가 혼재하고 federation 전처리는 상태 변이 + 조기 탈출이 얽힌다. 단 1건의 fall-through/return 오분류로 명령이 조용히 깨지는데(broadcast 누락/중복) 명령 경로는 driver 검증 불가. 이득은 스타일뿐 → 보류 유지.
  • 1-5 (Server 베이스 지연 / make_server)재조사 확인: class Server(*_SERVER_BASES)는 import 시 1회 합성. 지연화 이득은 kill-server re-exec로 충족되어 0에 가깝고, Server 본문 전체를 팩토리로 떼는 foundational 변경 위험만 큼 → 보류 유지.

권고: 1-2는 명령 경로 driver 검증 수단이 생기기 전엔 보류. 1-10은 pyte.Screen을 자작 Screen으로 교체하지 않는 한 무의미(별개 대형 과제). 1-5는 트리거 부재.


4. 요약 (TL;DR)

규모(약 64k LOC) 대비 아키텍처 규율은 동급 TUI 프로젝트에서 보기 드물게 높다. 플러그인 경계의 청결함(delete-to-disable 41개 훅 + getattr 가드), 코어의 죽은 코드 부재(TODO/FIXME 0건), 견고한 와이어 프로토콜·VT 파서·IPC 보안 경계, 상시 퍼징, 868개 헤드리스 단위 테스트가 공통적으로 확인된 강점이다. 선행 감사의 완화책(F1F8, S1S6)은 전수 코드에 실재함을 확인했다.

그러나 5개 관점 모두에서 한 가지 근원 부채가 반복된다: 거대 단일 파일 / God 클래스 (client.py 약 3,981줄·129메서드 App, serverio._handle_cmd 113분기, claude-code/servermixin.py 600줄 단일 메서드). 이것이 유지보수 변경 비용·LLM 부분 수정 회귀·동시 편집 충돌 표면을 동시에 키운다. 그리고 신규 발견 중 단 1건의 실재 보안 취약점(NEST 자동 승격 — High)이 있다.

관점별 등급

관점 등급 한 줄 요약
유지보수성 B− (★★★½) 플러그인 경계·코어 위생 우수 vs God 클래스·테스트 인프라 취약·문서 부채
안정성 B 핵심 IPC/프로토콜/파서/DB 견고 vs PTY-host·federation 경계 하드닝 필요
보안 A− 성숙도 매우 높음. 단 NEST 자동 승격에 net-new High 1건
LLM 친화성 B+ 명시 디스패치·집중 훅 계약·풍부한 docstring 우수 vs 거대 파일·스크래핑 결합
동시작업 대응 B 단일 루프 + 동기 구조 변경으로 모델 동시성 견고 vs 송신 fan-out 계층 최약

교차 최우선 처방 (관점 횡단, ROI 순)

  1. [보안 H] NEST 자동 승격 endpoint 직결 차단 — 유일한 실재 취약점. 가장 먼저.
  2. [안정 H] _remote_transport ssh 프로세스 정리 + LimitOverrunError 포착 — 좀비/fd 누수, 단일 최고 ROI.
  3. [동시 H] 송신 fan-out slow-consumer 가드 + per-writer 직렬화 — 멀티 클라이언트 가용성 직결.
  4. [유지보수 H] Server 베이스 합성 지연 + harness.running_app() 컨텍스트 매니저 — 테스트 거짓 실패·복붙 동시 해소.
  5. [유지보수/LLM/동시 H] 거대 파일 분할(client.py Mixin 물리 분리, test_client.py 기능 분할) — 3개 관점 부채 동시 완화.
  6. [안정 M] PTY-host/federation read 타임아웃·keepalive 도입 — 'wedge 영구 hang' 구조적 근원 봉합.

5. 유지보수성 — B− (★★★½)

강점

  • [A] 플러그인 경계 계약: "디렉토리 삭제 = 조용히 비활성화" 불변식이 코어 전역에서 지켜진다. 코어 → 플러그인은 41개 레지스트리 훅 + getattr(pane, "_field", 기본) 가드(6곳, 예 servertree.py:511)로만 닿고, 코어가 플러그인을 직접 import하는 곳은 없다. test_plugin_contract.py가 강제. 이 프로젝트 유지보수성의 최대 강점. [검증됨]
  • [A−] 코어 위생: pytmuxlib 전수 grep — TODO/FIXME/XXX/HACK/주석 처리 코드 블록 0건.

발견

ID 심각도 발견
5-1 H client.py God 모듈
5-2 H serverio._handle_cmd God 함수
5-3 H 코어 옵션 기본값 ↔ 영속 키 손중복 → 실버그
5-4 H t() 우회 하드코딩 한국어 ~15개
5-5 M 소형 오버레이 3종 구조 복제
5-6 M 죽은/이중 정의
5-7 M 이중 VT 파서 잔재
5-8 H 테스트 _PLUGIN_SERVER_MIXINS import-time 동결
5-9 H 고정 pilot.pause(N) 남용 + 보일러플레이트 복붙
5-10 M 누적 문서 부채 + stale 산문 숫자
  • [H] client.py God 모듈 (client.py:2675): PytmuxApp이 팩토리 함수 안 중첩 클래스로, 8개 믹스인(메서드 77개) + 본문(52개/1,306줄)을 MRO로 129개 메서드 끌어모은다. 믹스인 분리는 물리적 위치 분리일 뿐 논리적으로는 God 클래스다. 중첩이라 테스트가 build_client_app()을 거쳐야만 접근 가능 → 단위 테스트 곤란.
    • 영향: 변경 비용·LLM 부분 수정 회귀·동시 편집 충돌 표면이 동시에 커진다.
    • 개선/수정: PytmuxApp을 모듈 레벨 클래스로 승격(생성자 인자화), 디스패치/렌더/입력을 협력 객체로 추출. → 수정 완료(응집 믹스인 3모듈 clientconn.py·clientcmd.py·clientio.py 추출, 4017 → 1621줄).
  • [H] serverio._handle_cmd God 함수 (serverio.py:418, 355줄/80분기/복잡도≈113): 단일 async 메서드가 모든 클라 명령을 if/elif로 처리. 명령별 단위 테스트 불가.
    • 개선: _CMD_HANDLERS: dict[str, handler] 테이블 디스패치. → 재조사 결과 fall-through/return 혼재 위험으로 보류 유지(상세는 §3).
  • [H] 코어 옵션 기본값 ↔ 영속 키 손중복 → 실버그 (server.py:108-218 로드 vs serverpersist.py:557-588 _save_opts): 같은 23개 옵션을 두 파일에 수기 나열. 실버그: server.py:217usage_refresh_sec를 로드하나 _save_opts에 없음 → 사용자가 바꿔도 저장 시 소실, 다음 기동에 600으로 리셋. load ↔ save 대칭 테스트 부재.
    • 개선/수정: _OPTS_KEYS = ((key, default, cast), ...) 단일 테이블 패턴(드리프트 불가 모범, claude-code/__init__.py:571)을 코어에 적용. → 수정 완료(영속 버그 + 대칭 테스트).
  • [H] t() 우회 하드코딩 한국어 ~15개 → 영어 사용자에게 한글 노출. 한글 누출 테스트 (test_i18n.py:218)는 카탈로그 값만 검사 → 위젯 리터럴 사각지대: claude-code/screens.py:869-900 토큰 뷰어 탭바 11개 전부 Label("기간")/"세션"/"한도"/"대사", usagelog.py:408, ncd/screen.py:39.
    • 개선/수정: 위젯/notice 한글 리터럴 금지 소스 리터럴 lint 테스트 추가. → 수정 완료.
  • [M] 소형 오버레이 3종 구조 복제 (~125–165줄): clock/calendar/claude-token-usage-view가 client_tick/오버레이 render 프리앰블/블록 폰트를 글자 단위로 복제(clock/render.py:33-42calendar/render.py:65-71 ≈ usage overlay.py:29-37).
    • 개선/수정: plugins/_overlaybase.py + 코어 clientrender.dim_pane()/draw_block_text() 승격. 위험 최저. → 수정 완료(오버레이 dedup).
  • [M] 죽은/이중 정의: server.py:358 _tab_index_of(호출 0건, 삭제 가능), replay.py:23 _char_cells(clientutil.py:27 lru_cache판 복사), SGR 변환 이중 구현(model._rewrite_sgr_token vs vtparse._sgr_params_from_raw, 한쪽만 고치면 드리프트). → 죽은 코드 수정 완료.
  • [M] 이중 VT 파서 잔재: 기본 native인데 pyte 전용 우회 ~111줄(model.py:286-332 등) 잔존. pyte opt-in 폐기 결정 시 프로덕션 ~140줄 + 테스트 ~150–250줄 삭제 가능. → 재조사 결과 pyte.Screen이 back-end로 항상 필요해 won't-fix(상세는 §3).
  • [H] 테스트 _PLUGIN_SERVER_MIXINS import-time 동결 (server.py:36): import 순간 Server 베이스를 plugins.load() 결과로 영구 동결 → 부분 실행 시 claude 믹스인 없이 동결되어 거짓 실패. 곳곳 poison 가드(test_plugin_prompt_history.py:192 조기 return)로 PASS/스킵이 불명확.
    • 개선: Server 베이스 합성을 import-time → 팩토리(make_server()) 시점으로 지연. → 재조사 결과 foundational 변경 위험으로 보류 유지(상세는 §3).
  • [H] 고정 pilot.pause(N) 438회 vs wait_until 18회: 느린 CI에서 거짓 실패, 러너 재시도 2회(run.py:39)가 진짜 회귀를 FLAKY PASS로 무마. 보일러플레이트(srv, task, sock = await server_only() + teardown) 214회 복붙.
    • 개선/수정: harness.py@asynccontextmanager running_app() → 복붙을 한 줄로 + wait_until 강제. → 수정 완료(harness running_server() 컨텍스트 매니저).
  • [H] 누적 문서 부채: 날짜별 교훈 로그 23개가 코드 참조 0건, 고아 다수, 메모리 색인과 중복. 산문 속 숫자 클레임이 stale(README.md:163 "845 passed" 실측 868; 플러그인 개수 표기 불일치; server.py:114 주석 "pyte 기본"이 아래 default "native"와 모순).
    • 개선/수정: durable 교훈은 주제별 영속 문서로 승격, 날짜 로그는 archive 격리, 산문 숫자 → 불변식(테스트) 위임. → stale 주석/숫자 수정 완료.

6. 안정성·견고성 — B

정량: pytmuxlib 전체 except Exception: 128곳. 단 대부분 의도적이며 좁게 잡혔다 — 핫패스는 _log_error()로 트레이스백 기록(serverio.py:774), 정리 경로는 contextlib.suppress, IPC는 (OSError, ConnectionError)로 좁힘. 무로깅 광역 except의 실질 갭은 PTY-host 계층·일부 폴백에 집중.

강점 (오탐 회피)

  • 와이어 프로토콜 (protocol.py): MAX_FRAME=64MiB 상한, read_msg가 EOF/비-JSON/decode 오류를 None 흡수, clamp_dim 가드. 튼튼함.
  • VT 파서 (vtparse.py): 증분 FSM으로 feed 경계 분할·멀티바이트 carry 안전, OSC _OSC_MAX=4096 캡, 과다 파라미터 arity 축소 재시도. tests/fuzz 상시 퍼징(protocol·ptyhost·clamp·vtparse).
  • IPC 보안 경계 (ipc.py): 토큰·peer-UID·/tmp 선점 거부·0600·TOCTOU-free. 견고.
  • usagedb 마이그레이션 (claude-code/usagedb.py:92-110): 단계별 sqlite3.Error 가드 + 실패 시 user_version 보류(재시도) + 멱등 staged + WAL. 데이터 손상 내성 양호.

발견 (High)

ID 발견 영향
H1 _UnixPty.write 부분 write/EAGAIN 미처리 입력 무음 유실
H2 PTY-host _read_loop 콜백 예외 광역 흡수 단일 패널 예외 → 전 패널 연결 오인 사망
H3 ConPTY spawn() 실패 시 핸들/프로세스 누수 조용한 폴백 + 매번 누수
H4 _remote_transport ssh 서브프로세스 reap/cleanup 누락 좀비/fd 누수 (단일 최고 ROI)
H5 자동 재연결이 "hello 성공"을 "병합 성공"으로 오판 거짓 "재연결됨" + reader/proc 누적
  • [H1] _UnixPty.write 부분 write/EAGAIN 미처리 (pty_backend.py:291-292): fd가 논블로킹 (:202)인데 os.write 1회 호출 + 반환값 무시. 큰 페이스트/포화 시 입력 무음 유실 또는 BlockingIOError_handle_input(serverio.py:993)에서 무음 드롭. 세 PTY 계층 중 재현 가장 쉬움.
    • 수정: memoryview 잔여 루프 write + EINTR 재시도 + EAGAIN 시 양보/버퍼링. → 수정 완료.
  • [H2] PTY-host _read_loop 콜백 예외 광역 흡수 (ptyhostclient.py:59-76 except Exception: pass): 한 패널 콜백 예외가 루프 break → _handle_lost() → host는 멀쩡한데 전 패널 연결 죽음으로 오인, 로깅도 0.
    • 수정: 콜백 per-frame try/except 격리, 진짜 단절(read_frame → None)일 때만 break. → 수정 완료.
  • [H3] ConPTY spawn() 실패 시 HPCON/파이프 핸들·프로세스 누수 (conpty.py:529-588 + pty_backend.py:625): __init__이 핸들 생성 후 spawn() 예외 시 close() 없이 버려짐. 상위 안전망(pty_backend.py:106-112 except Exception: pass)이 로깅 0으로 삼킴 → "조용한 폴백 + 매번 누수".
    • 수정: cp.spawn(...) try로 감싸 실패 시 cp.close() + 폴백 직전 1회 디버그 로깅. → 수정 완료.
  • [H4] _remote_transport ssh 서브프로세스 reap/cleanup 누락 (serverremote.py:206-246): wait_for(proc.stdout.readline(), 15) TimeoutError 시 proc(로컬 변수) kill/wait 안 됨 → ssh 자식 + 3 fd 누수. 핸드셰이크 실패 경로(:222)도 proc.kill()await proc.wait() 없어 <defunct> 회수 실패. readline()LimitOverrunError(>64KiB 무개행)가 except(:280)에 안 잡혀 attach 태스크 미처리 예외. 단일 최고 ROI 수정.
    • 수정: try/finally로 모든 실패 경로 proc.kill(); await wait_for(proc.wait(), 2) 보장 + except에 LimitOverrunError/ValueError 추가. → 수정 완료.
  • [H5] 자동 재연결이 "hello 성공"을 "병합 성공"으로 오판 (serverremote.py:382-389): 대화형 attach는 _remote_wait_first_status로 실제 탭 도착을 확인하나(serverio.py:442-452), _remote_reconnect_loopremote_attach() 반환값만 보고 그건 hello 송신 직후 True(:300). 업스트림 pty-host wedge 시 매번 "재연결됨" notice + 화면 안 옴 + reader/proc 누적.
    • 수정: 재연결 성공 기준을 _remote_wait_first_status 통과로 강화(대화형 경로와 정합). → 수정 완료.

발견 (Medium)

ID 발견 영향 / 수정
M1 _on_eofreap(block=True)가 이벤트 루프 블로킹 (ptyhost.py:198-213) 전 패널 I/O 정지 위험 → executor로 이전, 수정 완료
M2 PTY-host/federation read 타임아웃·keepalive 부재 (ptyhostproto, serverremote.py:503) half-open wedge 영구 hang(구조적 근원). ping op 존재 → 앱 keepalive / TCP SO_KEEPALIVE → keepalive, 수정 완료
M3 owned reader/watcher 스레드 join 부재 (pty_backend.py:716,764) teardown 비결정·드문 stale EOF → 스레드 join, 수정 완료
M4 restore_resume_state 부분 복원 시 master fd 누수 + except에 TypeError 누락 (serverpersist.py:256-279) 변조 상태 파일 시 복원 전체 크래시 → 세션 전손. 부분 실패 정리, 수정 완료
M5 재시작 상태 파일 비원자 쓰기 (serverpersist.py:131-133) execv 직전 사망 시 절반 JSON → 복원 실패. temp + os.replace(토큰/포트 파일은 이미 이 패턴), 수정 완료
M6 status 브로드캐스트 무추적 create_task(write_frames) 누적 §7 H-1과 동일 근원, 동반 수정 완료
M7 ensure_connected host spawn 실패 무로깅 + 최대 6초 블로킹 (ptyhostmgr.py:85) spawn 로깅, 수정 완료

7. 보안 — A−

선행 보안 감사·코드 감사의 적용 상태를 실제 코드와 대조 검증 → 문서에 "적용됨"으로 기재된 완화책은 전수 코드에 실재함을 확인. 와이어 JSON 전용(pickle/eval/exec/os.system/ shell=True 0건 재확인), subprocess 전수 argv, SQL 파라미터화, 토큰 + peer-UID 인증, 0600/0700 권한, OSC/CSI 하드닝, 런타임 퍼징까지 갖춘 매우 높은 성숙도. 그러나 그 이후 추가된 중첩(NEST) 경로에 net-new 취약점 1건.

[NEW-1] High — 위조 패널 출력으로 임의 호스트 아웃바운드 연결 + 키 입력 가로채기

위치: serverpty.py:179(_nest_dcs_scan 무조건 호출)·:232-246(ssh; DCS가 pane._ssh_dest 설정), serverremote.py:703-754(_nest_attach_request/_nest_do_attachtcp:// 접두 시 endpoint 직결)·:185-203(_remote_transport endpoint 분기는 ssh 옵션 가드 미경유), server.py:123 (nest_auto_attach 기본 True).

전제: 패널에서 신뢰 불가 프로그램 출력을 보는 것만으로 성립(위협 모델 #4). 무인가 IPC 접근 불요. remote_allowed_hosts 기본 비어 있음.

시나리오:

  1. _nest_dcs_scan모든 패널 raw 출력에 무조건 실행(provenance 게이트 없음).
  2. 악성 출력(조작 파일 cat, 손상 원격 셸)이 ESC P >|pytmux-ssh;<b64> ESC\ DCS를 emit해 pane._ssh_dest를 임의값(예: tcp:evil.example:9999)으로 설정.
  3. nest; REQ self-report도 동일값으로 맞춤.
  4. _nest_host_match(dest, selfreport) 가드는 공격자가 양쪽을 제어하므로 자명 통과.
  5. _nest_do_attachtcp: 접두라 endpoint 직결 분기remote_attachopen_connection("evil.example", 9999) 실 아웃바운드 TCP.
  6. 클라가 그 탭을 보면 live 키 입력이 공격자 서버로 릴레이(MITM/피싱).

핵심: 내부 설계 문서는 "attach 인자는 self-report를 쓰지 않고 래퍼가 기록한 pane._ssh_dest만 쓰므로 새 호스트 아웃바운드 불가"라 단언하나, pane._ssh_dest 자체가 신뢰 불가 패널 출력(ssh; DCS)에서 설정되므로 전제가 거짓. tcp:// 접두는 ssh 옵션 가드(-/공백 거부·allowlist)를 endpoint 분기로 완전 우회한다.

완화 현황(부분): ssh 분기면 옵션 가드가 막으나 endpoint 분기는 미경유. 디바운스(5s)·notice· nest_auto_attach OFF 스위치는 있으나 기본 ON이고 notice 전 키 입력이 흐를 수 있음.

개선/수정:

  1. (핵심) _nest_do_attach에서 _ssh_dest 발 dest는 tcp:// 로컬 endpoint 직결 금지, 항상 ssh 호스트로만 해석 → ssh 옵션 가드 경유 강제.
  2. (근본) ssh; DCS provenance 강화: 서버가 panel_env에 심은 nonce를 래퍼가 DCS에 포함, 서버가 검증 → 임의 프로그램이 _ssh_dest를 못 심게. 또는 자동 승격 대신 확인 팝업.
  3. (즉효 심층 방어) NEST 자동 승격에도 remote_allowed_hosts allowlist 적용(기본 비어도 endpoint 거부) 또는 nest_auto_attach 기본 OFF.

수정 완료: provenance 토큰 도입 + 비로컬 endpoint 직결 차단.

검증 완료 (문서 주장 = 코드 일치, 재보고 아님)

토큰 인증·peer-UID(TCP family는 None 반환)·state-dir lstat 거부·0600/0700·ssh 옵션 인젝션 거부(host 분기 한정)·SQL 전수 파라미터화(유일 f-string PRAGMA user_version={int} 안전)·역직렬화 무(pickle/eval/ exec/yaml 0, marshal.load는 로컬 신뢰 입력만)·subprocess 전수 argv·NEST DCS 정규식 {0,8192} 상한 (ReDoS 차단) — 전부 PRESENT.


8. LLM 친화성 — B+

강점 (긍정)

  • [L] grep 가능성 우수: 동적 디스패치 거의 없음. 명령 라우팅은 _run_command(client.py:1130) 명시 if/elif 76분기 → grep '"split-h"'로 핸들러 직행. getattr 454건은 대부분 플러그인 덕타이핑 한 곳(plugins/__init__.py)에 집중. 문자열 메서드 조립 메타프로그래밍 없음.
  • [L] 빠른 검증: 868 헤드리스 테스트, python tests/run.py 종료코드 0 = 통과. 56개 중 16개만 드라이버, 나머지 순수 단위 → LLM 회귀 검증에 이상적.
  • 플러그인 훅 계약 모범: plugins/__init__.pyRegistry가 ~40훅을 한 곳에 docstring과 함께.

발견

  • [H] Claude TUI 영어 스크래핑 결합 (claude.py:16~591): 핵심 기능(상태/토큰/계정)이 Claude Code CLI의 영어 TUI를 정규식/문자열로 긁는다. 하드코딩 리터럴 20+개: 상태("esc to interrupt"/스피너 글리프 ✽✢✳✶✷✻), 한도(_LIMIT_BLOCKED_RE reached|exceeded|hit), /usage 파싱 (Current\s+(session|week)/(\d{1,3})%\s*used), 모델 화이트리스트 (Opus|Sonnet|Haiku). 업스트림 문구 변경 시 깨진다.
    • 단 방어 상당: 계정은 이메일 신호만 신뢰 + unknown 폴백 + ReDoS 상한, 오탐 이력 docstring화, tests/fixtures/claude/ 실 캡처 7종 회귀망.
    • 약점: 진짜 한도 차단(limit.txt) 등 3종은 합성/미해결 → 가장 중요한 한도 경로 실측 미검증.
    • 개선/수정: 골든 픽스처 정기 대조 회로 차단기, 가능한 곳은 ANSI/화면 모델 구조 신호로 이전, 모델 화이트리스트 카탈로그 외부화, 미해결 픽스처라도 명시 회귀 테스트. → 모델 화이트리스트 외부화 완료.
  • [M] 거대 파일 vs 컨텍스트 윈도우: test_client.py 5,992줄·test_server.py 4,064줄·client.py 3,981줄. LLM이 일부만 보고 수정 → 회귀. 완화: client.py는 ~14개 명확한 Mixin(grep '^class '로 부분 Read 가능)이라 단일 더미는 아님.
    • 개선/수정: test_client.py를 기능별 분할(import 그래프 단순, 저비용·고효과), client.py Mixin 물리 분리. → client.py 분할 수정 완료.
  • [M] 동적 합성 ServerClaudeMixin → 정의 위치 grep 불가 (plugins/__init__.py:163): server.server_scan() 실제 구현이 claude-code/servermixin.py에 있고 런타임 합성 → Server 정의만 보면 "정의 없는 호출"로 보인다.
    • 개선/수정: 호출 지점 근처에 # 구현: claude-code/servermixin.py:ServerClaudeMixin.server_scan 포인터 주석. → 수정 완료.
  • [M] 루트 온보딩 문서 부재 + 문서 노이즈: 온보딩 진입점이 표준 위치에 없고 정보가 여러 문서에 분산. 대형 핸드오프 문서를 무심코 Read 시 컨텍스트 예산을 통째로 소진. 완화: 메모리 색인이 자산 역할.
    • 개선/수정: 루트 온보딩 문서 신설(빌드/테스트 명령, 훅 계약 위치, delete-to-disable 규약, 거대 파일/문서 Read 주의 30~50줄). → 수정 완료(CLAUDE.md).
  • [L] 타입/docstring 커버리지(AST 측정): docstring 52%(858/1,648), 타입 어노테이션 51%(801/1,548). 평균 이상이나 절반 무타입 → 신규 코드 어노테이션 의무화, 특히 훅 시그니처 전수 타입화.
    • 수정(부분): 값 반환 9개 훅에 반환 타입 명시. void 디스패처 16훅은 zero-signal churn 회피로 제외.

9. 동시작업 대응 — B

아키텍처 전제 (핵심)

서버는 단일 스레드 asyncio 루프. 동시성 프리미티브는 PTY 백엔드 경계 4곳뿐(conpty 락 3 + pty_backend reader/watcher 스레드 + usageprobe 락 1). 서버 비즈니스 로직(model/tree/io/remote)에 락이 전혀 없고 필요도 없다 — reader 스레드는 loop.call_soon_threadsafe로만 펌프 (pty_backend.py:454), 구조 변경 연산(servertree.py split/kill/new_window)은 전부 동기라 원자적. 협조적 스케줄링 덕에 model 동시 변경은 락 없이 안전. 위험은 await가 가르는 read-then-write공유 writer의 비직렬화 fan-out에 집중.

강점 (검증)

  • [A] PTY 스레드 ↔ 루프 경계 (pty_backend.py:421-475): call_soon_threadsafe 전용 진입 + 백프레셔(pause_reader) + 종료 가드.
  • [A] usagedb 동시 쓰기 (usagedb.py:80-90): 서버당 단일 연결 + WAL + busy_timeout=3000 + 소켓별 파일 격리. 한 소켓 = 한 서버 직렬 쓰기 → 경합 없음.
  • [A−] ptyhost 소유권 (ptyhost.py:91-116): last-wins + _PENDING_MAX 1MB 링 flush + 서버측 reconnect-storm 가드(serverio.py:273-312).

발견

  • [H-1] Fan-out 송신 per-writer 직렬화 부재 → 프레임 순서 역전 (serverio.py:253 create_task(_send_full(c))serverremote.py:340/464/578, servertree.py:589 등; protocol.py에 write 락 0건): 같은 StreamWriter에 flush 루프 write_frames와 다중-await _send_full이 동시 미결 → (구)full … (신)delta … (구)full 프레임 혼입. 클라는 도착순 적용 + 시퀀스 번호 없음(client.py:3261) → 한 프레임 깨짐(다음 flush 33ms 내 자가 교정, 영구 손상은 아님).
    • 수정: ClientConnasyncio.Lock 또는 클라당 outbound 큐. 최소 대안: _send_full을 단일 write_frames로 묶어 다중 await 제거. → 수정 완료(송신 fan-out 직렬화).
  • [H-2] Slow/stalled consumer가 flush 루프 전체를 head-of-line 블록 (serverio.py:399-400 for c: await write_frames(c.writer, ...); 송신 경로에 wait_for/high-water 0건): 한 클라 TCP 수신 윈도가 포화하면 writer.drain()(protocol.py:98) 무기한 대기 → 모든 클라·모든 세션 렌더 정지. 단일 최고 위험.
    • 수정: 클라별 송신 task 분리 또는 wait_for(write_frames(...), timeout=N) + high-water 초과 시 느린 소비자 마킹/드롭. → 수정 완료(느린 소비자 가드).
  • [M-1] 원격 입력 릴레이 비직렬화 fire-and-forget (serverremote.py:622): 빠른 연속 키 + resize 릴레이가 link.writer를 공유하면 순서가 흔들린다. 라우팅 화이트리스트(_REMOTE_RELAY_ACTIONS)는 정합 확인(재보고 아님). H-1의 per-link 직렬화로 동반 해소. → 수정 완료.
  • [M-2] run_in_executor 사이 stale 모델 write (servertree.py:572-575 _autorename_loop): await suspend 동안 kill_window가 끼어들면 제거된 탭에 stale 이름 write(양성 결함, 다음 루프 교정).
    • 수정: resume 후 tab in sess.tabs and 캡처 pane is active 재확인. → 수정 완료(autorename 가드).

개발 워크플로 동시성 (간략)

거대 단일 파일(client.py ~3,981줄·serverio.py 1,184줄·model.py 1,323줄·claude servermixin.py 2,181줄)이 동시 편집 충돌 표면을 키운다 — 병렬 작업이 같은 파일의 다른 영역을 건드려도 버전 관리 edit/commit이 파일 전체를 잡아 미커밋 줄을 동반 게시할 수 있다. 완화는 코드보다 워크플로 게이트(게시 전 diff로 본인 hunk만인지 확인)이며, 코드 차원 추가 완화는 §5·§8의 거대 파일 분할과 동일한 처방이다.


10. 교차 종합 처방 (관점 횡단, 우선순위)

# 처방 관점 심각도 효과
1 NEST 자동 승격 endpoint 직결 차단 + provenance 강화 보안 H 유일 실재 취약점 봉합
2 _remote_transport proc try/finally 정리 + LimitOverrunError 포착 안정 H 좀비/fd 누수 차단(최고 ROI)
3 송신 fan-out slow-consumer 타임아웃 + per-writer Lock 동시 H 멀티 클라이언트 가용성 직결
4 Server 베이스 합성 지연(make_server()) + harness.running_app() CM 유지보수 H 테스트 거짓 실패 + 복붙 동시 해소
5 거대 파일 분할(client.py Mixin 물리 분리·test_client.py 기능 분할) 유지/LLM/동시 H 3관점 부채 동시 완화
6 PTY-host/federation read 타임아웃·keepalive 안정 M 'wedge 영구 hang' 근원 봉합
7 코어 옵션 _OPTS_KEYS 테이블 통합 유지보수 H usage_refresh_sec 영속 버그 해결
8 _UnixPty.write 잔여 루프 + EAGAIN 처리 안정 H 입력 무음 유실 차단
9 루트 온보딩 문서 신설 + 위젯 한글 리터럴 lint LLM/유지 M 온보딩·i18n 누출 동시
10 날짜 로그 아카이브·durable 교훈 승격, 산문 숫자 → 불변식 유지보수 M 문서 rot 부채 정리

한 줄 결론: 코어 아키텍처(명시 디스패치·집중 훅 계약·단일 루프 동기 구조 변경·견고한 프로토콜/ 파서/IPC·상시 퍼징)는 이미 우수하며 선행 감사 완화책도 실재한다. 남은 부채는 두 갈래로 수렴 한다 — (가) 거대 파일/God 클래스(유지보수·LLM·동시 편집 충돌 동시 악화)와 (나) out-of-process 경계(PTY-host·federation)의 실패 경로·타임아웃·송신 fan-out 견고성. 보안은 NEST 자동 승격 1건만 잡으면 A급을 유지한다. 위 1~5번 착수 시 전 관점이 한 등급씩 올라간다.


관련 문서

Clone this wiki locally