뜯어본 것공개

Odysseus

문서에 적힌 규율 다섯 개가 코드로 강제되는지 하나씩 찾아봤습니다. 강제되는 건 하나였습니다.

문서는 저보다 앞서 있는데, 그 문서를 강제하는 장치는 저보다 뒤에 있었습니다. 이 레포가 자기 문서에 선언한 규율 다섯 개를 골라 그것을 강제하는 실행 지점이 코드에 있는지 전수 확인한 결과, 다섯 중 하나만 실재했습니다. 나머지 넷은 규율이 아니라 잘 쓰인 의도였습니다. 그리고 그 하나 — 프롬프트 인젝션 가드 — 가 이 조사에서 제가 실제로 가져온 유일한 물건입니다.

docker compose — 네 컨테이너가 한 벌로 뜬다 odysseus :7000 FastAPI · 바닐라 JS chromadb 벡터 검색 searxng 메타 검색 ntfy 푸시 알림 본체 안 — 파이썬 21만 줄 · 자바스크립트 16만 줄 core · 11파일 인증 · DB · 미들웨어 routes · 75파일 채팅 · 메일 · 캘린더 src · 143파일 에이전트 루프 · 압축 prompt_security 유일하게 검증 통과 파란 칸 둘이 이 조사에서 살아남은 것 — 나머지는 규모가 크되 규율이 강제되지 않는다
셀프호스팅 AI 워크스페이스. 도커 컴포즈 한 줄로 벡터DB·메타검색·푸시까지 같이 올라온다. 생후 두 달, 별 84,277개.

무엇을 하는 물건인가

남의 서버에 안 맡기고 내 기계 위에 통째로 올리는 AI 작업 공간이다. 대화·메일·캘린더·문서 검색·심층 리서치·이미지 생성이 한 웹 UI 안에 들어 있고, 그 뒤에서 에이전트 루프가 툴을 돌린다. 모델은 로컬이든 API든 붙일 수 있다.

파일로 보면 이렇다.

파일
파이썬 1,064 213,480 FastAPI
자바스크립트 162 158,514 번들러 없음 — 바닐라 ES 모듈
테스트 754 84,992 런타임 대비 40%
src/ 143 56,115 평평한 폴더 — 최대 구조 부채
mcp_servers/ 5 3,531 메일·이미지·메모리·검색 내장

최대 파일이 메일 라우트 6,032줄, 에이전트 루프 5,248줄이다. 생후 두 달에 이 규모라는 게 이 레포를 읽을 때 계속 붙는 조건이다.

설계에서 눈에 띄는 결정 둘. 에이전트 루프가 펜스 코드블록을 파싱해서 툴을 부른다 — 네이티브 함수 호출도 같은 경로로 합류시킨다. 함수 호출을 지원 안 하는 로컬 소형 모델까지 받으려는 선택이다. 그리고 컨텍스트가 85%에 닿으면 자동으로 압축하고, 입력 예산을 모델의 컨텍스트 창에서 동적으로 산출한다.

별 84,277개는 근거로 못 쓴다

포크가 187개다. 비율이 450 대 1인데 정상적인 오픈소스는 5~20 대 1이다. 유명 유튜버 본인 프로젝트라 스타는 눌렀지만 코드는 안 만지는 사람이 압도적이라는 뜻이다.

봇 조작과는 다르다. PR 3,317건에 기여자 313명이라 활동이 같이 크다 — 봇 스타는 활동을 동반하지 않는다. 그러니 숫자 자체는 진짜고, 다만 품질 신호가 아니다.

2차 출처도 못 쓴다. 이 프로젝트를 소개한 글 여럿이 라이선스를 MIT라고 적었는데 실제로는 AGPL-3.0이고(도입 판단을 통째로 뒤집는 오보다), 포크를 10,000개라고 적었는데 실제로는 187개다. 이 글의 수치는 전부 API와 얕은 클론으로 직접 쟀다.

규율 다섯 중 코드가 강제하는 건 하나였다

문서가 유난히 좋은 레포다. 위협 모델 문서, 테스트 표준 문서, 의존성마다 붙은 근거 주석. 그래서 문서가 선언한 규율을 다섯 개 골라, 그것을 강제하는 지점이 코드에 실재하는지 하나씩 찾았다.

문서가 선언한 규율 코드가 강제하는가 프롬프트 인젝션 가드 통과 호출부 29개 실재 테스트 754파일 8.5만 줄 불합격 머지를 안 막는다 수집 시점 자동 분류 절반 41% 미분류 행위 우선 소스 검증 금지 불합격 본인들이 45곳 위반 의존성마다 근거를 적는다 불합격 30 중 26 무핀 다섯 중 하나. 나머지 넷은 규율이 아니라 잘 쓰인 의도였다
선언과 강제를 따로 셌다. 문서가 좋다는 것과 그 문서가 지켜진다는 것은 다른 사실이다.

통과한 하나 — 프롬프트 인젝션 가드. 위협 모델 문서는 외부에서 들어온 텍스트가 전부 신뢰불가 래퍼를 거친다고 주장한다. 웹 검색결과, 가져온 URL, 유튜브 자막, 검색된 문서, 저장된 메모리, 스킬 텍스트, 통합, MCP 툴 설명, 메일, 업로드 파일, 심층 리서치 크롤링. 테스트를 뺀 호출부가 29개 실재하고, 주장한 표면을 실제로 덮는다. 툴 실행 결과까지 감싼다 — 대부분의 구현이 놓치는 지점이다.

