문법이 깨진 class Broken { void broken( {가 보안 검사를 통과했습니다. Shakedown을 Semgrep 1.180.0과 실제로 연결했을 때, 검사 실패를 뜻하는 SCAN_FAILED 대신 다음 단계로 진행할 수 있는 ALLOW가 반환됐습니다. 이 동작 때문에 기존 회귀 테스트 2개가 실패했습니다.
저는 테스트의 기대값을 낮추지 않기로 했습니다. 대신 대상 코드를 실행하지 않는 Java 구문 검사를 앞에 추가했고, 실패하던 2개 테스트를 기대값 변경 없이 통과시켰습니다. 이 글은 보안 검사 하나에 맡겨 두었던 역할을 왜 나눴는지 설명합니다.
Shakedown은 저장소를 분석하고 이미지를 만들어 배포하는 해커톤 서비스입니다. 초기 보안 게이트는 팀원이 구현한 기반입니다. 저는 엔진 연동과 오류 상태 보완을 맡으며 이 반례에 대한 통과 조건을 유지하고, 보완할 검사 범위를 정했습니다. 구현·조사·검증 실행에는 AI를 활용했습니다. 이 글은 Semgrep 1.180.0 경로에서 발견한 문제와 Java 사전 검사 수정을 다룹니다.
ALLOW와 검사 실패는 다른 결과여야 했습니다
보안 게이트의 결과는 이후 빌드·배포 판단에 전달됩니다. 입력을 충분히 해석하지 못한 상황을 문제없음으로 처리하면, 다음 계층은 실패를 알 수 없습니다. 이번 입력은 컴파일이나 실행 전에 이미 괄호 구조가 깨져 있으므로, 검사 실패로 표시하는 것이 기존 테스트의 기대였습니다.
실제 연동에서는 회귀 테스트 두 개가 같은 실패를 드러냈습니다. 최적화를 끄는 시도도 문제를 해결하지 못했습니다. 테스트를 통과시키기 위해 기대값을 ALLOW로 낮추면, 원래 확인하려던 경계를 삭제하는 셈입니다. 저는 SCAN_FAILED 기대를 유지한 채 보완 수단을 검토했습니다.
컴파일러를 실행하지 않고 구문 경계를 보완했습니다
Java 코드의 정상 여부를 보려면 javac로 컴파일하거나 Gradle로 빌드하는 방법도 떠올릴 수 있습니다. 컴파일에는 클래스패스 준비가 필요하고, 프로젝트 빌드까지 실행하면 의존성 다운로드와 빌드 스크립트 실행도 검사 범위에 들어옵니다. 저장소를 받아 검사하는 단계에서 필요한 범위보다 실행 책임이 커집니다.
| 선택지 | 얻는 것 | 이번 경계에서의 판단 |
|---|---|---|
| 기대값을 ALLOW로 변경 | 테스트는 통과 | 검사 실패를 정상으로 바꾸므로 제외 |
| javac·Gradle 실행 | 컴파일·빌드 결과 | 의존성·스크립트 실행까지 섞여 제외 |
| 자체 Java 문법 엔진 | 세부 동작 통제 | 별도 언어 구현 책임이 커짐 |
| Tree-sitter의 기존 grammar | 실행 없이 구문 구조 확인 | Semgrep 앞의 보완 검사로 채택 |
Tree-sitter는 입력을 구문 트리로 파싱하는 도구입니다. 제가 이 단계에서 필요로 한 것은 프로젝트의 빌드 성공 여부가 아니라, 구조가 깨진 입력을 다음 검사에 정상 입력처럼 넘기지 않는 것이었습니다. 기존 Java grammar로 구문 오류를 먼저 확인하면 클래스패스 없이 이 질문에 답할 수 있습니다.
parser가 없거나 crash·timeout이 발생한 경우도 SCAN_FAILED로 처리했습니다. 도구가 판단하지 못했을 때에도 정상 상태를 반환한다면, 도구 하나를 추가하고 같은 문제를 반복하는 셈이기 때문입니다.
원래 실패한 테스트를 그대로 통과시켰습니다
실제 scanner 연동 보고서에서 확인한 범위는 다음과 같습니다.
| 질문 | 확인한 결과 |
|---|---|
| 원래 실패하던 입력을 거부하는가 | 기존 테스트 2개를 기대값 변경 없이 통과 |
| 불완전 구문을 찾는가 | 괄호·대입 오류 검사 |
| 현대 Java 구문을 무조건 거부하지 않는가 | record·sealed·switch 등 검사 |
| 검사하면서 대상 코드가 실행되는가 | 클래스패스 없이 검사, static initializer 미실행 확인 |
| 후속 grammar·규칙 고정 뒤의 회귀 | 게이트 427개 통과·Windows 전용 1개 skip, 엔진 207개 통과 |
두 실패를 막는 것만큼 정상 구문을 잘못 막지 않는 것도 중요했습니다. 그래서 record·sealed·switch를 함께 대조했고, static initializer가 실행되지 않는지도 확인했습니다. 427개와 207개는 2026년 10월의 후속 고정 뒤 해당 회귀 실행의 범위입니다. 수정은 PR #11의 연동·보완 기여에 포함됐습니다.
문법 정상은 보안 안전의 증거가 아닙니다
이 보완으로 말할 수 있는 것은 구문 검사 실패를 성공처럼 넘기지 않도록 했다는 것입니다. 구문이 정상이어도 컴파일·타입 검사에서 실패할 수 있고, 정상 실행되는 코드에 취약점이 있을 수도 있습니다. Tree-sitter 검사를 추가했다고 전체 Java 보안 검증을 완성한 것은 아닙니다.
같은 이유로 C/C++·C#·Ruby·PHP처럼 구문 검사만 제공하는 범위는 REVIEW로 남겼습니다. 문법을 읽을 수 있다는 이유로 보안 검사까지 완료됐다고 알리지 않기 위해서입니다.
이번 수정에서는 테스트가 실패한 사실보다 무엇을 정상으로 인정할지를 지키는 데 집중했습니다. 깨진 Java 입력은 검사 실패로 돌려주고, 파싱 가능한 입력은 다음 검사로 넘기며, 지원하지 못하는 분석은 검토 대상으로 남깁니다. 이 구분이 있어야 이후 빌드·배포 단계도 검사 결과를 근거로 결정할 수 있습니다.