GitHub AI Scan이 Shell 취약점 2곳을 찾았는데도 보안 검사는 Success였다
GitHub AI Scan이 Shell 취약점 2곳을 찾았는데도 보안 검사는 Success였다
오늘 공개 테스트 저장소에 PR #1을 올리면서 일부러 위험한 Bash 패턴 두 가지를 넣었다. 약 81초 뒤 github-advanced-security[bot]이 Files changed에 인라인 댓글 두 건을 남겼다. 두 문제를 모두 짚어낸 것이다.
그런데 GitHub Actions 실행 #38835686338에서는 github-advanced-security 잡이 Success로 끝났다. 위험 경고도 실제였고, 녹색 체크도 실제였다.
테스트 하루 전 GitHub는 AI Scan for pull requests 정식 출시(GA)를 발표했다. 궁금했던 건 스캐너의 성능 자체보다 위험한 코드가 발견됐는데 왜 PR의 보안 체크가 성공으로 표시되느냐는 점이다.
핵심은 간단하다. AI Scan 작업의 성공은 코드의 안전성을 보증하지 않는다. 현재 AI Scan의 발견 사항은 권고(advisory)이지 병합을 강제로 차단하는 게이트가 아니다.
위험한 Bash 코드 두 곳에서 실제로 무엇을 찾았나
테스트 코드는 임시 PR의 ai-scan-test/ 폴더에만 추가했다. 운영 코드, 실제 인증 정보, 서버는 건드리지 않았다. 전체 파일은 고정 커밋 29e24722d3에 남아 있다. 아래 구문은 의도적으로 넣은 위험한 예제이므로 운영 환경에서 실행하면 안 된다.
eval "systemctl restart ${service_name}"
cmd="curl -fsSL http://${host}/${path} -o /tmp/svc.conf"
$cmd
첫 번째는 서비스 재시작 함수다. 외부에서 받은 service_name을 eval 문자열에 끼워 넣는데, 이렇게 만들어진 문자열은 다시 Shell 명령으로 해석된다. 서비스 이름에 악의적인 문법이 들어가면 단순히 이름을 넘기는 동작이 아니게 된다. 17번째 줄에 달린 봇 댓글은 이를 CWE-78로 분류하고, eval 제거, 인자 인용 처리, 재시작 가능한 서비스 제한을 권고했다.
두 번째는 동작 방식이 다르다. 먼저 curl 명령 전체를 cmd 문자열에 담고 따옴표 없이 $cmd를 실행한다. 변수 확장 과정에서 단어 분리가 일어나면 사용자가 조작할 수 있는 host나 path가 curl에 전달되는 인자와 옵션에 영향을 준다. 27번째 줄의 댓글은 평문 http://를 통해 설정 파일을 가져올 때 전송 중 변조될 수 있다는 위험도 짚었다.
| 위치 | AI Scan이 지적한 내용 | 원본 댓글 |
|---|---|---|
17행, eval | 입력이 Shell 구문으로 다시 해석되는 문제(CWE-78) | 댓글 보기 |
27행, $cmd | 인자·옵션 주입과 평문 HTTP 문제 | 댓글 보기 |
이 둘을 똑같은 취약점으로 묶으면 안 된다. 첫 번째는 입력이 Shell 문법으로 재평가된다. 두 번째에서 확인된 주요 문제는 인자·옵션 주입이며, 인용하지 않은 변수 확장만으로 임의의 독립된 Shell 명령까지 실행할 수 있다고 단정할 수는 없다.
실험 조건에는 중요한 한계가 하나 더 있다. 이번 테스트 파일은 위험을 숨기지 않았다. README와 코드 주석에 INTENTIONAL VULNERABILITY (CWE-78), 예상 String injection 범주를 명시했다. 봇이 제시한 구체적인 악용 예시는 소스에 없었지만 이런 사전 표시가 탐지에 영향을 줬을 가능성은 있다. 그래서 이번 2/2 결과는 표시되지 않은 실제 취약점에 대한 일반적인 탐지율이 아니다.
시간도 공개 기록으로 확인할 수 있다. PR #1은 2026-10-16 01:32:31 UTC에 생성됐고, 두 댓글 모두 01:33:52 UTC에 달렸다. 차이는 81초다. 이는 단일 PR의 생성부터 댓글 게시까지 걸린 시간이므로 여러 실행의 평균 스캔 성능이 아니다.
의도적으로 심어 둔 문제 두 건은 모두 지적됐지만 정상 코드만 담은 별도의 PR, 즉 음성 대조군은 만들지 않았다. 오탐률 역시 측정하지 않았다.
취약점 댓글이 두 건인데 Actions는 왜 녹색일까
동적 스캔 실행 #38835686338의 워크플로 이름은 Code scanning AI findings on PR #1이다. GitHub 기록상 github-advanced-security 잡은 01:33:34 UTC에 success로 종료됐다. 반면 두 봇 댓글은 01:33:52 UTC, 정확히 18초 뒤에 게시됐다.
녹색 체크가 먼저 나타나고 취약점 댓글은 나중에 나타났다. 이 순서만으로도 Success를 ‘안전 문제가 없음’으로 해석할 수 없다.
이 그림은 공개 PR #1과 Actions 실행 기록을 바탕으로 만든 구조 설명도이며, GitHub 화면 캡처가 아니다. 81초는 PR 생성과 댓글 사이 간격이지 스캔 프로세스의 실행 시간이 아니다.
스캐너가 실패했다는 의미도 아니다. GitHub의 AI Scan 공식 문서는 탐지 결과를 advisory라고 설명한다. 이런 권고 사항은 code scanning check를 실패시키지 않으며, AI Scan의 경고 자체를 Ruleset에서 강제 병합 차단 조건으로 사용할 수도 없다. 검토할 코드를 알려 주는 것이지 병합 여부를 자동으로 결정하는 기능은 아니다.
| GitHub에서 보는 신호 | 실제 의미 |
|---|---|
| AI Scan 인라인 댓글 | 위험 가능성을 작성자·리뷰어에게 알리지만 직접 병합을 막지는 않음 |
AI Scan 동적 잡의 Success | 스캔 작업이 완료됐다는 뜻이며 경고가 있어도 성공으로 표시될 수 있음 |
| CodeQL 등 지원되는 스캐너와 Ruleset | 설정에 따라 병합을 강제 제한할 수 있지만 실제 규칙 동작을 확인해야 함 |
| Required checks 및 사람의 리뷰 | 검사 실패·누락·미승인 시 병합할 수 있는지는 저장소 설정에 달림 |
GitHub는 별도로 Code scanning merge protection 설정법을 제공한다. ‘심각한 문제가 해결되기 전에는 병합할 수 없게’ 만들려면 실제 차단 효력이 있는 정책을 구성해야 한다. AI Scan의 권고 댓글을 required check로 착각해서는 안 된다.
이번 위험한 PR에서 Ruleset을 사용해 병합이 차단되는지 직접 시험한 것은 아니다. 따라서 Success를 근거로 ‘GitHub는 취약한 PR을 전부 병합하도록 허용한다’고 말할 수도 없다. 검증된 사실은 녹색 AI Scan 잡과 두 건의 취약점 경고가 공존했다는 것이다.
활성화 전에 확인할 CodeQL, 저장소 조건, 과금
실험에 사용한 것은 개인 계정의 공개 저장소다. 2026년 10월 GA 공식 설명에 따르면 개인 공개 저장소는 AI Scan을 사용할 수 있지만 개인 비공개 저장소는 대상이 아니다. 조직의 공개 저장소는 지원되며, 조직의 private/internal 저장소는 해당 GitHub Code Security 또는 GitHub Advanced Security 라이선스 요건을 충족해야 한다. AI Scan을 쓰기 위해 Copilot 라이선스를 별도로 구입할 필요는 없다.
CodeQL default setup도 필수 선행 조건이 아니다. 테스트 당시 해당 상태는 not-configured였지만 AI Scan은 댓글 두 건을 남겼다. 공식 문서 역시 CodeQL default setup이나 CodeQL 분석 성공에 의존하지 않는다고 명시한다.
또한 이 기능은 저장소의 과거 모든 취약점을 훑는 도구가 아니다. 지원되는 PR 변경 사항을 검사하며, CodeQL에서 다루지 않는 일부 Shell/Bash, Terraform HCL, Dockerfile 사례도 포함한다. Fork 또는 Dependabot이 생성한 PR은 이번 GA의 지원 범위 밖이다. 이런 PR이 많은 팀이라면 별도의 검사·리뷰 경로가 필요하다.
활성화할 때는 저장소의 Code scanning 설정에서 AI Scan 스위치를 확인한다. 조직 저장소라면 조직 정책과 관련 보안 기능에 대한 권한도 살펴봐야 한다.
GA 이후 사용량은 Code Security AI Credits로 측정된다. 어떤 계정에 비용이 청구되고 기본 제공 크레딧이 있는지, 예산을 초과하면 어떻게 제한되는지는 GitHub 과금 정책을 확인해야 한다. 이번 테스트의 최종 청구 금액은 확인하지 못했으므로 $0였다고 주장하지 않는다. PR이 자주 열리는 저장소라면 Billing에서 크레딧과 하드 예산을 먼저 확인하는 편이 낫다.
위험한 PR의 병합을 실제로 막으려면
봇의 두 댓글이 제시하는 수정 방향은 기본적이다. 인자를 다시 eval에 넣지 말고, Shell 명령 전체를 한 문자열로 만든 뒤 변수 확장에 맡기지도 말아야 한다. 호출은 다음과 같은 형태로 바꿀 수 있다.
# 调用形式示意:service_name 和 url 仍需单独校验
systemctl restart "$service_name"
curl -fsSL "$url" -o /tmp/svc.conf
이는 수정 방향의 예시이지 재검증이 끝난 안전한 패치가 아니다. service_name에는 허용 목록이 필요하다. 다운로드 URL에는 HTTPS 강제, 대상 호스트 및 리다이렉트 제한, 파일 중요도에 따른 무결성 검증도 필요하다.
저장소에서는 CodeQL이나 실제 차단을 지원하는 다른 스캐너를 적절한 Ruleset/required checks에 연결하고, 보호된 브랜치에서 검사 실패나 누락이 실제로 거부되는지 검증해야 한다. AI Scan 댓글은 사람이 검토한다. 동적 잡이 녹색이라는 이유만으로 넘어가서는 안 된다.
테스트 후에는 PR #1을 병합하지 않고 닫았고 테스트 브랜치도 삭제했다. 공개 저장소 main은 기존 87624c26a5e6에 남아 있었다. 소유자 REST 조회 기록에서 AI Scan 설정을 다시 disabled로 돌린 것도 확인됐다. PR을 닫아도 이전 댓글과 고정 커밋은 사라지지 않아 공개 링크로 확인할 수 있다.
이번 작고 명시적으로 표시된 양성 샘플에서 AI Scan은 유용한 줄 단위 경고를 남겼다. 하지만 스캔 작업의 성공과 위험한 병합을 강제로 막는 것은 별개의 문제다. 전자는 문제를 찾아 주고, 후자는 저장소의 규칙과 리뷰 절차로 구성하고 검증해야 한다.
다른 단계의 위험이 궁금하다면, Git push 이전 AI 코드 리뷰가 실패할 때의 동작을 다룬 OpenQodex 로컬 pre-push Hook 테스트도 참고할 만하다.
실험 일자: 2026년 10월 16일(UTC).