Proactive Scan의 기반이 된 벤치마크
요약
Proactive Scan의 성능을 검증하기 위해 새롭게 설계된 벤치마크를 공개합니다. 모델의 단순 암기를 방지하기 위해 6월 말에 새로 작성된 28개의 테스트 케이스를 사용하여 단일 파일 및 교차 파일 결함을 분석합니다.
핵심 포인트
- 모델의 데이터 오염을 방지하기 위해 최신 생성 코드로 벤치마크 구성
- 단일 파일 결함뿐만 아니라 파일 간 컨텍스트를 활용한 교차 파일 결함 탐지 강조
- Claude Haiku 4.5와 Voyage 임베딩을 활용한 분석 파이프라인 적용
- 실제 발생 가능한 ZeroDivisionError 등 다양한 실패 클래스 포함
Proactive Scan의 기반이 된 벤치마크
화요일에 우리는 Proactive Scan을 출시했습니다. 이는 무언가 충돌(crash)하기 전에, 가장 위험한 파일을 순위별로 나열하고 교차 파일 컨텍스트(cross-file context)를 사용하여 각 파일을 읽는 단일 명령입니다.
오늘 우리는 그 기반이 된 벤치마크를 공개합니다. 수치, 방법론, 우리의 첫 번째 버전이 잘못된 경보(cried wolf)를 울렸던 부분, 그리고 여전히 놓치고 있는 버그의 종류까지 모든 것을 공개합니다.
결과
이 벤치마크를 위해 새로 작성된 28개의 테스트 케이스가 있습니다. 결함이 심어진 20개의 파일과 대조군으로서의 깨끗한 8개 파일로 구성되었습니다.
| 카테고리 | 점수 | 심어진 내용 |
|---|---|---|
| 단일 파일 결함 (Single-file defects) | 8/8 | SQL 인젝션 (SQL injection), 리소스 누수 (resource leak), 0으로 나누기 (division by zero), 가변 기본 인자 (mutable default arg), bare except, 누락된 await (missing await), off-by-one, 하드코딩된 비밀값 (hardcoded secret) |
| ... |
프로덕션과 동일한 파이프라인을 실행하는 스테이징 API를 대상으로 2026년 7월 2일에 실행되었습니다. 코드베이스 인덱싱을 위한 Voyage 임베딩 (embeddings), 분석을 위한 Claude Haiku 4.5를 사용했습니다.
이것은 우리가 점수를 매긴 n=28의 코퍼스 (corpus)입니다. 이는 스캔이 우리가 설계한 대로 작동함을 나타냅니다. 이것이 우리가 누군가를 이겼다는 뜻은 아닙니다. 경쟁사 제품을 실행하지 않았으므로, 여기에서 경쟁사 수치는 찾을 수 없을 것입니다.
왜 케이스를 처음부터 새로 작성했는가
이 벤치마크를 구축하는 게으른 방법은 인기 있는 오픈 소스 저장소(repos)에서 알려진 버그를 가져오는 것입니다. 내부 회귀 테스트(regression test)를 통해 정확히 그런 자료가 준비되어 있었습니다. 하지만 우리는 한 가지 이유로 그것을 공개하지 않았습니다. 바로 모델들이 해당 저장소들을 이미 읽었다는 점입니다. 훈련 데이터에서 유명한 React 버그를 인식하는 탐지기는 분석을 하는 것이 아니라 재현 (recall)을 하고 있는 것입니다.
따라서 28개의 케이스 모두 6월 말에 새로 작성되었습니다. 독창적인 코드, 독창적으로 심어진 결함, 실제 세계의 실패 클래스(failure classes)를 모델로 삼았지만 공개된 어떤 것에서도 복사하지 않았습니다. 어떤 모델도 이것들을 본 적이 없습니다.
교차 파일(Cross-File) 케이스는 어떤 모습인가
단일 파일 케이스는 기본 요건 (table stakes)입니다. 제대로 된 린터 (linter)라면 어떤 except라도 플래그를 표시합니다. 12개의 교차 파일 (cross-file) 케이스가 바로 이 스캔이 존재하는 이유입니다. 도구가 프로젝트의 나머지 부분을 알지 못하면 이들은 보이지 않기 때문입니다.
다음은 축약된 두 개의 파일로 구성된 XF-05입니다:
# limits.py
# 윈도우당 요청 수; 0은 비활성화함 (단, 호출자가 이 값으로 나눔)
WINDOW_SLOTS = 0
...
rate.py는 그 자체로는 결함이 없습니다. 모든 줄이 정확합니다. 버그는 관계 속에 존재합니다. 다른 파일의 설정 값이 0이고, 이 파일이 그 값으로 나누기 때문입니다. 단일 파일 도구로 rate.py를 가리키면 도구는 아무런 반응을 보이지 않습니다. 스캔 도구는 두 파일을 모두 인덱싱하고, rate.py를 읽을 때 limits.py를 컨텍스트 (context)로 가져와서 곧 발생할 ZeroDivisionError를 보고합니다.
이것이 12개의 모든 파일 간 (cross-file) 사례에 적용되는 패턴입니다. 버그가 있는 파일은 로컬 (locally) 관점에서는 깨끗하며, 결함은 프로젝트의 다른 곳에 있는 사실이 주어졌을 때만 존재합니다.
점수 산정 방식 (How Scoring Works)
각 사례는 예상되는 시그널 (signal)을 선언합니다: 키워드와 발견 사항이 반드시 언급해야 하는 이슈 클래스 (issue class)입니다 (XF-05의 경우: "division by zero" 또는 문제가 되는 상수 이름). 스캔이 심어진 파일에서 해당 시그널과 일치하는 발견 사항을 보고하면 해당 사례는 탐지된 것으로 간주합니다. 깨끗한 파일은 스캔 결과가 아무것도 반환되지 않을 때 통과됩니다.
관대한 키워드 매칭 (Lenient keyword matching) 방식은 우리가 "문제를 우리가 하는 방식대로 표현했는가"가 아니라 "문제를 보았는가"를 채점한다는 것을 의미합니다. 우리는 사례와 채점기를 모두 작성했으며, 이는 실제적인 이해 상충 (conflict) 요소입니다. 이에 대한 완화책은 투명성입니다: 코퍼스 (corpus), 예상 시그널, 그리고 실행기 (runner)가 공개되어 있으므로, 여러분이 직접 다시 실행해 보고 우리의 점수 산정에 동의하지 않을 수 있습니다.
오탐과의 전쟁 (The False Positive War)
모두가 묻는 헤드라인 수치는 탐지율 (detection)입니다. 하지만 도구를 계속 설치해 둘지 여부를 결정하는 수치는 오탐 (false alarms)입니다. 정상적인 코드를 플래그 (flag)하는 스캔은 사용자가 이를 무시하도록 훈련시키며, 그렇게 되면 스캔이 없는 것보다 더 나빠집니다.
우리의 git 히스토리는 이 이야기를 정직하게 보여줍니다. 5일 동안 세 번의 프롬프트 버전을 출시했습니다.
scan-v3. 벤치마크가 적용된 첫 번째 버전입니다. 모든 것을 찾아냈고, 깨끗한 파일에서도 스타일 선호도를 "찾아냈습니다". 깨끗한 에러 핸들링 (error-handling) 파일이 예외를 다시 던지지(re-raising) 않는다는 이유로 플래그되었습니다. Wolf는 울었습니다.
scan-v4. 완전히 새로운 오탐 (false-positive) 클래스를 분리해냈습니다. 외부 또는 알 수 없는 입력을 방어적으로 처리하는 코드가, 단지 그 입력이 무엇인지 알 수 없다는 이유로 플래그(flagged)되는 현상이었습니다. 해결책은 프롬프트 규율 (prompt discipline)이었습니다. 탐지 결과 (finding)는 반드시 작성된 코드로부터 도달 가능한 구체적인 입력값과 함께, 구체적인 실패 사례를 명시해야 합니다. "이것은 위험할 수 있습니다"는 탐지 결과가 아닙니다.
scan-v5. 하나의 제어 파일 (CLEAN-06, 비동기 패턴)에서 여전히 간헐적인 모호한 플래그 (hedge-flag)가 발생했습니다. 지침을 더욱 강화했습니다: 코드가 올바르다면, 아무 말도 하지 마십시오. '탐지 결과 0건'은 유효하고 좋은 답변입니다.
마지막 문장을 모델이 믿게 만드는 데 가장 오랜 시간이 걸렸습니다. 언어 모델 (Language models)은 도움이 되고 싶어 하며, "아무것도 찾지 못했습니다"라는 말은 도움이 되지 않는다고 느낍니다. 8개의 깨끗한 파일에 대해 모델이 침묵을 유지하게 만드는 것이, 버그가 있는 20개의 파일에 대해 말을 하게 만드는 것보다 더 어려웠습니다.
놓치는 부분 (What It Misses)
이 벤치마크는 우리가 심어놓은 것들을 테스트합니다. 우리가 알고 있는 여러 버그 클래스에 대해서는 아무것도 말해주지 않으며, 이 경계에 대해 솔직하게 밝히는 것이 이번 발표의 핵심입니다.
런타임 데이터 (runtime data)가 필요한 버그. 부하 상황에서만 나타나는 레이스 컨디션 (race), 트래픽 패턴이 필요한 누수 (leak) 등이 이에 해당합니다. 정적 분석 (Static reading)으로는 이를 잡아낼 수 없습니다. 이는 오류가 발생한 후, DebugAI의 디버거 측면이 담당할 역할입니다.
명세 버그 (Spec bugs). 코드가 명시된 대로 정확히 동작하지만, 그 명시된 내용이 비즈니스 요구사항과 일치하지 않는 경우입니다. 사용자의 의도 없이는 어떤 도구도 이를 잡아낼 수 없습니다.
규모 (Scale). 피스처 (Fixture) 파일들은 작습니다. 2,000줄의 코드와 40개의 임포트 (import)가 포함된 프로덕션 파일은 더 어려운 검색 (retrieval) 문제입니다. 우리는 이를 별도로 테스트하며, 이 수치에는 포함되지 않았습니다.
언어 (Languages). 코퍼스 (Corpus)는 DebugAI가 지원하는 Python 및 JavaScript/TypeScript로 구성되어 있습니다. 여기에는 그 이상의 범용성을 가진 내용은 없습니다.
28개의 케이스는 벤치마크일 뿐, 증명이 아닙니다. 28개 케이스에서 만점을 받았다는 것은 메커니즘이 작동한다는 의미이지, 메커니즘이 완벽하다는 의미가 아닙니다.
재현 방법 (Reproduce It)
코퍼스 (기대 신호가 포함된 28개 케이스 전체), 러너 (runner), 그리고 가공되지 않은 결과 JSON은 공개되어 있습니다: github.com/1shizaan/debugai-scan-benchmark. 러너에 본인의 API 키를 지정하여 다시 실행해 보세요:
python3 run.py --api-key dbg_your_key_here
만약 저희의 점수가 실제보다 높게 측정된 사례를 발견하신다면, 이슈(issue)를 생성해 주세요. 저희는 공개적으로 그 결과를 받아들일 것입니다.
FAQ
Q: 왜 Copilot이나 Semgrep과 비교하지 않나요?
A: 공정한 일대일 비교는 그 자체로 하나의 프로젝트이기 때문입니다. 동일한 코퍼스 (corpus), 각 도구를 실제 사용자가 사용하는 방식 그대로 실행, 문서화된 버전, 블라인드 스코어링 (blind scoring), 공개된 손실 (losses) 등이 필요합니다. 출시 주간에 이를 서둘러 진행하는 것은 다른 벤더사들로부터 신뢰할 수 없는 수치들이 나오는 것과 정확히 같은 결과를 초래할 것입니다. 만약 비교를 진행하게 된다면, 별도의 포스트로 다루겠습니다.
Q: 직접 테스트 케이스를 작성하면 높은 점수를 보장받는 것 아닌가요?
A: 그것은 실제적인 위험 요소이며, 솔직한 답변은 부분적으로 그렇다는 것입니다. 저희는 스캔이 잡아내야 하는 케이스들을 설계했습니다. 이를 검증하는 것은 대조군 (controls)입니다. 모든 것을 플래그 (flag) 하도록 조정된 스캐너는 탐지에는 성공하겠지만, 8개의 깨끗한 파일 (clean files) 모두에서 실패할 것입니다. 오탐 (false positives) 제로를 달성하는 것은 증거를 공개하지 않고서는 조작할 수 없는 점수의 절반입니다.
Q: 어떤 모델이 스캔을 실행하나요?
A: 프로젝트의 Voyage 임베딩 인덱스 (Voyage-embedded index) 위에서 저희의 scan-v5 프롬프트 (prompt)를 사용하는 Claude Haiku 4.5입니다. 무료 티어에서도 동일한 파이프라인 (pipeline)이 적용됩니다.
원문은 debugai.io에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기