Build Log · Drewgent infra · 2026-07-13

출시 전날, 내가 만든 backdoor를 발견했다

isAgent === true. 6글자. 이 한 줄 때문에 결제 우회가 가능했다. 그런데 이 코드는 누군가가 일부러 심은 게 아니었다. 3주 전에 만든 rate limiter bypass 플래그가, 리팩토링을 거치면서 결제 게이트를 통과하는 통로로 변한 거다.

더 무서운 사실: 나는 이 코드를 스무 번은 본 셈이다. PR 리뷰에서도, 배포 전 점검에서도. “원래 있던 코드”라는 익숙함이 나를 blind하게 만들었다.

이 글에서 다루는 것

  • 왜 “있는 코드”가 가장 위험한가
  • Fresh-Eye adversarial review 시스템 — 구조와 설계 결정
  • 6개 체크리스트 + 대전제 회귀검사
  • housekeeper + cron 통합으로 자동화한 방법

문제: 익숙함이라는 면역 결핍

소프트웨어에는 두 종류의 버그가 있다. 새로 만든 코드의 버그는 금방 발견한다. 테스트도 있고, PR 리뷰도 있다. 문제는 반대쪽. 이미 존재하는 코드가 시간이 지나면서 의미가 변하는 경우다.

내가 겪은 패턴:

  • Rate limiter bypass로 만든 isForce 플래그가 결제 검증을 통과하는 통로가 됨
  • 로컬 개발용 anon_guest pooled identity가 프로덕션에서 모든 사용자의 리포트를 공유하게 만듦
  • “나중에 고치자” 주석이 3개월 후에는 스펙으로 굳어짐

문제는 코드 품질이 아니라 인지 편향이다. “원래 있던 코드”는 뇌가 자동으로 필터링한다. 마치 자기 방 안의 냄새를 못 맡는 것처럼.

결정: 처음 보는 시각을 강제하자

처음 든 생각: “코드 리뷰를 더 열심히 하면 되지 않을까?”

아니었다. 문제는 열심히가 아니라 시각이었다. 같은 사람이 같은 코드를 10번째 보는데 갑자기 “이거 위험한데?”라는 깨달음이 올 확률은 기하급수로 떨어진다.

필요한 건 의도적으로 신선한 눈을 가장하는 체계였다. 코드 리뷰어를 더 뽑는 대신, 검사 자체를 프로토콜화하기로 했다.

설계 제약:

  • $0. 사람을 더 고용하지 않는다. LLM을 쓴다면 flash 모델로.
  • Machine-verifiable. “이상한 느낌”이 아니라 grep으로 잡을 수 있어야 한다.
  • 주기적. 일회성 검사는 의미가 없다. 버릇이 되어야 한다.
  • 회귀검사. 대전제(axioms)를 정의하고 모든 변경이 이를 위반하는지 확인한다.

무엇을 만들었나

1. Fresh-Eye Skill — 6개 체크리스트

하나의 opencode skill로, 6개 영역을 순차 검사한다.

영역 질문 예시 패턴
Bypass/Backdoor 우회 플래그가 있나? isAgent, isForce, IS_LOCAL_DEV
Pooled Identity 공유 ID가 있나? anon_guest, shared
Dead/Unused 죽은 코드가 있나? 선언만 있는 함수, 주석 처리
Dual Source Drift 중복 구현이 있나? frontend/ vs public/
Security Smell client 값을 trust하나? innerHTML, 미검증 amount
Configuration Drift deprecated API를 쓰나? deepseek-chat, 하드코딩된 secret

각 질문은 machine-verifiable하다. 예를 들어 bypass 체크는 grep -rn "isAgent"로 1초면 끝난다. 복잡한 정적 분석이 필요하지 않다. 문제는 누군가 그 grep을 실행해야 한다는 사실이다. 그게 바로 fresh-eye의 역할.

2. AXIOMS — 대전제 회귀검사

모든 프로젝트는 AXIOMS.md에 대전제를 정의한다.

# m-log AXIOMS.md
1. 결제 bypass는 4point@m-log.cc + IS_LOCAL_DEV만 허용
2. 모든 결제 금액은 서버 catalog 기준, client 금액 신뢰 금지
3. 모든 LLM 호출은 deepseek-v4-flash, thinking 비활성화
4. pooled anonymous identity 금지
5. 리포트 생성 실패 시 히스토리 저장 금지

각 axiom은 machine-verifiable해야 한다는 규칙이 핵심이다. “코드는 깔끔하게” 같은 모호한 원칙은 금지. grep "anon_guest"로 통과/실패가 결정되어야 한다.

