CodeQL 分析
支持的语言:Python、JavaScript/TypeScript、Go、Java/Kotlin、C/C++、C#、Ruby、Swift。
技能资源: 参考文件和工作模板位于 {baseDir}/references/ 和 {baseDir}/workflows/。
核心原则
-
数据库质量不可妥协。 一个能构建成功的数据库并不自动意味着质量良好——缓存的构建不提取任何内容,但报告构建成功。
-
数据扩展捕获 CodeQL 遗漏的问题。 Django、Spring 和 Express 项目仍然将数据库调用、请求解析和 shell 执行封装在项目特定的 API 中,任何自带的模型都无法覆盖这些。
-
显式套件引用可防止静默查询丢失。 切勿将包名直接传递给
codeql database analyze——每个包的defaultSuiteFile都会应用隐藏过滤器,可能产生零结果。始终生成一个.qls文件。 -
零发现需要调查,而非庆祝。 可能意味着提取不佳、缺少模型、使用了错误的包或套件过滤。构建后运行
{baseDir}/scripts/check_db_quality.py,确认{baseDir}/scripts/verify_query_suite.py对正在使用的套件退出代码为零——生成脚本会运行它,所以仅在重复使用或手动编辑套件时才需要手动调用——并在报告中说明两者均已通过。 -
macOS Apple Silicon 需要针对编译型语言的变通方案。 退出代码 137 是
arm64e/arm64不匹配,而非构建失败。在回退到build-mode=none之前,先尝试 Homebrew arm64 工具或 Rosetta。 -
严格按步骤执行工作流。 每个阶段都作为下一阶段的门控;跳过质量评估或数据扩展会在结果中留下不可见的差距。
每次 Bash 调用都是全新的 Shell
没有任何内容可以跨 Bash 调用传递:变量、数组或从 build_log.sh 中 sourced 的函数都不行。下面每个使用某个值的代码块都必须在同一个代码块内重新建立该值。工作流指向此处而非重复说明;它们声明的是该站点特定的损害,因为每个站点都以不同且静默的方式失败:
- 丢失的函数会使
run_logged退出代码为 127,构建阶梯将其解读为失败的方法,并降级至--build-mode=none,从未调用过 CodeQL - 丢失的数组扩展为空,因此用户选择的每个
--threat-model和--model-packs都会被丢弃,而最终报告仍会列出它们已使用 - 在
set -u下丢失的标量会以unbound variable中止代码块
输出目录
所有生成的文件(数据库、构建日志、诊断信息、扩展、结果)都存储在一个输出目录中。
- 如果用户在提示中指定了输出目录,则将其用作
OUTPUT_DIR。 - 如果未指定,则默认为
./static_analysis_codeql_1。如果该目录已存在,则递增为_2、_3,依此类推。
在这两种情况下,始终先创建目录(使用 mkdir -p),然后再写入任何文件。
在运行此操作之前,将 USER_SPECIFIED_DIR 设置为用户提示中的文字路径,或将其保留未设置以自动递增。没有其他内容会赋值给它。
# 解析输出目录
USER_SPECIFIED_DIR="${USER_SPECIFIED_DIR:-}" # 如果存在,请在此处替换用户的路径
if [ -n "$USER_SPECIFIED_DIR" ]; then
OUTPUT_DIR="$USER_SPECIFIED_DIR"
else
BASE="static_analysis_codeql"
N=1
while [ -e "${BASE}_${N}" ]; do
N=$((N + 1))
done
OUTPUT_DIR="${BASE}_${N}"
fi
mkdir -p "$OUTPUT_DIR"
输出目录在任何工作流执行之前解析一次。所有工作流都接收 $OUTPUT_DIR 并将其工件存储在那里:
$OUTPUT_DIR/
├── rulesets.txt # 选定的查询包(步骤 3 后记录)
├── codeql.db/ # CodeQL 数据库(包含 codeql-database.yml 的目录)
├── build.log # 构建日志
├── codeql-config.yml # 排除配置(解释型语言)
├── diagnostics/ # 诊断查询和 CSV
├── extensions/ # 数据扩展 YAML
├── raw/ # 未过滤的分析输出
│ ├── results.sarif
│ └── run-all.qls | important-only.qls
└── results/ # 最终结果(为重要模式过滤,为全模式复制)
└── results.sarif
数据库发现
CodeQL 数据库通过其目录中是否存在 codeql-database.yml 标记文件来识别。搜索现有数据库时,始终收集所有匹配项——可能因之前的运行或不同语言而存在多个数据库。
发现命令。 find_databases.sh 每行列出一个数据库路径,过滤掉失败构建留下的标记文件。在选择该数组的同一个代码块内构建数组——每次 Bash 调用都是全新的 shell,所以在此构建的数组在下一次调用时为空,运行会认为没有数据库:
# 命令替换,而不是 `done < <(...)`:进程替换会丢弃脚本的退出状态,
# 因此“此 shell 的 PATH 上没有 codeql”(退出码 2)会作为一个空列表返回,
# 并导致即使磁盘上有三个完好的数据库,也会转而执行“构建新数据库”。
if ! DB_LIST=$("{baseDir}/scripts/find_databases.sh" "${OUTPUT_DIR:-.}" .); then
echo "错误:数据库发现失败——请参阅上面的消息" >&2
exit 1
fi
FOUND_DBS=()
while IFS= read -r db; do
[ -n "$db" ] || continue
FOUND_DBS+=("$db")
done <<<"$DB_LIST"
echo "找到 ${#FOUND_DBS[@]} 个现有数据库"
# 选择提示所需的元数据,在此处收集而非在其自己的块中:
# FOUND_DBS 在下一次 Bash 调用时消失,对一个不再存在的数组进行循环
# 不会打印任何内容并报告成功。
for db in "${FOUND_DBS[@]}"; do
CODEQL_LANG=$(codeql resolve database --format=json -- "$db" 2>/dev/null | jq -r '.languages[0]')
CREATED=$(grep '^creationMetadata:' -A5 "$db/codeql-database.yml" 2>/dev/null | grep 'creationTime' | awk '{print $2}')
echo "$db — 语言: $CODEQL_LANG,创建时间: $CREATED"
done
切勿假定数据库名为 codeql.db——通过其标记文件来发现它。
当找到多个数据库时: 使用 AskUserQuestion 让用户根据上面打印的语言和创建时间选择使用哪个数据库,或构建一个新数据库。AskUserQuestion 最多接受四个选项,因此当数据库数量超过该限制时,提供最近三个加上“构建新数据库”,并在提示文本中列出其余选项。如果用户在提示中明确说明了使用哪个数据库或构建一个新数据库,则跳过 AskUserQuestion。
快速开始
对于常见情况(“扫描此代码库以查找漏洞”):
# 验证 CodeQL 是否已安装。如果未安装,在此处停止——后续每条命令都会失败,
# 并带有信息量较少的错误,并且运行会在说明原因之前浪费一个构建周期。
if ! command -v codeql >/dev/null 2>&1; then
echo "错误:在 PATH 上未找到 codeql。请通过以下方式之一安装:" >&2
echo " gh extension install github/gh-codeql # 然后:gh codeql install-stub" >&2
echo " brew install --cask codeql" >&2
echo " https://github.com/github/codeql-action/releases (codeql-bundle)" >&2
exit 1
fi
# jq 在下一步中解析 `codeql resolve database --format=json`。没有它,
# CODEQL_LANG 将返回空,运行将继续针对错误的语言。
if ! command -v jq >/dev/null 2>&1; then
echo "错误:在 PATH 上未找到 jq(brew install jq / apt install jq)" >&2
exit 1
fi
# uv 运行两个防护脚本和两个套件生成器。在此处而不是在套件生成时检查它,
# 套件生成是在构建之后——否则没有 uv 的机器会在失败之前花费整个构建时间。
if ! command -v uv >/dev/null 2>&1; then
echo "错误:在 PATH 上未找到 uv(https://docs.astral.sh/uv/getting-started/)" >&2
exit 1
fi
codeql --version
然后使用上面输出目录中的代码块解析 OUTPUT_DIR——它会尊重用户指定的目录,而简单的自动递增则不会。
然后执行完整的管道:构建数据库 → 创建数据扩展 → 运行分析,使用下面的工作流。
应拒绝的合理化解释
这些捷径会导致遗漏发现。不要接受它们:
- “security-extended 就够了” - 这是基线。始终检查 Trail of Bits 包和社区包是否可用于该语言。它们捕获
security-extended完全遗漏的类别。 - “security-and-quality 是最广泛的套件” -
security-and-quality排除了所有experimental/查询路径。对于全模式,同时导入security-and-quality和security-experimental。根据语言不同,差异有 1-52 个查询。 - “数据库构建成功了,所以没问题” - 数据库能构建不代表提取得好。始终运行质量评估并检查文件计数是否与预期的源文件相符。
- “标准框架不需要数据扩展” - 即使是 Django/Spring 应用也存在 CodeQL 无法建模的自定义包装器。跳过扩展意味着遗漏漏洞。
- “对于编译型语言,build-mode=none 就可以了” - 它会产生严重不完整的分析。仅作为绝对最后的手段使用。在 macOS 上,先尝试 arm64 工具链变通方案或 Rosetta。
- “构建在 macOS 上失败,就直接用 build-mode=none” - 退出代码 137 由
arm64e/arm64不匹配引起,而非根本性构建失败。请参阅 references/macos-arm64e-workaround.md。 - “没有发现结果意味着代码是安全的” - 运行
check_db_quality.py和verify_query_suite.py并报告它们已通过。没有它们,零发现与一个未提取任何内容的数据库在输出上是相同的。 - “我就运行默认套件” / “我直接传递包名” - 每个包的
defaultSuiteFile都会应用隐藏过滤器并可能产生零结果。始终使用显式套件引用。 - “我把文件放在当前目录” - 所有生成的文件必须放在
$OUTPUT_DIR中。将文件散落在工作目录中会使清理变得不可能,并存在覆盖之前运行结果的风险。 - “直接使用我找到的第一个数据库” - 可能存在针对不同语言或来自先前运行的多个数据库。当找到多个时,向用户展示所有选项。仅在用户已经明确指定要使用哪个数据库时才跳过此提示。
- “用户说了‘扫描’,意味着他们希望我选择一个数据库” - “扫描”不是数据库选择。如果存在多个数据库且用户未指定,请询问。
工作流选择
此技能包含三个工作流。一旦选择了某个工作流,请逐步执行,不要跳过任何阶段。
这些运行时间较长。数据库构建有四种回退方法,因此使用任务工具来跟踪进度。根据运行情况决定哪些步骤值得跟踪。
| 工作流 | 目的 |
|---|---|
| build-database | 按顺序使用构建方法创建 CodeQL 数据库 |
| create-data-extensions | 检测或生成为项目 API 的数据扩展模型 |
| run-analysis | 选择规则集,执行查询,处理结果 |
无人值守构建
此插件提供 /static-analysis:codeql-build,它可以端到端执行构建数据库步骤:检测语言和工具链,逐级使用方法阶梯,并在梯级之间应用 build-fixes.md 中的修复,并强制执行质量关卡。
/static-analysis:codeql-build {"target": "/abs/path", "lang": "cpp"}
它不会提出任何问题。每种方法均失败,以及数据库已构建但低于质量阈值,都会以状态返回——no-method-succeeded 和 built-below-threshold——供您在此处处理,因为剩余提取器错误是否仅限于无需分析的代码是一个运行无法做出的判断。
当构建是漫长且不确定的部分,并且您希望将其推向结论时使用它。当您希望对尝试哪种方法有发言权,或者当构建失败需要即时解释时,请手动执行 build-database.md。
自动检测逻辑
如果用户明确指定了要做什么(例如,“构建一个数据库”、“对 ./my-db 运行分析”),则直接执行该工作流。如果用户的提示已经清楚表达了他们的意图,则不要调用 AskUserQuestion 进行数据库选择——例如,“构建一个新数据库”、“分析 static_analysis_codeql_2 中的 codeql 数据库”、“从头开始运行完整扫描”。
对于“测试”、“扫描”、“分析”等类似指令的默认管道: 使用上面数据库发现中的命令发现现有数据库,然后做出决定。
| 条件 | 操作 |
|---|---|
| 未找到数据库 | 解析新的 $OUTPUT_DIR,执行构建 → 扩展 → 分析(完整管道) |
| 找到一个数据库 | 使用 AskUserQuestion:重用它还是构建新的? |
| 找到多个数据库 | 使用 AskUserQuestion,上限为四个选项——请参阅数据库发现 |
| 用户明确说明了意图 | 跳过 AskUserQuestion,直接按其指示操作 |
数据库选择提示
当找到现有数据库并且用户未明确指定使用哪一个时,通过 AskUserQuestion 以“现有 CodeQL 数据库”为标题呈现它们。标记每个选项,附带上文收集的路径、语言和创建时间——./static_analysis_codeql_1/codeql.db (language: python, created: 2026-02-24)——并将最后一个选项设为“构建新数据库”。
选择后:
- 如果用户选择了现有数据库: 将
$OUTPUT_DIR设置为其父目录(或包含该数据库的目录),将$DB_NAME设置为所选路径,然后继续执行扩展 → 分析。 - 如果用户选择了“构建新”: 解析一个新的
$OUTPUT_DIR,执行构建 → 扩展 → 分析。
通用决策提示
如果提示中数据库和工作流都不明确,则通过 `AskUserQue
…[为翻译截断]