# AI 생성 코드를 어떻게 리뷰할까? 테스트가 모두 통과해도 반례와 증거로 검증하기

> Canonical: https://vxi.me/ko/posts/vibe-coding-ai-code-review-evidence-gates/
> Locale: ko-kr
> Published: 2026-10-25
> Updated: 2026-10-25
> Markdown: https://vxi.me/ko/posts/vibe-coding-ai-code-review-evidence-gates.md

<figure class="ai-review-evidence-hero not-prose" lang="ko-kr">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.mobile.still.ko-kr.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.still.ko-kr.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.mobile.ko-kr.svg" type="image/svg+xml"><img src="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.ko-kr.svg" alt="서로 다른 세 코드 버전이 8/8, 10/10, 12/12 테스트를 통과했다. 독립 리뷰에서 네 종류의 결함이 발견됐고 별도 탐침 네 개가 차단됐다. 오프라인 한정." width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption style="font-size:0.8rem;line-height:1.5;opacity:.78;margin-top:.35rem">서로 다른 세 버전의 테스트와 독립 리뷰 결과입니다. 초록색 테스트 통과는 운영 배포 승인이 아닙니다.</figcaption>
</figure>

Vibe Coding은 코드 생산 속도를 높였지만 리뷰 부담까지 같은 속도로 줄여 주지는 않습니다. LLM은 깔끔한 diff를 빠르게 만들 수 있지만, 자연스러운 설명이나 테스트의 전체 통과만으로 운영 환경에 안전한 구현이라고 증명되지는 않습니다.

사례는 이 블로그 프로그램의 애니메이션 터미널 SVG를 위한 빌드 전 검증기입니다. 제한된 XML/CSS 규칙, manifest 경로와 SHA-256이 실제 파일과 일치하는지, 여덟 언어 글이 화면에 보이는 데모 이미지를 참조하는지 확인합니다. 고정 canonical SVG는 정상 입력용 fixture입니다. 네 가지 결함은 병합 전 격리 테스트에서 발견됐으며, 증거 범위는 이 검증기의 오프라인 검사에 한정됩니다.

초기 테스트는 **8/8 통과**했지만 독립 리뷰에서 잘못된 허용 두 건이 발견됐습니다. 첫 수정 뒤 **10/10 통과**했으나 다음 리뷰에서 두 건이 더 발견됐습니다. 최종 고정 버전은 주요 테스트 **12/12 통과**했고, 독립 리뷰어가 추가 공격 탐침 네 개를 실행해 모두 거부됐습니다. JavaScript 파일 세 개도 node --check를 통과했습니다.

범위와 불변 조건을 고정하고, 최소 반례를 만들고, 실제 실패가 관찰된 경우에만 RED로 기록하고, 정확한 commit에서 양성·음성 사례를 독립 재검증한 뒤 위험에 따라 판단합니다. 로직 테스트는 빌드, 플랫폼 검사, 런타임 검증을 대신하지 않습니다. 고위험 변경은 범위, Base/Head commit, 불변 조건, 정상 사례, 반례, 실제 명령과 환경, 미검증 경계, 결론을 작은 증거 카드에 기록할 수 있습니다. 검증기는 블로그 저장소의 빌드 전 구성요소이며 별도 배포되는 범용 CLI가 아닙니다. 리뷰 시간 절감도 측정하지 않았습니다.

공개 저장소에는 비식별 처리된 합성 반례와 실행 가능한 교육용 테스트만 포함됩니다. 이 블로그 프로그램의 운영용 검증기, 실제 수정 코드, 내부 아키텍처는 포함하지 않습니다. 본문의 8/8→10/10→12/12는 원래 엔지니어링 작업의 오프라인 검증 결과이며, 공개 테스트는 네 가지 결함 유형만 재현하고 운영 구현을 복제하지 않습니다.

<a class="gh-repo-pill" href="https://github.com/vxime/ai-code-review-counterexamples/tree/74d9f12331ef4d015bf882a6e09d66e59434368b" target="_blank" rel="noopener noreferrer"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" width="18" height="18"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645.12.54 2.873.12 3.176.765.84 1.23 1.91.81 3.22.61 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606.21 2.896.015 3.286C20.565 22.092 24 17.592 24 12.297"/></svg><span>비식별 합성 예제와 재현 가능한 테스트</span></a>

## 왜 8/8 테스트를 통과하고도 잘못된 입력을 허용했을까?