불합격 셋.

  • 테스트가 게이트가 아니다. 8만 5천 줄이 있는데 CI 잡에 continue-on-error: true가 붙어 있다. 깨져도 초록불이고 머지를 안 막는다. 실제 머지 방어선은 문법 검사 + 보안 스캔 + 사람 리뷰가 전부인데, 두 달간 PR이 3,317건 들어온 레포에서 품질 방어선이 사람이라는 뜻이다
  • 행위 우선 검증을 스스로 어긴다. 표준 문서가 “소스 텍스트나 AST가 아니라 관측 가능한 행위를 검증하라”고 못 박는데, 소스를 직접 읽는 테스트가 45개 있다. 그중에 보안 불변식을 소스 문자열 매칭으로 고정한 것이 있다 — 변수명만 바꿔도 깨지고(거짓 실패), 다른 경로로 격리가 뚫려도 통과한다(거짓 통과). 가장 나쁜 조합이다
  • 의존성 30개 중 26개가 버전 무핀이고 락파일이 없다. 암호·인증 라이브러리까지 포함해서다. 권장 설치 경로가 docker compose up --build사용자마다 다른 의존성 조합을 받는다 — 재현 불가능한 빌드다

가장 아이러니한 대목: CI 액션은 커밋 해시로 핀 고정해 공급망 공격을 막는데, 정작 프로덕션에 들어가는 런타임 의존성은 무방비다. 공급망 규율이 CI에만 있고 런타임엔 없다.

절반 하나. 테스트 754개에 수집 시점 자동 분류를 씌웠는데, 분류기를 직접 돌려 보니 41%가 미분류다. 가장 중요한 5,248줄짜리 에이전트 루프의 테스트가 어느 영역에도 안 들어간다 — 키워드 매칭에 “agent”가 없어서다. 다만 이건 설계 의도이기도 하다(아래 참조).

가져온 것 — 가드 마커 이스케이프

신뢰불가 텍스트를 구분자로 감싸 프롬프트에 넣을 때, 그 텍스트 안에 구분자 리터럴이 들어 있으면 블록이 조기 종료된다. 그 뒤 내용은 신뢰 구역으로 새어 나간다. 이 레포는 넣기 전에 마커를 무해한 토큰으로 치환하고, 라벨도 같이 처리하고, 개행까지 제거한다.

구현이 열 줄이다. 그리고 이게 제게 없던 방어입니다.

보안 문제이기 전에 사고 문제다. 기획서나 이슈 원문을 통째로 모델에 넣는 지점이 여럿인데, 문서에 우연히 구분자 비슷한 문자열이 있으면 공격자 없이도 프롬프트 구조가 깨진다. 실제로 대괄호 태그 하나가 파서를 세운 일이 이미 있었다. 악의를 가정하지 않아도 도입 근거가 선다.

같이 본 것 둘, 원리만 가져왔다.

  • 모르는 것을 크게 드러내는 분류. 41% 미분류는 사고가 아니라 설계다. 분류기 독스트링이 *“추측하느니 미분류로 남기는 쪽을 선호한다”*고 적어 뒀고, 미분류 파일도 자기 마커를 받아 주소를 갖는다. 그래서 “아직 이해 못 한 영역”이 숫자로 보인다. 모든 항목을 어떤 분류에든 밀어 넣으면 이해 못 한 것이 정상 분류 속에 숨는다 — 목표는 0%가 아니라 추세다
  • 끄되, 왜 끄는지와 언제 켤지를 그 자리에 적는다. 테스트를 게이트에서 뺀 건 실패지만 빼는 방식은 배울 만하다. continue-on-error 바로 위에 현 상태·이유·졸업 조건 세 줄이 붙어 있다. ⚠졸업 조건에 기한이 없으면 영구가 된다는 게 함정이지만, 아무 말 없이 끄는 것보다는 낫다

판정

사내 도입 ✗ — AGPL-3.0에 관리자 = 셸 실행 + 파일 읽기쓰기 + 메일 발송, 의존성 무핀, 생후 두 달, 릴리스 태그 0개
개인 실험 △ — 무핀 의존성과 비게이트 테스트가 겹치면 어제 되던 게 오늘 깨진다. 도커 격리 · 외부 미노출 조건
코드 읽기 ◎ — 단 프롬프트 보안 모듈만

에이전트 루프 패턴을 보러 왔다면 여기가 아니다. 이 레포가 그 패턴을 상류에서 가져왔다고 스스로 밝히고 있고, 상류는 MIT라 라이선스도 자유롭다.

⚠ 그리고 이 조사가 남긴 규칙 하나. 처음 읽었을 때 저는 이 레포를 “규율 있는 프로젝트”로 판정했다. 문서가 워낙 좋아서다. 잘 쓰인 문서를 보고 문서의 품질을 코드의 품질로 착각한 것이고, 위 다섯 칸 중 넷이 그 착각의 크기다.

“규율이 있다”고 쓰려면 그 규율을 강제하는 실행 지점 — CI 게이트·훅·린터 — 을 먼저 찾는다. 없으면 그건 규율이 아니라 의도다.