大模型联动 CodeQL + Coverity 完整落地方案
一、整体系统分层架构(解耦可扩展设计)
分层说明
- 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+,依赖库安装命令:
bash
pip install pydantic python-dotenv requests tqdm sarif-parser
2. 环境配置文件 .env
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
python
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、注释。
python
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完整代码,无多余文字。
python
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双引擎扫描)
bash
python static_scan_orchestrator.py \
--repo /data/workspace/avionics_cpp \
--lang cpp \
--build-cmd "mkdir build && cd build && cmake .. && make -j4"
2. Java后端项目仅启用CodeQL扫描
bash
python static_scan_orchestrator.py \
--repo /data/workspace/java_service \
--lang java \
--build-cmd "mvn clean compile" \
--no-coverity
3. Python项目仅CodeQL(无需编译命令)
bash
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三重key去重,减少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、项目源码目录授予读写执行权限。