Huny's Dev Blog
huny.dev

CodeQL ai 위협 모델: LLM 출력의 검사 출발점을 직접 연결하기

11월 3~4일 소개된 CodeQL 업데이트를 계기로 ai 위협 모델의 옵트인과 사용자 source model을 구분한다. Python 모델 예제, 로컬 구조 검사 7개와 실제 스캔 전 검증 절차를 정리한다.

Hun Jang
Hun Jang Nov 5, 2026

LLM 출력과 정적 분석 사이에 명시적인 source model을 연결하는 과정을 표현한 AI 생성 일러스트.

LLM이 돌려준 문자열을 셸 명령, 파일 경로, HTML에 사용하는 코드는 어디부터 검사해야 할까. 모델 호출 뒤에 래퍼와 응답 변환이 여러 겹 쌓이면, 정적 분석기가 그 값을 신뢰할 수 없는 입력으로 인식하는지부터 확인해야 한다.

GitHub는 11월 3일 CodeQL 2.27.3 업데이트를 소개하면서 LLM 생성 데이터를 위한 옵트인 ai 위협 모델을 안내했다. 중요한 조건이 함께 있다. 내장 source model은 제공하지 않는다. 따라서 설정을 켜는 것과 애플리케이션의 LLM 출력 지점을 분석에 연결하는 작업을 함께 진행해야 한다. GitHub 공식 발표

이 글은 새 옵션의 의미, Python용 교육 예제, 실제 도입 전에 수행할 검증을 정리한다. 발표일과 패키지 릴리스일은 다르다. 2.27.3 CLI 변경 이력은 10월 20일자이고, 11월 4일 소개된 2.27.4의 CLI 변경 이력은 11월 2일자다. ai 모델이 11월 4일에 처음 출시된 것으로 읽으면 안 된다. 2.27.3 변경 이력, 2.27.4 변경 이력

검사할 입력을 이름 붙이는 위협 모델

CodeQL의 위협 모델은 분석에 포함하거나 제외할 수 있는 데이터 흐름 출발점의 묶음이다. 기본 remote는 네트워크 요청·응답을, 새 ai는 LLM 생성 출력을 나타낸다. Python 공식 문서는 sourceModel의 kind 값으로 이 분류를 지정하도록 설명한다. 위협 모델과 데이터 확장 형식은 아직 베타이므로 버전 변화도 확인해야 한다. Python 라이브러리 모델 문서

실무에서는 다음 세 질문을 나눠 보는 편이 좋다.

  • 출발점: 실제 텍스트가 어느 함수의 반환값이나 응답 필드에서 나오는가.
  • 전달 경로: 변환 함수, 컨테이너, 비동기 처리 등을 거친 뒤에도 흐름을 추적할 수 있는가.
  • 도착점: 실행할 쿼리가 위험한 셸 호출이나 출력 위치를 검사하는가.

이는 도입을 위한 점검 관점이다. 출발점 하나를 추가했다고 모든 위험한 사용법을 자동으로 찾는다고 판단해서는 안 된다. 특히 LLM 출력 이후의 데이터 흐름을 검사하는 기능을 프롬프트 인젝션이나 환각을 포괄적으로 판별하는 기능으로 확대 해석할 수는 없다.

Python 래퍼 하나를 source로 지정해 보기

다음은 가상의 외부 패키지 acme_llm에 있는 동기 함수 generate_text()가 문자열을 직접 반환한다는 가정으로 만든 교육용 예시다. 특정 상용 SDK에 적용해 검증한 모델은 아니다.

models/llm.json:

첫 열은 패키지 import에서 탐색을 시작하고, 둘째 열은 generate_text 멤버의 호출 반환값을 선택한다. 셋째 열 ai가 이 값을 어느 위협 모델에 넣을지 지정한다. JSON 확장은 .json 파일에 저장한다. 접근 경로와 sourceModel 문법

실제 래퍼가 응답 객체를 반환한다면 객체 자체와 텍스트 필드는 다르다. 스트리밍 청크, async 함수, 리스트로 감싼 응답도 별도로 살펴야 한다. 패키지 이름만 실제 SDK 이름으로 바꾸기보다, 프로젝트에서 호출하는 형태와 값을 꺼내는 위치를 먼저 작은 예제로 고정하는 것이 좋다.

모델 파일이 분석에 들어가는지 확인하기

파일을 저장한 다음에는 CodeQL이 그 파일을 읽도록 model pack을 구성해야 한다. models/ 디렉터리의 상위인 model pack 루트에 qlpack.yml을 다음처럼 준비할 수 있다.

