Kibana CodeQL 安全扫描实战:自定义查询的本地开发、单元测试、远程 SARIF 获取与内联抑制
【免费下载链接】kibanaYour window into all of your data项目地址: https://gitcode.com/GitHub_Trending/ki/kibana
Kibana 仓库内置了一套完整的 CodeQL 安全扫描工作流:以 .github/codeql 目录承载自定义安全查询(qlpack),以 quick_check.sh 脚本在 Docker 中完成本地建库、分析与单元测试,以 fetch_sarif.mjs 脚本从 GitHub 拉取远端扫描结果。本文基于仓库中的 CodeQL 技能文档 与配套源码,系统讲解这套体系的目录布局、本地运行方式、单元测试结构、新查询编写规范、内联抑制(inline suppression)约定以及常见故障排查,读完后可直接在本地复现 Kibana 的 CodeQL 开发-测试-验证闭环。
整体目录布局
Kibana 的 CodeQL 相关资产分布在两个位置:.github/codeql/(查询与配置)和scripts/codeql/(本地运行工具):
.github/codeql/ ├── codeql-config.yml # Main config (paths-ignore, packs, query-filters) ├── custom-queries/ │ ├── qlpack.yml # QL pack definition (name: kibana-custom-queries) │ ├── codeql-pack.lock.yml │ ├── suppression/ # Alert suppression logic │ │ ├── AlertSuppression.ql │ │ └── AlertSuppression.qll │ └── <category>/ # e.g. dos/, xss/ │ ├── <RuleName>.ql # Query file │ ├── <RuleName>.qhelp # Help docs (XML) │ ├── <RuleName>.md # Human-readable docs │ ├── <category>-security.qls # Query suite │ └── <RuleName>/ # Unit test directory │ ├── <RuleName>.qlref # Points to the .ql file (relative to qlpack root) │ ├── <RuleName>.expected # Expected test output │ └── test.js # Test source code scripts/codeql/ ├── quick_check.sh # Local analysis via Docker └── codeql.dockerfile # Docker image (ubuntu + CodeQL CLI)各组成部分在仓库中均有真实落地,可以逐一对照:
qlpack.yml 定义查询包元信息:包名为
kibana-custom-queries,版本1.0.0,依赖官方的codeql/javascript-all库,提取器(extractor)为javascript:name: kibana-custom-queries version: 1.0.0 dependencies: codeql/javascript-all: "*" extractor: javascriptcodeql-config.yml 是 GitHub CodeQL 分析的主配置,包含三部分:
paths-ignore:大规模排除测试夹具(**/__fixtures__/**)、mock/stub 目录、*.test.*/*.spec.*文件、dev_docs、docs、examples、scripts等非产品代码路径,保证扫描聚焦于真实运行代码;query-filters:排除官方库中与 Kibana 技术栈无关的规则目录(codeql/javascript-queries/AngularJS、codeql/javascript-queries/Electron);packs与queries:声明官方githubsecuritylab/codeql-javascript-queries查询包,并通过uses: ./.github/codeql/custom-queries把自定义查询纳入分析。
自定义查询目前已按风险类别组织为
dos/(拒绝服务)与xss/(跨站脚本)两类,另有suppression/承载抑制逻辑。例如 custom-queries/README.md 记录了UnboundedArrayInRoute规则:ID 为js/kibana/unbounded-array-in-route,用于检测缺少maxSize约束的schema.arrayOf()调用,修复方式是添加{ maxSize: N }第二个参数。
本地运行分析(quick_check.sh)
quick_check.sh 是本地 CodeQL 全流程入口:它通过 Docker 构建 CodeQL 数据库,并对真实源码运行查询。三种典型用法:
# 用整个自定义查询目录分析指定源码目录 bash scripts/codeql/quick_check.sh -s <source_dir> -q .github/codeql/custom-queries # 用单个查询文件分析 bash scripts/codeql/quick_check.sh -s <source_dir> -q .github/codeql/custom-queries/dos/UnboundedArrayInRoute.ql # 指定结果目录 bash scripts/codeql/quick_check.sh -s <source_dir> -r .codeql-results -q .github/codeql/custom-queries选项说明:
| 选项 | 说明 |
|---|---|
-s <source_dir> | (分析模式必填)要扫描的源码目录 |
-q <query_dir\|query_file> | 自定义查询目录,或单个.ql文件 |
-r <results_dir> | 数据库与 SARIF 的存放位置(默认.codeql/) |
-t | 改为运行单元测试而非分析(配合-q使用,不需要-s) |
输出:SARIF 文件位于<results_dir>/database/results.sarif;若系统安装了jq,脚本会自动打印彩色摘要(Rule / Message / File / Line / Security Severity)。
从源码可以看到脚本的完整执行链路:
- 架构适配(quick_check.sh#L47-L54):先通过
uname -m检测本机架构,由于 CodeQL CLI 二进制不支持 arm64,在 Apple Silicon(arm64)机器上会自动附加--platform linux/amd64,以模拟运行方式执行。 - 镜像构建(quick_check.sh#L56-L63):首次运行时本地构建名为
codeql-env的镜像,构建上下文为scripts/codeql/,Dockerfile 见下节。 - 建库(quick_check.sh#L112-L122):把源码目录挂载进容器,执行
codeql database create /workspace/shared/codeql-db --language=javascript --source-root=/workspace/source-code --overwrite。 - 分析(quick_check.sh#L124-L170):分三种分支——
-q指向单个.ql文件:脚本从该文件所在目录逐级向上查找qlpack.yml/codeql-pack.yml(quick_check.sh#L130-L149),找到 qlpack 根后以相对路径挂载执行codeql database analyze;-q指向目录:直接挂载该目录分析;- 未指定
-q:回退到官方查询集,运行codeql database analyze ... javascript-security-and-quality.qls githubsecuritylab/codeql-javascript-queries ... --download(quick_check.sh#L165-L170)。
- 摘要打印(quick_check.sh#L178-L219):用
jq解析 SARIF 的runs[].results[],按ruleId关联tool.driver.rules取出security-severity并着色输出;无jq时提示安装并指引用 SARIF 查看器查看完整结果。
quick_check.sh#L5 附近还定义了固定常量:语言固定为javascript,输出格式sarif-latest,默认结果目录.codeql,镜像名codeql-env。
配套镜像 codeql.dockerfile 基于ubuntu:latest,安装git、nodejs、jq等基础工具后,下载并解压 CodeQL CLIv2.23.2官方 bundle 到/usr/local/codeql-home,并以非 root 用户codeql运行(USER codeql),入口为ENTRYPOINT ["/bin/bash", "-c"],便于docker run直接传入codeql子命令。
CodeQL 单元测试
单元测试用于验证“查询是否精准命中目标行”。每个测试位于以查询命名的子目录中,结构如下:
<category>/<RuleName>/ ├── <RuleName>.qlref # Reference: "category/RuleName.ql" ├── test.js # Source code with `// $ Alert` annotations └── <RuleName>.expected # Expected output (auto-generated or hand-written)标注约定:
- 行尾
// $ Alert表示查询应当标记该行; - 未标注
// $ Alert的行不应被标记; .expected文件为管道分隔格式:| <location> | <message> |。
以仓库中现成的 dos/UnboundedArrayInRoute 测试为例:
UnboundedArrayInRoute.qlref 内容只有一行
dos/UnboundedArrayInRoute.ql,即相对 qlpack 根(custom-queries/)的查询路径;test.js 是一组带标注的模拟路由校验代码,覆盖多种正负用例,例如:
// BAD: Direct router pattern without maxSize router.post( { path: '/api/bad/direct-array', validate: { body: schema.arrayOf(schema.string()), // $ Alert }, }, handler );测试还包含嵌套在
schema.object内的数组、只有minSize没有maxSize的空 options 等边界场景,以及测试所需的__fixtures__/桩模块;UnboundedArrayInRoute.expected 记录预期的告警行与消息。
通过 Docker 运行测试(复用codeql-env镜像,首次运行自动构建):
# 运行某个具体测试目录 bash scripts/codeql/quick_check.sh -t -q .github/codeql/custom-queries/dos/UnboundedArrayInRoute # 运行 qlpack 下全部测试 bash scripts/codeql/quick_check.sh -t -q .github/codeql/custom-queries从 quick_check.sh#L68-L98 可以看到测试模式的实现:-t与-q组合使用时,脚本先向上定位 qlpack 根目录(找不到则报错退出),再把测试路径换算为相对 qlpack 根的路径,最后在容器内执行:
codeql test run /workspace/queries/<TEST_REL_PATH> --additional-packs /workspace/queries--additional-packs保证查询内import dos.KibanaDoSExclusions这类 qlpack 内部导入可以解析。
CI 中的单元测试:.github/workflows/codeql-pr.yml 工作流在 PR 触发(目标分支main,且改动涉及 JS/TS 源码或.github/codeql/**时)自动执行三件事:
- 权限检查(codeql-pr.yml#L18-L50):先确认 PR 作者具有
admin/maintain/write权限,避免外部 PR 触发耗时的分析任务; - 单元测试(codeql-pr.yml#L73-L87):
find .github/codeql/custom-queries -name "*.qlref"收集所有测试目录并去重,逐个执行codeql test run "$testdir" --additional-packs .github/codeql/custom-queries;若一个测试目录都找不到会直接失败,防止测试被静默跳过; - 正式分析(codeql-pr.yml#L89-L123):
codeql-action/init指定config-file: ./.github/codeql/codeql-config.yml,分析时设置环境变量CODEQL_EXTRACTOR_JAVASCRIPT_OPTION_SKIP_TYPES: true以跳过类型信息提取,加快大型仓库的扫描速度。
编写新查询
编写新查询遵循五步流程(对应技能文档 “Writing a New Query” 一节):
创建
.ql文件,放在.github/codeql/custom-queries/<category>/:- 使用
@id js/kibana/<descriptive-id>(必须全局唯一); - 包含
@kind problem(污点跟踪类查询用path-problem); - 设置
@problem.severity与@security-severity; import javascript引入官方 JavaScript 库;- 可参考 UnboundedArrayInRoute.ql 的既有写法。
以现有 DoS 查询为例,它的核心思路是:先通过
schemaVariable()谓词定位从@kbn/config-schema导入的schema绑定(同时兼容具名导入与命名空间导入),再在SchemaArrayOfCall类中匹配arrayOf属性调用,并用hasMaxSize()排除已提供maxSize选项的调用:/** * Gets the local variable bound to 'schema' imported from '@kbn/config-schema' */ LocalVariable schemaVariable() { exists(ImportDeclaration decl, ImportSpecifier spec | decl.getImportedPathExpr().getStringValue() = "@kbn/config-schema" and spec = decl.getASpecifier() and ( spec.getImportedName() = "schema" or spec instanceof ImportNamespaceSpecifier ) and result = spec.getLocal().getVariable() ) }from SchemaArrayOfCall arrayCall where not arrayCall.hasMaxSize() and not shouldExcludeFileFromDoSRules(arrayCall) select arrayCall, "This schema.arrayOf() call does not specify a maxSize. Unbounded input can cause Denial of Service (DoS) vulnerabilities. Consider adding { maxSize: N } as the second argument."注意查询头部还引用了共享的排除逻辑
import dos.KibanaDoSExclusions,通过shouldExcludeFileFromDoSRules过滤已知误报文件,这是 Kibana 自定义查询控制精确率(@precision medium)的一种手段。- 使用
创建单元测试目录
<category>/<RuleName>/:<RuleName>.qlref内容为<category>/<RuleName>.ql;test.js内含// $ Alert标注的测试用例;- 运行测试生成
.expected,并人工核对是否符合预期。
添加
.qhelp(XML)与/或.md作为规则文档。可选:若需将多条查询成组运行,添加
.qls查询套件(现有示例如 dos-security.qls)。本地实测:用
quick_check.sh对真实 Kibana 源码跑一遍,确认查询在生产代码上的行为。
获取远程 SARIF / 扫描结果
fetch_sarif.mjs 脚本用于从 GitHub 拉取某个 PR 或分支的 CodeQL 结果:
# 按 PR 号 GITHUB_TOKEN=ghp_xxx node .agents/skills/codeql/scripts/fetch_sarif.mjs 252121 # 按完整 ref GITHUB_TOKEN=ghp_xxx node .agents/skills/codeql/scripts/fetch_sarif.mjs refs/heads/main要求:环境变量GITHUB_TOKEN需具备security_events权限(scope);脚本依赖@octokit/rest(已在 Kibana 依赖中)。
从 fetch_sarif.mjs 源码可以看到它固定针对elastic/kibana仓库执行四个步骤:
- 解析 ref:输入为纯数字时自动转换为
refs/pull/<N>/merge(fetch_sarif.mjs#L44),否则原样作为分支 ref 使用; - 列出分析记录:调用
codeScanning.listRecentAnalyses获取该 ref 最近的 CodeQL 分析(取前 10 条),无结果直接退出; - 下载 SARIF:取最新一条分析,经
getSarif→analyses_url→ 分析详情 URL 三级跳转,以Accept: application/sarif+json拉取完整 SARIF,然后遍历runs[].results[],按ruleId关联规则表打印Rule / Severity / Message / File:Line摘要(fetch_sarif.mjs#L94-L116); - 拉取告警列表:调用
codeScanning.listAlertsForRepo获取同一 ref 的 code scanning alerts(每页 100 条),打印编号、规则、严重级别、状态与最近实例位置。
这使开发者无需打开 Web 界面即可在终端核对 CI 扫描结果,适合在本地修复误报或确认新告警后与远端数据比对。
内联抑制(Inline Suppressions)
对于确实安全的告警,Kibana 采用行内抑制注释,格式为// codeql[rule-id] justification text,且每条抑制必须附带具体理由,说明为什么该处是安全的。
有效示例:
// codeql[js/path-injection] User input is validated against an allowlist before use return fs.readFileSync(`/etc/${validatedPath}`, 'utf8');无效示例(应被标记出来):
- 缺少理由:
// codeql[js/path-injection]后跟任何解释; - 泛化理由:
"false positive"、"safe"、"not a vulnerability"—— 没有说明实际缓解措施; - 不完整理由:如仅写
"sanitized"—— 没有说明以何种机制完成净化。
好的理由应描述具体的安全机制,例如白名单校验、DOMPurify 转义、shell-quote 库、仅限测试代码等。
支撑这套机制的是 suppression/AlertSuppression.ql:它是一个@kind alert-suppression查询(IDjs/alert-suppression),定义了单行注释节点SingleLineComment(要求注释内不含换行符)并复用官方AS::Make<AstNode, SingleLineComment>机制,使 GitHub 能识别并解析// codeql[...]注释中的规则 ID 与理由文本,从而在平台上展示抑制理由。
故障排查
| 问题 | 解决方案 |
|---|---|
| Docker 在 ARM 上构建失败 | 确认设置了--platform linux/amd64(脚本会自动处理,见 quick_check.sh#L51-L54) |
报qlpack.ymlnot found | 脚本从.ql文件所在目录逐级向上查找qlpack.yml—— 确保custom-queries/根目录下存在该文件 |
测试产生.actual文件 | 对比.actual与.expected的 diff ——.actual文件已被 gitignore |
| 查询什么都没命中 | 检查 codeql-config.yml 的paths-ignore—— 测试/mock 目录被整体排除 |
摘要因找不到jq而无法打印 | 安装 jq(如brew install jq) |
小结
Kibana 的 CodeQL 体系形成了“查询包 + 本地工具 + CI 门禁 + 远端结果”的完整闭环:
- 查询包kibana-custom-queries 与 codeql-config.yml 决定扫描范围与规则集;
- quick_check.sh在
codeql-env容器(CodeQL v2.23.2)中统一完成建库、分析与codeql test run,并在 arm64 上自动降级为 amd64 模拟执行; - 单元测试(
.qlref+test.js+.expected)保证每条规则在改动前后行为可控,并由 codeql-pr.yml 在 PR 上强制执行; - fetch_sarif.mjs补齐了远端结果核对环节;
- 内联抑制规范+ AlertSuppression.ql 让每一次告警豁免都有据可查。
更多查询写法细节可参考 CodeQL 官方“编写 CodeQL 查询”文档(随 CodeQL CLI 发布),仓库内现成的 dos/ 与 xss/ 查询目录则是最直接的可运行范例。
【免费下载链接】kibanaYour window into all of your data项目地址: https://gitcode.com/GitHub_Trending/ki/kibana
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考