첫 번째 반례는 허용된 이미지와 승인되지 않은 다른 글의 리소스 참조를 한 줄에 넣었습니다. 기존 로직은 허용된 이미지 태그 하나를 발견하면 줄 전체를 통과시켰습니다. 독립 리뷰는 다른 slug에 실제 SVG 파일을 만들고 여덟 언어 글의 정상 이미지와 같은 줄에 Markdown 참조를 추가했습니다. 파일 부재로 인한 가짜 실패를 배제하기 위해 대상도 실제로 만들었습니다. 기존 구현은 posts_checked=8로 성공을 반환했습니다.

~~~html
<img src="/assets/terminal-demo.svg" alt="Terminal demo" width="356" height="137" /> ![foreign](../foreign/terminal-demo.svg)
~~~

두 번째 반례는 파일 시스템 경쟁 조건입니다. 검사한 경로와 최종적으로 열린 파일이 달라질 수 있습니다. checkout 안의 원래 경로에는 잘못된 바이트가 있고 외부에는 기대 SHA-256과 일치하는 canonical SVG가 있었습니다. 부모 디렉터리를 외부 디렉터리로 향하는 symlink로 바꾸자 기존 구현은 외부 파일을 읽고 잘못 허용했습니다. 우회된 것은 해시가 아니라 파일 출처 제약입니다.

