대형 모델과 CodeQL·Coverity 연동한 보안 검사 구현안
LLM 상호작용 계층이 요구 분석과 취약점 판정, 오탐 필터링, 수정 코드 및 탐지 규칙 생성을 담당하는 CodeQL·Coverity 연동 시스템 구조를 제시했다.
중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.
一. 전체 시스템 계층 아키텍처(결합 해제·확장 가능 설계)
계층 설명
- LLM 상호작용 계층: 요구사항 해석, 취약점 판단, 오탐 필터링, 수정 코드 생성, 사용자 정의 탐지 규칙 생성을 담당하며, 정제된 구조화 취약점 데이터만 수신하고 도구/소스코드를 직접 조작하지 않는다.
- 도구 스케줄링 핵심 계층: CodeQL, Coverity의 전체 수명주기 명령을 통합 래핑하고, 컴파일, 데이터베이스 구축, 스캔, SARIF 내보내기, 보고서 정규화, 컨텍스트 추출을 통합 처리하며, 상위 LLM과 하위 도구 CLI/API를 격리한다.
- 도구 실행 계층: 로컬 바이너리 CLI(codeql / cov-build / cov-analyze), 기업용 Coverity Connect 원격 REST API.
- 저장·캐시 계층: 코드 저장소 스냅샷, CodeQL 데이터베이스, Coverity 컴파일 중간 파일, 이력 SARIF 보고서, LLM 판단 결과를 캐시하여 증분 스캔 속도를 높인다.
- CI/작업 스케줄링 계층: Git PR, 메시지 큐와 연동하고, 스캔 작업을 비동기로 하달하며, 동시성을 제어하고 리소스를 제한한다.
완전한 데이터 흐름 폐쇄 루프
코드 저장소 변경/사용자 업로드 코드 ↓ 작업 디스패처 → 도구 스케줄러(Python 스크립트) ├─ 언어, 프로젝트 유형을 판단하여 스캔 엔진 자동 선택 ├─ 증분 전처리: diff 변경 파일만 추출하고 변경되지 않은 코드는 건너뜀 ├─ 병렬 실행: CodeQL 보안 스캔 + Coverity 정적 결함 스캔 ↓ 도구 출력 SARIF2.1 표준화 보고서 ↓ 스케줄러가 두 SARIF를 정규화 파싱하고, 소스 컨텍스트·행 번호·함수 조각을 읽음 ↓ 구조화 취약점 데이터(정제, 중복 제거, 빈 경고 필터링) ↓ LLM에 전달하여 심층 판단: 1) 실제 취약점 / 도구 오탐 구분 2) 위험 등급화(고위험/중위험/저위험/권고) 3) 바로 대체 가능한 수정 코드 블록 생성 4) 신종 취약점 패턴을 귀납하여 CodeQL QL 사용자 정의 규칙 / Coverity 사용자 정의 Checker 생성 ↓ 스케줄러가 LLM이 생성한 규칙 구문을 자동 검증하고 재스캔하여 검출 효과 검증 ↓ 최종 감사 보고서, 수정 패치, 사용자 정의 탐지 규칙을 출력하여 PR/플랫폼으로 푸시
도구 지능형 라우팅 선정 전략
프로젝트 유형 | 우선 도구 조합 | 핵심 역량 설명 C/C++/임베디드/항공전자 GJB 프로젝트 | Coverity 주 스캔 + CodeQL 보조 | Coverity 메모리 범위 초과, 널 포인터, 리소스 누수, 정수 오버플로, 컴플라이언스 규칙; CodeQL 데이터 흐름 주입, 악성 로직 Java/Go/Python/Web 백엔드 | CodeQL 주 스캔 + Coverity 보조 | CodeQL 오염 분석, SQL 인젝션, XSS, 명령 주입; Coverity 유형, 리소스, 동시성 결함 JS/TS 프런트엔드/Node | CodeQL만 | Coverity는 JS 지원이 약함
二. 사전 환경 의존성과 전역 구성
1. 환경 사전 설치 요건
- CodeQL CLI가 시스템 환경 변수 codeql에 이미 추가되어 있어야 함;
- Coverity Analysis 툴체인: cov-build / cov-analyze / cov-export가 전역에서 사용 가능해야 함;
- 프로젝트 컴파일 의존성: JDK, GCC, CMake, Maven, Gradle, npm 등 해당 프로젝트의 컴파일 도구;
- Python3.9+, 의존 라이브러리 설치 명령:
pip install pydantic python-dotenv requests tqdm sarif-parser
2. 환경 구성 파일 .env
LLM 인터페이스 구성(OpenAI 호환 인터페이스, 사설 대형 모델 호환) LLM_API_BASE=http://127.0.0.1:8000/v1 LLM_API_KEY=sk-xxx LLM_MODEL_NAME=Qwen-72B-Chat # Coverity 기업 플랫폼 구성(로컬 CLI 모드에서는 비워둘 수 있음) COVERITY_CONNECT_URL=http://coverity-connect:8080 COVERITY_USER=admin COVERITY_TOKEN=xxx # 캐시 및 출력 디렉터리 WORKSPACE_ROOT=/data/code_scan_workspace SARIF_OUTPUT_DIR=/data/code_scan_workspace/sarif CODEQL_DB_CACHE=/data/code_scan_workspace/codeql_db COVERITY_INT_CACHE=/data/code_scan_workspace/coverity_int LLM_RESULT_CACHE=/data/code_scan_workspace/llm_audit MAX_SCAN_CONCURRENT=2
3. 통합 데이터 모델 models.py
from pydantic import BaseModel, Field from typing import List , Optional , Dict , Any class VulnerabilityItem ( BaseModel ): tool_source: str # codeql / coverity rule_id: str cwe_id: Optional [ str ] severity: str file_path: str start_line: int end_line: int source_code_snippet: str message: str data_flow_trace: Optional [ str ] = None # 오염원 추적 체인 class LLMVulnAuditResult ( BaseModel ): vuln_index: int is_true_vulnerability: bool risk_level: str # high / medium / low / info misreport_reason: Optional [ str ] = None fix_code: Optional [ str ] = None fix_explanation: str class ScanTaskConfig ( BaseModel ): repo_path: str project_lang: str # java / cpp / python / javascript build_command: Optional [ str ] = None enable_codeql: bool = True enable_coverity: bool = True incremental_scan: bool = False diff_files: Optional [ List [ str ]] = None
三、전체 도구 스케줄링 핵심 스크립트 static_scan_orchestrator.py
기능 구성:
- 전역 설정 로드, 디렉터리 자동 생성
- CodeQL 전체 파이프라인 래핑(데이터베이스 구축-스캔-SARIF 내보내기)
- Coverity 전체 파이프라인 래핑(cov-build-분석-SARIF 내보내기)
- SARIF 통합 파싱, 취약점 정규화, 중복 제거
- LLM 배치 취약점 판단 인터페이스 래핑
- LLM의 사용자 정의 탐지 규칙 생성 + 구문 검증 및 재스캔
- 통합 진입점 작업 실행 함수
- 명령줄 시작 진입점
```python import os import json import subprocess import shutil from dotenv import load_dotenv import requests from tqdm import tqdm from sarif_parser import SarifParser from typing import List, Dict, Optional, Any from models import ScanTaskConfig, VulnerabilityItem, LLMVulnAuditResult
환경 변수 로드 load_dotenv(".env")
전역 상수 WORKSPACE_ROOT = os.getenv("WORKSPACE_ROOT") SARIF_OUTPUT_DIR = os.getenv("SARIF_OUTPUT_DIR") CODEQL_DB_CACHE = os.getenv("CODEQL_DB_CACHE") COVERITY_INT_CACHE = os.getenv("COVERITY_INT_CACHE") LLM_RESULT_CACHE = os.getenv("LLM_RESULT_CACHE") LLM_API_BASE = os.getenv("LLM_API_BASE") LLM_API_KEY = os.getenv("LLM_API_KEY") LLM_MODEL_NAME = os.getenv("LLM_MODEL_NAME")
전체 캐시 디렉터리 초기화 def init_workspace_dir(): dir_list = [ WORKSPACE_ROOT, SARIF_OUTPUT_DIR, CODEQL_DB_CACHE, COVERITY_INT_CACHE, LLM_RESULT_CACHE ] for d in dir_list: os.makedirs(d, exist_ok=True)
도구 공통 실행 래퍼, 표준 출력/오류 캡처 def run_shell_cmd(cmd: List[str], cwd: str = None) -> Dict[str, Any]: try: proc = subprocess.run( cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=3600 ) return { "success": proc.returncode == 0, "stdout": proc.stdout, "stderr": proc.stderr, "return_code": proc.returncode } except subprocess.TimeoutExpired: return { "success": False, "stdout": "", "stderr": "명령 실행 시간 초과", "return_code": -1 }
===================== CodeQL 스케줄링 모듈 ===================== class CodeQLRunner: def __init__(self, task_cfg: ScanTaskConfig): self.task = task_cfg self.src_root = task_cfg.repo_path self.lang = task_cfg.project_lang self.db_name = f"{os.path.basename(self.src_root)}_codeql_db" self.db_path = os.path.join(CODEQL_DB_CACHE, self.db_name) self.sarif_out_path = os.path.join(SARIF_OUTPUT_DIR, f"{self.db_name}_codeql.sarif")
def create_codeql_database(self): """CodeQL 데이터베이스 구축, Java/C++은 컴파일 명령 자동 포함""" cmd = [ "codeql", "database", "create", self.db_path, "--source-root", self.src_root, "--language", self.lang ] # 컴파일 언어의 경우 빌드 명령 주입 if self.task.build_command and self.lang in ["cpp", "java", "csharp"]: cmd.extend(["--command", self.task.build_command]) print(f"[CodeQL] 데이터베이스 구축 시작: {self.db_path}") ret = run_shell_cmd(cmd) if not ret["success"]: raise Exception(f"CodeQL 데이터베이스 구축 실패: {ret['stderr']}") return True
def run_scan_export_sarif(self): """스캔 실행 및 표준 SARIF 보고서 출력""" # 언어별 내장 보안 스캔 규칙 세트 매칭 suite_map = { "java": "java-security-and-quality.qls", "cpp": "cpp-security-and-quality.qls", "python": "python-security-and-quality ```
.qls" , "javascript" : "javascript-security-and-quality.qls" } query_suite = suite_map.get(self.lang, "code-scanning.qls" ) scan_cmd = [ "codeql" , "database" , "analyze" , self.db_path, query_suite, "--format" , "sarif-latest" , "--output" , self.sarif_out_path ] print ( f"[CodeQL] 보안 스캔 실행, 규칙 집합: {query_suite} " ) ret = run_shell_cmd(scan_cmd) if not ret[ "success" ]: raise Exception( f"CodeQL 스캔 실패: {ret[ 'stderr' ]} " ) return self.sarif_out_path def full_scan_pipeline ( self ) -> str : """CodeQL 전체 파이프라인 진입점, sarif 파일 경로 반환""" self.create_codeql_database() sarif_path = self.run_scan_export_sarif() return sarif_path # ===================== Coverity 스케줄링 모듈 ===================== class CoverityRunner : def __init__ ( self, task_cfg: ScanTaskConfig ): self.task = task_cfg self.src_root = task_cfg.repo_path self.build_cmd = task_cfg.build_command self.int_dir_name = f" {os.path.basename(self.src_root)} _cov_int" self.int_dir = os.path.join(COVERITY_INT_CACHE, self.int_dir_name) self.sarif_out_path = os.path.join(SARIF_OUTPUT_DIR, f" {self.int_dir_name} _coverity.sarif" ) def cov_build_capture ( self ): """cov-build로 컴파일 중간 산출물 캡처""" if not self.build_cmd: raise Exception( "Coverity 스캔은 반드시 프로젝트 컴파일 명령 build_command를 제공해야 합니다" ) cmd = [ "cov-build" , "--dir" , self.int_dir, "bash" , "-c" , self.build_cmd ] print ( f"[Coverity] 컴파일 중간 파일 캡처: {self.int_dir} " ) ret = run_shell_cmd(cmd, cwd=self.src_root) if not ret[ "success" ]: raise Exception( f"cov-build 캡처 실패: {ret[ 'stderr' ]} " ) def cov_analyze_defect ( self ): """함수 간 전역 결함 분석 수행""" analyze_cmd = [ "cov-analyze" , "--dir" , self.int_dir, "--all" , "--security" , "--concurrency" , "--misra" ] print ( f"[Coverity] 정적 결함 분석 실행" ) ret = run_shell_cmd(analyze_cmd) if not ret[ "success" ]: raise Exception( f"cov-analyze 분석 실패: {ret[ 'stderr' ]} " ) def cov_export_sarif ( self ): """SARIF2.1 표준 보고서 내보내기""" export_cmd = [ "cov-export" , "--dir" , self.int_dir, "--format" , "sarif" , "-o" , self.sarif_out_path ] ret = run_shell_cmd(export_cmd) if not ret[ "success" ]: raise Exception( f"Coverity SARIF 내보내기 실패: {ret[ 'stderr' ]} " ) return self.sarif_out_path def full_scan_pipeline ( self ) -> str : """Coverity 전체 스캔 파이프라인 진입점""" self.cov_build_capture() self.cov_analyze_defect() sarif_path = self.cov_export_sarif() return sarif_path # ===================== SARIF 파싱 정규화 모듈 ===================== def parse_sarif_to_vuln_list ( sarif_file: str , tool_name: str , repo_root: str ) -> List [VulnerabilityItem]: """CodeQL/Coverity SARIF를 통합 파싱하여 표준화된 취약점 구조체 출력""" vuln_result = [] parser = SarifParser(sarif_
file) runs = parser.runs for run in runs: results = run.results rules = {rule. id : rule for rule in run.tool.driver.rules} if run.tool.driver.rules else {} for res in results: if not res.locations: continue loc = res.locations[ 0 ].physicalLocation file_rel_path = loc.artifactLocation.uri full_file_path = os.path.join(repo_root, file_rel_path) start_line = loc.region.startLine if loc.region else 1 end_line = loc.region.endLine if loc.region and loc.region.endLine else start_line # 소스 코드 스니펫 읽기 code_snippet = "" if os.path.exists(full_file_path): try : with open (full_file_path, "r" , encoding= "utf-8" , errors= "ignore" ) as f: lines = f.readlines() snippet_start = max ( 0 , start_line - 3 ) snippet_end = min ( len (lines), end_line + 3 ) code_snippet = "" .join(lines[snippet_start:snippet_end]) except Exception: code_snippet = "소스 코드 읽기 실패" rule_info = rules.get(res.ruleId, None ) cwe_id = None if rule_info and hasattr (rule_info, "properties" ) and "cwe" in rule_info.properties: cwe_id = rule_info.properties[ "cwe" ] vuln = VulnerabilityItem( tool_source=tool_name, rule_id=res.ruleId, cwe_id= str (cwe_id) if cwe_id else None , severity=res.level if hasattr (res, "level" ) else "unknown" , file_path=file_rel_path, start_line=start_line, end_line=end_line, source_code_snippet=code_snippet, message=res.message.text if hasattr (res.message, "text" ) else "" ) vuln_result.append(vuln) return vuln_result def merge_and_dedup_vulns ( vuln_lists: List [ List [VulnerabilityItem]] ) -> List [VulnerabilityItem]: """다중 도구 취약점 병합, 중복 제거(동일 파일+동일 행+동일 CWE를 중복 경고로 판정)""" merged = [] unique_key_set = set () for vuln_list in vuln_lists: for vuln in vuln_list: key = f" {vuln.file_path} | {vuln.start_line} | {vuln.cwe_id} " if key not in unique_key_set: unique_key_set.add(key) merged.append(vuln) return merged # ===================== LLM 판단 상호작용 모듈 ===================== def llm_audit_single_vuln ( vuln: VulnerabilityItem ) -> LLMVulnAuditResult: """단일 취약점을 대규모 모델에 전달하여 오탐 여부를 판단하고 수정 코드를 생성""" prompt = f""" 당신은 전문 코드 보안 감사 전문가로서, CWE 표준, C/C++/Java 보안 코딩 규범, 항공전자 임베디드 GJB 보안 요구사항을 엄격히 근거로 취약점을 분석합니다. 취약점 출처 도구: {vuln.tool_source} 검출 규칙 ID: {vuln.rule_id} 대응 CWE 번호: {vuln.cwe_id} 취약점 등급: {vuln.severity} 파일 경로: {vuln.file_path} 취약점 시작·종료 행: {vuln.start_line} ~ {vuln.end_line} 취약점 설명: {vuln.message} 취약점 부근 소스 코드 스니펫: ```code {vuln.source_code_snippet}
출력은 JSON 형식으로 요구하며, 필드 설명은 다음과 같습니다:
- is_true_vulnerability: bool, true는 실제 취약점, false는 도구 오탐
- risk_level: str, high / medium / low / info 중에서만 선택
- misreport_reason: 오탐인 경우 사유를 기재하고, 취약점이면 공란으로 둠
- fix_code: 실제 취약점인 경우 완전하고 대체 가능한 수정 코드를 제시하고, 오탐이면 공란으로 둠
- fix_explanation: 취약점 수정 로직 설명 또는 오탐 판정 근거. 순수 JSON만 반환하고, 별도의 설명이나 markdown, 주석을 추가하지 않는다.
headers = { "Authorization" : f"Bearer {LLM_API_KEY} " , "Content-Type" : "application/json" } payload = { "model" : LLM_MODEL_NAME, "messages" : [{ "role" : "user" , "content" : prompt}], "temperature" : 0.1 } resp = requests.post( f" {LLM_API_BASE} /chat/completions" , headers=headers, json=payload, timeout= 120 ) resp_data = resp.json() content = resp_data[ "choices" ][ 0 ][ "message" ][ "content" ].strip() # LLM 출력 정제, ```json 표식 제거 if content.startswith( "```json" ): content = content[ 7 :- 3 ].strip() audit_raw = json.loads(content) audit_res = LLMVulnAuditResult(**audit_raw) return audit_res def llm_generate_custom_rule ( vuln_patterns: List [VulnerabilityItem], lang: str ) -> str : """LLM이 빈도 높은 취약점을 기반으로 CodeQL 사용자 정의 QL 탐지 규칙을 생성""" pattern_text = "" for v in vuln_patterns: pattern_text += f"CWE {v.cwe_id} : {v.message} \n코드 예시:\n {v.source_code_snippet} \n\n" prompt = f
다음 빈도 높은 취약점 패턴을 기반으로, 완전하게 실행 가능한 CodeQL .ql 쿼리 스크립트를 생성하라. 언어는 {lang}이다. 취약점 패턴 집합: {pattern_text} 요구 사항: import, predicate 정의, 오염 추적 로직, alert 경고를 포함하며, 문법은 codeql query run으로 바로 실행할 수 있어야 하고, ql 전체 코드만 출력하며 불필요한 텍스트는 넣지 않는다.
headers = { "Authorization" : f"Bearer {LLM_API_KEY} " , "Content-Type" : "application/json" } payload = { "model" : LLM_MODEL_NAME, "messages" : [{ "role" : "user" , "content" : prompt}], "temperature" : 0.2 } resp = requests.post( f" {LLM_API_BASE} /chat/completions" , headers=headers, json=payload, timeout= 180 ) content = resp.json()[ "choices" ][ 0 ][ "message" ][ "content" ].strip() if content.startswith( "```ql" ) or content.startswith( "```" ): lines = content.splitlines() content = "\n" .join(lines[ 1 :- 1 ]) return content # ===================== 통합 작업 실행 진입점 ===================== def run_full_scan_task ( task_cfg: ScanTaskConfig ): init_workspace_dir() sarif_paths = [] print ( "===== 정적 코드 스캔 작업 시작 =====" ) # CodeQL 스캔 실행 if task_cfg.enable_codeql: cql_runner = CodeQLRunner(task_cfg) cql_sarif = cql_runner.full_scan_pipeline() sarif_paths.append({ "path" : cql_sarif, "tool" : "codeql" }) # Coverity 스캔 실행 if task_cfg.enable_coverity: cov_runner = CoverityRunner(task_cfg) cov_sarif = cov_runner.full_scan_pipeline() sarif_paths.append({ "path" : cov_sarif, "tool" : "coverity" }) # 모든 SARIF를 파싱하고 취약점 병합 all_vuln_groups = [] for item in sarif_paths: vulns = parse_sarif_to_vuln_list(item[ "path" ], item[ "tool" ], task_cfg.repo_path) all_vuln_groups.append(vulns) print ( f"[ {item[ 'tool' ]} ] 원시 경고 수: { len (vulns)} " ) merged_vulns = merge_and_dedup_vulns(all_vuln_groups) print ( f"병합 및 중복 제거 후 총 경고 수: { len (merged_vulns)} " ) # LLM으로 모든 취약점 일괄 판단 llm_audit_result_list = [] high_risk_vulns = [] print ( "대규모 모델 취약점 판단 시작, 오탐 필터링..." ) for idx, vuln in enumerate (tqdm(merged_vulns)): audit = llm_audit_single_vuln(vuln) llm_audit_result_list.append({ "vuln_info" : vuln. dict (), "audit" : audit.
dict ()}) if audit.is_true_vulnerability and audit.risk_level == "high" : high_risk_vulns.append(vuln) # LLM 전체 감사 결과 저장 task_save_name = os.path.basename(task_cfg.repo_path) + "_audit_result.json" save_path = os.path.join(LLM_RESULT_CACHE, task_save_name) with open (save_path, "w" , encoding= "utf-8" ) as f: json.dump(llm_audit_result_list, f, indent= 2 , ensure_ascii= False ) print ( f"LLM 감사 결과 저장 완료: {save_path} " ) # 고위험 취약점이 존재하면 자동으로 사용자 정의 탐지 규칙 생성 custom_ql_code = None if len (high_risk_vulns) > 0 : print ( f" { len (high_risk_vulns)} 건의 고위험 취약점 탐지, CodeQL 사용자 정의 규칙 자동 생성" ) custom_ql_code = llm_generate_custom_rule(high_risk_vulns, task_cfg.project_lang) ql_save_path = os.path.join(LLM_RESULT_CACHE, f" {task_save_name} .custom.ql" ) with open (ql_save_path, "w" , encoding= "utf-8" ) as f: f.write(custom_ql_code) print ( f"사용자 정의 QL 규칙 저장: {ql_save_path} " ) # 작업 요약 정보 출력 summary = { "repo" : task_cfg.repo_path, "lang" : task_cfg.project_lang, "total_raw_alerts" : sum ( len (g) for g in all_vuln_groups), "unique_alert_after_dedup" : len (merged_vulns), "high_risk_true_vuln_count" : len (high_risk_vulns), "llm_audit_file" : save_path, "custom_rule_ql" : custom_ql_code is not None } print ( "===== 스캔 작업 완료, 요약 정보 =====" ) print (json.dumps(summary, indent= 2 , ensure_ascii= False )) return summary # 명령줄 실행 진입점 if __name__ == "__main__" : import argparse parser = argparse.ArgumentParser(description= "CodeQL+Coverity 대규모 모델 연동 정적 스캔 스케줄링 도구" ) parser.add_argument( "--repo" , required= True , help = "코드 저장소 루트 디렉터리 절대 경로" ) parser.add_argument( "--lang" , required= True , choices=[ "cpp" , "java" , "python" , "javascript" ], help = "프로젝트 언어" ) parser.add_argument( "--build-cmd" , help = "프로젝트 컴파일 명령, C/C++/Java 필수" ) parser.add_argument( "--no-codeql" , action= "store_true" , help = "CodeQL 스캔 비활성화" ) parser.add_argument( "--no-coverity" , action= "store_true" , help = "Coverity 스캔 비활성화" ) args = parser.parse_args() task_config = ScanTaskConfig( repo_path=args.repo, project_lang=args.lang, build_command=args.build_cmd, enable_codeql= not args.no_codeql, enable_coverity= not args.no_coverity ) run_full_scan_task(task_config)
四、스크립트 실행 사용 예시
1. C++ 임베디드 항공전자 프로젝트 (CodeQL+Coverity 듀얼 엔진 스캔)
python static_scan_orchestrator.py \ --repo /data/workspace/avionics_cpp \ --lang cpp \ --build-cmd "mkdir build && cd build && cmake .. && make -j4"
2. Java 백엔드 프로젝트에서 CodeQL 스캔만 활성화
python static_scan_orchestrator.py \ --repo /data/workspace/java_service \ --lang java \ --build-cmd "mvn clean compile" \ --no-coverity
3. Python 프로젝트에서 CodeQL만 사용 (컴파일 명령 불필요)
python static_scan_orchestrator.py \ --repo /data/workspace/python_demo \ --lang python \ --no-coverity
五、스크립트 핵심 최적화 포인트
- 계층형 분리 아키텍처: CodeQL, Coverity, SARIF 파싱, LLM 상호작용이 완전히 격리되어 있어, 새로운 스캔 도구를 추가할 때는 Runner 클래스만 추가하면 됩니다;
- 강타입 데이터 제약: Pydantic을 기반으로 작업, 취약점, LLM 반환 구조체를 정의하여 JSON 필드가 뒤섞이는 문제를 방지합니다;
- SARIF 표준화 통합 파싱: 두 도구의 출력을 동일한 취약점 모델로 통일해 정규화하므로, 상위 LLM은 도구 출처를 구분할 필요가 없습니다;
- 소스 코드 컨텍스트 자동 추출: 각 취약점마다 취약점 전후 3줄의 코드를 자동으로 읽어 LLM 판단 정확도를 높입니다;
- 지능형 취약점 중복 제거: 파일 경로+행 번호+CWE 삼중 키로 중복을 제거하여 LLM의 불필요한 호출을 줄입니다;
- LLM 출력 정제 로직: markdown 코드 블록 표시를 자동으로 제거하여 JSON 파싱 이상을 방지합니다;
- 캐시 디렉터리 통합 관리: CodeQL 데이터베이스, cov-int 중간 파일, SARIF 보고서, LLM 감사 결과를 디렉터리별로 저장하고, 캐시 재사용을 지원하여 증분 스캔을 가속합니다;
- 완전한 로그 및 예외 포착: 모든 CLI 실행에서 stdout/stderr를 포착하고, 실패 시 명확한 오류 정보를 던져 문제 파악을 용이하게 합니다;
- 사용자 정의 탐지 규칙 자동 생성: 모든 고위험 취약점을 취합하여 LLM이 바로 실행 가능한 CodeQL QL 스크립트를 출력합니다;
- 완전한 명령줄 파라미터 기반 기동으로 GitLab CI, Jenkins 파이프라인과 원활하게 연동되어 원클릭 호출이 가능합니다.
六、프로덕션 환경 확장 최적화 방안
- 증분 스캔 기능: git diff를 연동해 변경된 파일만 스캔하여 전체 스캔 소요 시간을 크게 단축합니다;
- 비동기 작업 큐: Celery+RabbitMQ 기반으로 개조하여 다중 프로젝트 동시 스캔, CPU/메모리 자원 스로틀링을 지원합니다;
- Coverity Connect 원격 API 연동: CoverityApiRunner 클래스를 추가하여 로컬의 전체 툴체인 없이 원격으로 플랫폼의 결함을 가져옵니다;
- 오탐 영구 캐시: LLM이 판정한 안정적인 오탐을 sqlite 데이터베이스에 저장하여 다음 스캔 시 자동으로 필터링합니다;
- 포맷 보고서 내보내기: HTML, Markdown 감사 보고서를 지원하고 PR 코멘트를 자동으로 푸시합니다;
- 캐시 자동 정리: 정기 스크립트로 만료된 CodeQL DB, cov-int 캐시를 정리하여 디스크가 가득 차는 것을 방지합니다;
- 동시 실행 세마포어 제어: 동시에 실행되는 스캔 작업 수를 제한하여 서버 자원 고갈을 방지합니다;
- 경보 등급 게이트: 고위험 취약점에 대해 CI 파이프라인을 차단하고 코드 병합을 금지하도록 구성할 수 있습니다.
七、흔한 문제 트러블슈팅
- CodeQL 데이터베이스 생성 실패: 컴파일 명령이 독립적으로 실행 가능한지, 소스 코드에 문법 오류가 없는지, 디스크 공간이 충분한지 확인합니다;
- Coverity cov-build에서 결함 출력 없음: 컴파일 명령이 목적 파일을 생성할 수 있도록 완전히 컴파일해야 하며, 빈 컴파일은 경고를 발생시키지 않습니다;
- LLM 인터페이스 요청 오류: .env 파일의 LLM 주소, 키, 모델 이름 설정을 확인합니다;
- SARIF 파싱 결과가 비어 있음: 도구 스캔에 취약점이 없거나, 규칙 세트를 경량화 패키지로 선택한 경우이므로, 전체 보안 규칙 세트로 전환해 재시도합니다;
- 권한 오류: codeql, cov-build, 프로젝트 소스 디렉터리에 읽기/쓰기/실행 권한을 부여합니다.