이 파일이 있으면, 모든 결정을 axiom에 회귀검사할 수 있다. “카카오페이를 추가할까?” → axiom #2 위반 (단일 PG 원칙). “게스트 리포트를 캐시할까?” → axiom #4 위반.

3. Housekeeper 통합 — 자동 bypass 스캐너

가장 실용적인 부분이다. housekeeper(매일 04:00 deep clean)에 bypass 패턴 스캐너를 심었다.

SUSPICIOUS_PATTERNS = [
    r"(?i)(isAgent|isForce|isLocalDev)s*===s*true",
    r"['"]anon_guest['"]",
    r"forces*(===|==)s*true",
    r"deepseek-chat",
    r"payload?.isAgent",
]

이 패턴 중 하나라도 새로 발견되면, Discord로 경고가 간다. 더 중요한 건: 한 번 발견된 패턴은 knowledge.db에 저장되어 다음 fresh-eye 실행 시 reference로 사용된다.

Slice 1: 패턴 수집 → 검사 엔진

처음에는 스킬만 만들었다. 그런데 “일요일 06:00에 실행”이라는 cron job을 보고 든 생각: 이 사이에 누군가 bypass를 커밋하면? 그럼 일주일 동안 프로덕션에 위험한 코드가 떠돈다.

그래서 housekeeper deep clean에 실시간 스캐너를 추가했다. 매일 04:00마다 전체 코드베이스를 스캔한다. 주간 검사 + 일일 감시의 2계층 구조.

발견된 패턴은 3단계로 분류된다:

  • 🔴 Critical — 런칩 블로커. 결제 bypass, 인증 우회.
  • 🟡 Warning — 개선 권장. deprecated API, 중복 코드.
  • 🔵 Info — 참고. 주석 처리된 코드, 컨벤션 위반.

Slice 2: 일주일 후 — 실제 발견 사례

fresh-eye를 운영한 지 일주일. 벌써 2건을 찾았다:

  • isAgent — 테스트용 인증 우회 플래그가 프로덕션 번들에 포함됨
  • deepseek-chat — deprecated model name이 새 파이프라인에 재사용됨

두 건 모두 “원래 있던 코드를 복사했다가” 발생했다. 새로운 코드를 작성할 때는 항상 조심하지만, 기존 패턴을 따라갈 때는 조심하지 않는다는 걸 증명하는 사례.

설계 결정과 실패 모드

결정 선택 대안 이유
검사 방식 grep 기반 패턴 매칭 정적 분석(ESLint, Semgrep) $0, 학습 불필요, 1초면 실행
실행 주기 일간 + 주간 2계층 PR 마다 실행 PR gate는 이미 bypass 존재
저장소 knowledge.db + AXIOMS.md 별도 DB SQLite 하나로 통합
발견 시 Discord 알림 자동 수정 자동 수정은 HITL 원칙 위반

알려진 한계

  • LLM 생성 패턴은 탐지 못 함 — 템플릿 기반 bypass는 잡지만, LLM이 즉석에서 만든 변종은 놓친다
  • False positiveisLocalDev가 정당한 dev 환경 체크일 수도 있다. Critical 경고는 항상 사람이 확인해야 한다
  • machine-verifiable 제약 — “이 코드는 너무 복잡하다” 같은 원칙은 axiom으로 만들 수 없다

정리: 처음 보는 시각을 시스템화하는 법

fresh-eye가 가르쳐준 교훈은 간단하다.

  • 인간의 뇌는 익숙한 것을 필터링한다. 이를 인정하고 시스템으로 보완해야 한다
  • Machine-verifiable 원칙이 모호한 원칙보다 100배 강력하다. “보안에 신경 써” vs “grep ‘isAgent’가 0이어야 함”
  • 나 혼자서도 adversarial review를 할 수 있다. 신선한 눈을 강제하는 프로토콜만 있으면
  • 사후 발견보다 사전 예방. bypass가 프로덕션에 도달하기 전에 잡는 게 핵심

이 시스템은 하루 0.00038달러(flash 모델 1회 호출)로 운영된다. 내 대신 코드를 읽어주는 시니어 개발자를 고용한 셈이다. 단, 그 시니어 개발자는 처음 보는 코드처럼 읽어준다.

이 시스템의 전체 코드는 skills/brain/fresh-eye/SKILL.md와 scripts/drewgent_housekeeper.py(check_code_bypasses)에 있다. AXIOMS.md 템플릿은 PRD 스킬에서 확인할 수 있다.