최종 Linux 경로는 신뢰된 checkout 루트를 열고 이미 열린 부모 디렉터리 파일 디스크립터를 기준으로 경로를 해석합니다. 중간 요소는 디렉터리여야 하며 O_NOFOLLOW를 사용하고 최종 파일의 symlink도 거부합니다. 파일 종류, 링크 수, 크기를 검사하고 읽기 전후의 device, inode, 크기를 비교한 뒤 digest를 확인합니다. Linux /proc/self/fd를 사용하고 procfs가 없으면 fail closed합니다. 범위는 신뢰된 checkout 루트와 procfs를 전제로 하며 임의의 동일 UID 또는 호스트 수준 공격자는 포함하지 않습니다. [Linux openat2(2) 문서](https://man7.org/linux/man-pages/man2/openat2.2.html)를 참고하세요.

## 두 번째 라운드: 해시는 맞아도 콘텐츠가 보이지 않을 수 있다

첫 수정 뒤 테스트는 **10/10 통과**했습니다. 세 번째 반례는 reduced-motion 설정에서 마지막 프레임이 보이는지에 관한 문제입니다. SVG 프레임은 기본적으로 투명하고 애니메이션이 내용을 드러냅니다. 움직임 줄이기 규칙이 애니메이션만 멈추고 마지막 프레임을 표시하지 않으면 화면이 비어 있을 수 있습니다.

~~~css
.f { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  #f0 { animation: none; opacity: 1; }
  #f3 { animation: none; } /* 최종 프레임은 투명 */
}
~~~

테스트는 SVG SHA-256, 바이트 크기, content-addressed 파일명, canonical manifest를 다시 계산해 주변 검증을 유지했습니다. 검증 대상은 “최종 결과가 보여야 한다”는 계약입니다. [MDN의 prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/%40media/prefers-reduced-motion)를 참고하세요.

네 번째 반례는 Markdown 주석입니다. 기존 검증기는 정규식으로 이미지 태그를 세면서 HTML 주석을 제외하지 않았습니다. 유일한 이미지를 주석으로 감싸면 소스에 태그는 남아도 페이지에는 보이지 않지만 기존 로직은 이미지 한 장으로 계산했습니다. [CommonMark 0.31.2의 HTML block 규칙](https://spec.commonmark.org/0.31.2/#html-blocks)에 따르면 주석 안의 태그는 보이는 이미지가 아닙니다.

~~~html
<!-- <img src="/assets/demo.svg" alt="Terminal demo" /> -->
~~~

<figure class="ai-review-counterexample not-prose" lang="ko-kr">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.mobile.still.ko-kr.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.still.ko-kr.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.mobile.ko-kr.svg" type="image/svg+xml"><img src="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.ko-kr.svg" alt="첫 수정 버전이 10/10 테스트를 통과한 뒤에도 HTML 주석 안의 이미지를 보이는 이미지로 잘못 계산하는 문제가 발견됐다. 최종 12/12 통과, 별도 탐침 네 개 차단." width="1200" height="700" loading="lazy" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption style="font-size:0.8rem;line-height:1.5;opacity:.78;margin-top:.35rem">테스트가 모두 통과해도 반례를 놓칠 수 있습니다. 주석 안의 이미지 태그는 보이는 이미지가 아닙니다.</figcaption>
</figure>

## 테스트 결과를 정확한 버전에 연결하기

| 코드 버전 | 주요 테스트 | 이후 독립 리뷰 |
| --- | --- | --- |
| 초기 버전 | 8/8 통과 | SEC-01, SEC-02 잘못된 허용 |
| 1차 수정 버전 | 10/10 통과 | SEC-03, SEC-04 잘못된 허용 |
| 최종 오프라인 검증 버전 | 최상위 테스트 12/12 통과 | 별도 공격 탐침 네 개 모두 거부 |

최종 12개 테스트에는 SEC-01~04 회귀 테스트가 포함됩니다. 추가 네 탐침은 12/12에 포함되지 않으므로 16/16으로 합산하면 안 됩니다. JavaScript 파일 세 개는 node --check를 통과했지만 이는 문법 검사입니다.

뒤의 두 회귀 테스트는 소스 수정 전에 커밋됐지만 테스트 전용 커밋에서 전체 suite를 실행하지 않았습니다. 따라서 전체 10/12 RED 기록은 없습니다. 확인 가능한 것은 이전 검증기에서 잘못된 허용을 독립적으로 재현했고 최종 고정 버전의 주요 테스트와 추가 탐침이 통과했다는 점입니다.

동작 증거가 안정된 뒤 복잡도를 검토합니다. helper, 분기, 설정, 호출 지점을 따라가며 각 요소가 어떤 불변 조건을 보호하는지, 중복인지, 실제 사용되는지 확인합니다. 코드가 짧다고 더 단순한 것은 아닙니다. 경로 보호를 제거하면 닫힌 경쟁 조건이 다시 생길 수 있습니다.


<!-- vxi-evidence-panel: ai-code-review-counterexamples -->

관련 글: [Mutation Testing](/posts/ai-generated-tests-mutation-testing/), [OpenQodex pre-push 리뷰의 fail-open 경계](/posts/openqodex-prepush-review-fail-open/), [Diffle 리뷰 댓글 앵커](/posts/diffle-git-diff-comment-anchoring/)。

## 테스트 통과와 운영 승인 사이에 남은 것

최종 독립 검증은 Node.js v22.16.0, Linux x64의 오프라인 검증기를 대상으로 했습니다. 주요 테스트 12/12 통과, 추가 공격 탐침 네 개 모두 거부, canonical SVG·manifest·8개 언어 바인딩의 정상 사례 통과, JavaScript 파일 세 개의 node --check 통과가 확인됐습니다.

이 증거는 해당 환경과 고정 규칙에서 알려진 네 가지 잘못된 허용이 수정됐다는 제한된 결론을 뒷받침합니다. 블로그 전체가 운영 준비를 마쳤다는 뜻은 아닙니다. 악의적인 동시 파일 시스템 작성자에 대한 네이티브 macOS 동작은 미검증이며 전체 사이트 빌드와 최종 페이지 실행 확인도 오프라인 테스트 범위 밖입니다.

실제 승인선은 위험에 따라 나눠야 합니다. 단위 테스트는 포함된 동작을, 정적 검사는 특정 패턴을, 빌드와 통합 테스트는 모듈 결합을, 플랫폼과 런타임 검사는 배포 경계를 확인합니다. 중요한 경계가 검증되지 않았다면 부분 성공을 전체 운영 준비 완료라고 말하면 안 됩니다.

계약과 반례로 의심할 동작을 찾고 정확한 버전의 테스트와 독립 증거로 다음에 깊이 읽을 부분을 결정합니다. 사람의 판단을 없애는 것이 아니라 “괜찮아 보인다”를 증거와 범위가 있는 검증 가능한 리뷰 결정으로 바꾸는 것입니다.

## Verification context

- Level: first-party
- Verified: 2026-10-25T00:00:00.000Z
- Environment: Node.js v22.16.0 / Linux x64
- Evidence mode: mixed
- Public repository: https://github.com/vxime/ai-code-review-counterexamples
- Evidence commit: 74d9f12331ef4d015bf882a6e09d66e59434368b
- Evidence receipt: https://vxi.me/evidence/vibe-coding-ai-code-review-evidence-gates.json