이름은 예시이며, "*"는 구조를 보여주기 위한 넓은 호환 범위다. 운영에서는 실제로 시험한 라이브러리 버전 범위를 정한다. Model pack은 library: true, extensionTargets, dataExtensions를 사용하며 일반 의존성인 dependencies는 두지 않는다. Model pack 자체도 public preview다. Model pack 작성 안내

CLI의 database analyze에는 두 종류의 옵션이 있다.

  • --threat-model=ai: ai 출발점 묶음을 활성화한다.
  • --model-packs=<name@range>: 분석에 사용할 model pack을 지정한다.

기본 위협 모델은 명시적으로 끄지 않으면 유지된다. 여기서 pack 이름과 버전은 실제로 탐색 가능한 대상으로 연결해야 하며, 호환되는 Python 데이터베이스와 쿼리·라이브러리도 필요하다. 위 두 옵션만 붙인다고 아직 만들지 않은 데이터베이스나 모델이 생기지는 않는다. CLI 옵션 명세

이 글은 CLI와 데이터 확장 구성 원리를 다룬다. GitHub Actions의 기본·고급 설정은 적용 경로가 다르고 공식 안내 사이에도 지원 언어 범위의 표현 차이가 있으므로, 이 예제를 Python Actions 전체 구성의 동작 보장으로 사용하면 안 된다. 사용하는 실행 방식과 버전의 문서를 다시 확인해야 한다. 워크플로 설정 안내

로컬에서 확인한 범위와 남은 검증

위 JSON과 YAML은 로컬 Python에서 파싱했다. 다음 7개 구조 검사를 통과했다.

  1. library가 참인지.
  1. dependencies가 없는지.
  1. 확장 대상이 codeql/python-all인지.
  1. glob이 실제 models/llm.json 파일을 찾는지.
  1. 확장 predicate가 sourceModel인지.
  1. 각 모델 행이 문자열 3개로 구성됐는지.
  1. source 종류가 ai인지.

이 결과는 설정 파일의 구조 검사다. CodeQL CLI는 이 환경에 설치되어 있지 않아 데이터베이스 생성, 쿼리 실행, source 인식, SARIF 결과를 검증하지 않았다. 실제 LLM 호출도 하지 않았다.

다음 단계에서는 같은 작은 코드 예제를 대상으로 ai 활성화 여부와 사용자 model pack 포함 여부를 교차해 네 조합을 비교한다. 이는 앞으로 수행할 검증 방법이다. 모델 로딩 정보와 경고를 함께 보고, 의도한 출발점부터 위험한 사용 지점까지 실제 경로가 보고되는지 확인한다. 다른 기본 모델이 이미 같은 출발점을 잡을 수 있으므로 경고 개수의 차이만으로 성공을 판정하지 않는다.

정상 사용 예제도 함께 둬야 한다. 예를 들어 LLM이 제안한 작업 이름을 미리 정한 허용 목록에 대응시키는 코드와, 문자열을 그대로 실행하는 코드를 나누어 본다. 두 경우의 분석 결과를 설명할 수 있어야 모델을 넓히거나 차단 규칙을 추가할 때 회귀를 발견하기 쉽다.

2.27.4에서는 pack 파일 누락 경고도 살펴본다

11월 4일 공식 안내에는 model pack을 배포하는 팀이 알아둘 변경도 있다. 2.27.4 CLI는 pack을 묶거나 게시할 때 심볼릭 링크를 해석한 결과가 pack 루트 밖을 가리키는 파일을 제외하고 경고한다. 저장소에 파일이 보인다는 사실만으로 배포된 pack에도 포함됐다고 가정하면 안 된다. 2.27.4 공식 발표

따라서 새 모델을 적용할 때는 설정 diff, 포함된 파일, 분석에서 로딩한 모델, 실제 경로 결과를 차례로 확인한다. 테스트를 통과한 작은 래퍼부터 시작하면 누락된 출발점과 잘못 연결한 반환값을 구분하기 쉽다.

CodeQL의 ai 위협 모델은 LLM 출력을 명시적으로 검사 범위에 넣을 수 있는 기반이다. 팀에서 얻어야 할 결과는 옵션을 켰다는 기록보다 어떤 출력이 어떤 위험한 사용 지점까지 추적되는지 재현 가능한 예제다.

기준일: 2026년 11월 5일 KST. 공식 문서와 로컬 설정 구조 검사를 바탕으로 작성했다. 실제 보안 스캔이나 외부 서비스 설정 변경은 수행하지 않았다.

추천 글