advanced-security/github-sbom-toolkit
GitHub: advanced-security/github-sbom-toolkit
该工具用于跨 GitHub 组织批量收集依赖图 SBOM 并匹配恶意软件安全公告,帮助企业进行软件供应链安全审计与风险追踪。
Stars: 18 | Forks: 6
# GitHub SBOM 工具包
枚举 GitHub Enterprise(所有组织)或单个组织内所有仓库的 Dependency Graph SBOM。
通过 PURL 搜索收集到的 SBOM,将其缓存以供离线分析,同步恶意软件安全公告,并将 SBOM 包与这些公告进行匹配。
支持人类可读、JSON、CSV 和 SARIF 输出。SARIF 警报可上传至 GitHub Code Scanning。
## 🚀 功能
- 枚举 Enterprise 中的组织以及每个组织中的仓库
- 支持并发 + 可选延迟及重试/限流处理,获取每个仓库的 SBOM
- 获取 SBOM 时提供可选的进度条
- 可选抑制次要速率限制警告,以及完全静默模式以抑制信息提示消息
- 自适应退避:每次触发次要速率限制时,SBOM 获取延迟增加 10%,以减少未来的限流
- 可选的分支扫描†:通过 Dependency Review 获取非默认分支的 SBOM 差异,并在需要时通过 Component Detection + Dependency Submission 提交缺失的依赖快照
- SBOM 和安全公告的离线缓存,支持增量更新
- 匹配:
- 根据版本对 SBOM 包与恶意软件公告进行匹配
- 可选择抑制声明所有版本均受影响的“无限制”恶意软件公告(例如,漏洞范围为 '*'、'>=0')
- 支持通过精确 PURL、semver/range 或通配符(包名路径段后跟 `/*`)搜索包
- 提供用于临时 PURL 查询的交互式 REPL(支持历史记录和优雅的 Ctrl+C 处理)
- 支持 YAML 忽略文件,可在全局或限定于特定组织/仓库范围下抑制特定的公告 ID 或 PURL
- 原因追踪:每次搜索匹配都会显示匹配的查询;每次恶意软件匹配都会显示触发它的公告
- 输出:
- 人类可读的控制台输出
- JSON 或 CSV 输出(至标准输出或文件),包含搜索和恶意软件匹配结果
- 可选的 SARIF 2.1.0 输出,为每个包含恶意软件匹配的仓库生成单独文件
- 包含 Code Scanning 上传†
- 兼容 GitHub.com、GitHub Enterprise Server、GitHub Enterprise Managed Users 以及带有 Data Residency(自定义 base URL)的 GitHub Enterprise Cloud
† 此功能需要 GitHub Advanced Security/GitHub Code Security
## 用法
### 快速开始
收集某个组织内所有仓库的 SBOM(将 JSON 文件写入 `sboms/`),然后执行 PURL 搜索:
```
npm run start -- --sync-sboms --org my-org --sbom-cache sboms --purl pkg:npm/lodash@4.17.21
```
搜索多个 PURL(精确匹配、通配符和 semver range)。`pkg:` 前缀是可选的;系统会自动添加:
```
npm run start -- --sbom-cache sboms \
--purl npm/react@18.2.0 \
--purl 'npm/express/*' \
--purl 'npm/chalk@>=5.0.0 <6.0.0'
```
使用 GitHub Enterprise Server:
```
npm run start -- --sync-sboms --enterprise ent --base-url https://github.internal/api/v3 --sbom-cache sboms --token $GHES_TOKEN
```
### 🔀 分支扫描与 Dependency Review
使用 `--branch-scan` 启用分支 SBOM 收集和依赖差异比对。
参数:
```
--branch-scan # Fetch SBOMs for non-default branches
--branch-limit # Max number of non-default branches per repo (default 10)
--diff-base # Override base branch for diffs (default: repository default)
```
示例:扫描前 5 个功能分支,并将它们与 `main` 分支进行差异比对:
```
npm run start -- --sync-sboms --org my-org \
--sbom-cache sboms --branch-scan --branch-limit 5 \
--diff-base main --token $GITHUB_TOKEN
```
搜索结果将包含分支匹配项:在匹配列表中,包的 PURL 会带有 `@branch` 注释(例如 `pkg:npm/react@18.3.0@feature-x`)。Dependency Review 的添加/更新操作也会被搜索;仅考虑在 head 端添加/更新的包。
如果获取分支 SBOM 或差异失败,错误将被记录,但不会停止对其他分支或仓库的收集。
#### 处理缺失的 Dependency Review 快照
如果 Dependency Review API 对分支差异返回 404(通常是因为 base 或 head commit 上缺少依赖快照),工具包可以选择尝试使用 Component Detection 和 Dependency Submission 生成并提交快照。此功能源自公开的 [Component Detection Dependency Submission Action](https://github.com/advanced-security/component-detection-dependency-submission-action) 并进行了内置和分支定制。
通过以下方式启用自动提交 + 重试:
```
--submit-on-missing-snapshot
```
除非你通过 `--component-detection-bin` 提供本地二进制文件,否则工具将尝试从 GitHub Releases 下载最新的 Component Detection 版本到当前目录并运行它。
如果提交失败,将保留原始的 404 原因并继续收集。
##### 使用本地 Component Detection 二进制文件
与其自动下载最新版本,你可以指定工具包使用本地的 `component-detection` 可执行文件。如果你已经自行管理该二进制文件或需要自定义构建,这将非常有用。
通过 `--component-detection-bin` 传递路径,并可选择限制语言以减少稀疏检出(sparse checkout)的大小:
```
npm run start -- \
--sync-sboms --org my-org --sbom-cache sboms \
--branch-scan --submit-on-missing-snapshot \
--submit-languages JavaScript,TypeScript \
--component-detection-bin /usr/local/bin/component-detection
```
在 MacOS 上,你可能会发现系统保护机制阻止了下载的二进制文件的运行。你可以[查看 .NET 代码](https://github.com/microsoft/component-detection/)并通过如下包装脚本运行它:
```
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR" || exit 1
dotnet run --project "./src/Microsoft.ComponentDetection/Microsoft.ComponentDetection.csproj" "$@"
```
注意事项:
- 提供 `--component-detection-bin` 会跳过任何下载逻辑,直接使用你的二进制文件。
- 快照提交会执行针对特定语言的稀疏检出(sparse checkout),获取常见的清单/锁文件(例如,`package.json`、`requirements.txt`、`pom.xml`)。
- 提交后,工具包会短暂等待并重试一次 dependency review 差异比对。
### 🔑 身份验证
在执行网络操作(如 `--sync-sboms`、`--sync-malware` 和 `--upload-sarif`)时,需要具有适当 scope 的 GitHub token。
细粒度 PAT(fine-grained PAT)需要对 `Contents` 具有 `Read-only` 权限。要上传 SARIF,你需要对 `Code scanning alerts` 具有 `Read and write` 权限。如有必要,你可以使用 Classic PAT,或使用来自 GitHub App 且具有 `repo`、`read:org` 和 `security_events`(写入)scope 的 token。你可能会发现使用 [`gh` CLI](https://cli.github.com/) 生成 token 非常方便。
它可以通过 `GITHUB_TOKEN` 环境变量或 `--token` 参数提供。
离线操作(纯搜索、使用预缓存数据进行匹配)不需要 token。
### 从文件提供 PURL 查询
提供一个包含一个或多个 PURL(或 PURL + semver range)查询的文件,每行一个。空行和以 `#` 开头的行将被忽略。
示例文件 `queries.txt`:
```
# 精确的 PURL
pkg:npm/chalk@5.6.1
# 版本范围 (semver caret)
pkg:npm/chalk@^5.0.0
# 版本范围 (不等式)
pkg:npm/chalk@>=5.0.0 <6.0.0
```
运行(例如使用离线 SBOM):
```
npm run start -- --sbom-cache sboms --purl-file queries.txt
```
### SBOM 缓存工作流
1. 首次收集(运行时逐步填充缓存):
```
npm run start -- --sync-sboms --org my-org --sbom-cache sboms
```
2. 随后的离线搜索(无 API 调用;使用之前写入的按仓库划分的 JSON):
```
npm run start -- --sbom-cache sboms --purl pkg:npm/react@18.2.0
```
### 恶意软件公告同步与匹配
在一次运行中同步恶意软件公告,然后与 SBOM 包进行匹配:
```
npm run start -- --sync-sboms --org my-org --sbom-cache sboms --sync-malware --malware-cache malware-cache --match-malware
```
在更新公告的同时使用已缓存的 SBOM(离线):
```
npm run start -- --sbom-cache sboms --sync-malware --malware-cache malware-cache --match-malware
```
使用已缓存的恶意软件公告进行离线匹配(无网络调用):
```
npm run start -- --sbom-cache sboms --malware-cache malware-cache --match-malware
```
仅同步恶意软件公告(无需 SBOM 缓存):
```
npm run start -- --sync-malware --malware-cache malware-cache --token $GITHUB_TOKEN
```
使用 `--output-file` 将恶意软件匹配结果(以及稍后可选的搜索结果)写入 JSON 文件:
```
npm run start -- --sbom-cache sboms --malware-cache malware-cache --match-malware --output-file report.json
```
如果你在同一次调用中也执行了搜索(添加 `--purl` 或 `--purl-file`),JSON 文件将同时包含 `malwareMatches` 和 `search` 顶级键。
#### 公告速率限制处理
公告同步使用 GitHub GraphQL 和自适应重试/退避机制来处理次要速率限制和暂时性错误:
- 在遇到 `403` 次要速率限制、`429` 和 `5xx` 响应时重试。
- 提供时遵守 `Retry-After`;否则使用带抖动的指数退避。
- 遵循 `--quiet` 以抑制重试日志消息。
如果重试耗尽,同步将优雅中止,并保留之前缓存的公告。
#### 忽略匹配项
通过 `--ignore-file` 提供一个 YAML 忽略文件,以在生成 SARIF / JSON 输出之前抑制特定匹配项。结构:
```
# 在所有位置忽略特定的 advisory ID
advisories:
- GHSA-aaaa-bbbb-cccc
# 通过 PURL 忽略 (@ 后为可选的 semver/range 组件)。如果省略 version/range,则忽略所有版本。
purls:
- pkg:npm/lodash # any version
- pkg:npm/react@>=18.0.0 <18.3.0
# 范围内的忽略 (org 或 org/repo)。仅在这些范围内应用。
scoped:
- scope: my-org
advisories: [GHSA-1111-2222-3333]
- scope: my-org/my-repo
purls:
- pkg:maven/com.example/app@1.2.3
```
规则优先级:
1. 限定仓库范围的块
2. 限定组织范围的块
3. 全局 advisories / purls
第一个匹配的规则将抑制该发现;输出日志将显示被忽略的数量。被忽略的项将从 SARIF 和 JSON/CSV 输出中完全移除。
##### 忽略“无限制”的恶意软件公告
一些恶意软件公告列出的漏洞版本范围实际上涵盖了包的所有可能版本(例如:`*`、`>=0`、`0`、`0.0.0`、`>=0.0.0`)。这些会产生低信噪比的噪音,例如针对私有包的名称影子攻击。
使用此标志:
```
--ignore-unbounded-malware
```
启用后,任何 `vulnerableVersionRange` 归一化为这些无限制模式之一的恶意软件匹配,都会在 JSON / SARIF / CSV 输出之前被过滤掉。摘要行(至标准错误输出)会报告被移除的数量。
目前被视为无限制的启发式模式:
- `*`
- `>= 0`, `> 0`
- `0`, `0.0.0`, `>= 0.0.0`
#### 公告日期截止
使用 `--malware-cutoff` 排除较旧的公告进行匹配。如果某条公告的 `publishedAt` 和 `updatedAt` 时间戳都严格早于截止时间,则该公告将被跳过。
接受的格式:
- 纯日期:`YYYY-MM-DD`(解释为 `YYYY-MM-DDT00:00:00.000Z`)
- 完整的 ISO 时间戳:例如 `2025-09-29T15:30:00Z`
示例:
```
# 忽略完全在 2025 年 9 月 29 日之前发布和最后更新的 advisory
npm run start -- --sbom-cache sboms --malware-cache malware-cache --match-malware --malware-cutoff 2025-09-29
# 使用精确的 timestamp (保留当天 UTC 时间之后更新的 advisory)
npm run start -- --sbom-cache sboms --malware-cache malware-cache --match-malware --malware-cutoff 2025-09-29T12:00:00Z
```
原理:这使你可以专注于新引入/最近更改的恶意软件公告(例如,在增量监控期间),而无需重新报告较旧的历史匹配项。在截止时间之后更新的公告仍然符合条件,即使它最初是在截止时间之前发布的。
### 进度条与日志噪音抑制
当收集大量 SBOM 时,你可以启用轻量级进度条:
```
npm run start -- --sync-sboms --org my-org --sbom-cache sboms --progress
```
次要速率限制警告(可能会在视觉上干扰进度条)会被自动静音。
行为细节:
- 一旦枚举出仓库数量,进度条就会显示所有组织的总体完成进度(如果使用 `--enterprise`)
- 渲染会受到节流(约 12 fps),以避免过度写入标准输出
- 标准错误消息(例如,严重失败)仍会显示
- 抑制功能只会隐藏次要速率限制的信息警告;主要速率限制重试仍会记录一次
为了减少一般日志噪音,你可以使用 `--quiet` 来抑制非错误控制台输出,同时保留进度条、人类可读结果和机器可读的 JSON;或者仅使用 `--suppress-secondary-rate-limit-logs` 来抑制触发速率限制的警告。
### 输出模式
仅输出 JSON 至标准输出:
```
npm run start -- --sbom-cache sboms --purl pkg:npm/chalk@5.6.1 --json
```
人类可读 + JSON(JSON 写入文件;标准输出保持可读性):
```
npm run start -- --sbom-cache sboms --purl pkg:npm/chalk@5.6.1 \
--json --cli --output-file search-results.json
```
如果你指定了 `--cli --json`,则还必须提供 `--output-file`,以避免混合输出导致的标准输出损坏。
输出行和 JSON 输出会附加原因上下文:
- 搜索匹配项:`{query: <原始查询字符串>}`
- 恶意软件匹配项:`{advisory: }`
这使得可以清楚地看出是哪个输入(用户查询或特定公告)导致了每个结果。
#### SARIF 输出与 Code Scanning 上传
为恶意软件匹配生成 SARIF 2.1.0 文件(每个包含匹配项的仓库生成一个):
```
npm run start -- --sbom-cache sboms --malware-cache malware-cache --match-malware --sarif-dir sarif-out
```
每个文件名为 `_.sarif`,并包含规则(每个公告 GHSA 对应一个)和结果(每个匹配的包对应一个)。
将这些 SARIF 文件上传到 GitHub Code Scanning(在每个受影响的仓库中创建警报):
```
npm run start -- --sbom-cache sboms --malware-cache malware-cache \
--match-malware --sarif-dir sarif-out --upload-sarif --token $GITHUB_TOKEN
```
注意事项:
- `--upload-sarif` 需要 `--sarif-dir` 和 `--match-malware`
- 上传需要具有适当 repo/org scope 和访问权限的 token
- 该工具会尝试解析每个仓库的默认分支 commit SHA;如果无法解析,则跳过该仓库的上传
- SARIF 上传合并由 GitHub 处理;针对同一 commit 的重复上传将替换同一工具之前的结果
### 自签名 / 内部证书
如果你的 GitHub Enterprise Server 实例或 TLS 拦截代理使用自签名或私有 CA 证书,请提供一个 PEM 包,以便 REST (Octokit)、GraphQL 公告同步和 SARIF 上传能够信任它:
```
npm run start -- --sync-sboms --enterprise ent \
--base-url https://ghe.internal/api/v3 \
--ca-bundle /path/to/internal-ca.pem \
--sbom-cache sboms --token $GITHUB_TOKEN
```
PEM 文件可以包含多个串联的证书。如果无法读取,会发出警告并使用系统默认信任库。
### 交互模式
在初始收集/加载后进入交互式提示(支持方向键历史记录、Ctrl+C 处理):
```
npm run start -- --sbom-cache sboms --interactive
```
然后每行输入一个 PURL 查询。输入空行或在空行上使用 Ctrl+C 将退出。在非空行上按 Ctrl+C 会清空该行。
## 参数参考| 参数 | 用途 |
|------|---------|
| `--token ` | GitHub token;`--sync-sboms`、`--sync-malware` 和 `--upload-sarif` 必需(或使用 `GITHUB_TOKEN`) |
| `--enterprise ` | 跨 Enterprise 中的所有组织进行收集(同步时与 `--org`/`--repo` 互斥) |
| `--org ` | 单个组织范围(同步时与 `--enterprise`/`--repo` 互斥) |
| `--repo ` | 单个仓库范围,格式为 `owner/name`(同步时与 `--enterprise`/`--org` 互斥) |
| `--base-url ` | GitHub Enterprise Server REST base URL(例如 `https://ghe.example.com/api/v3`) |
| `--concurrency ` | 并行获取 SBOM(默认为 5) |
| `--sbom-delay ` | 获取 SBOM 请求之间的延迟(默认为 3000) |
| `--light-delay ` | 轻量级元数据请求之间的延迟(默认为 100) |
| `--sbom-cache ` | 读取/写入按仓库划分的 SBOM JSON 的目录;SBOM 同步和离线使用时必需 |
| `--sync-sboms` | 执行 API 调用以收集 SBOM;未指定时 CLI 使用 `--sbom-cache` 离线运行 |
| `--progress` | 在 SBOM 收集期间显示进度条 |
| `--suppress-secondary-rate-limit-logs` | 抑制次要速率限制警告日志(与 `--progress` 一起使用时很有用) |
| `--quiet` | 抑制非错误输出(进度条和机器输出仍会发出) |
| `--ca-bundle ` | 包含用于 REST/GraphQL/SARIF 上传的额外 CA 证书的 PEM 包 |
| `--purl ` | 添加 PURL / semver range / 通配符查询(可重复使用) |
| `--purl-file ` | 每行包含一个查询的文件(支持注释) |
| `--json` | 以 JSON 格式输出搜索结果(除非指定了 `--output-file`,否则输出至标准输出) |
| `--cli` | 在生成 JSON/CSV 时同时输出人类可读内容;需要 `--output-file` 以避免标准输出混合 |
| `--csv` | 以 CSV 格式输出结果(搜索 + 恶意软件匹配)(至标准输出或 `--output-file`) |
| `--output-file ` | 将 JSON/CSV 输出写入文件;当 `--cli` 与 `--json` 或 `--csv` 一起使用时必需 |
| `--interactive` | 初始处理完成后进入交互式 PURL 搜索提示 |
| `--sync-malware` | 获取并缓存恶意软件公告(MALWARE);需要 token |
| `--match-malware` | 将 SBOM 包与缓存的恶意软件公告进行匹配 |
| `--malware-cache ` | 存储恶意软件公告缓存的目录(进行恶意软件操作时必需) |
| `--malware-cutoff ` | 排除 `publishedAt` 和 `updatedAt` 均早于截止时间的公告 |
| `--ignore-file ` | YAML 忽略文件(advisories / purls / scoped blocks),用于在输出前过滤匹配项 |
| `--ignore-unbounded-malware` | 抑制具有实际无限制漏洞范围的公告(例如 `*`、`>=0`) |
| `--sarif-dir ` | 为每个仓库写入 SARIF 2.1.0 文件(用于恶意软件匹配) |
| `--upload-sarif` | 将生成的 SARIF 上传到 Code Scanning(需要 `--match-malware` 和 `--sarif-dir`) |
| `--branch-scan` | 获取非默认分支的 SBOM 差异(受 `--branch-limit` 限制) |
| `--branch-limit ` | 限制每个仓库扫描的非默认分支数量(默认为 10) |
| `--diff-base ` | 覆盖 dependency review 差异的 base 分支(默认为仓库的默认分支) |
| `--submit-on-missing-snapshot` | 遇到差异 404 时,运行 Component Detection 提交快照,然后重试 |
| `--submit-languages
- ` | 将快照提交限制为特定语言(逗号分隔) |
| `--component-detection-bin
标签:MITM代理, SBOM, 依赖管理, 硬件无关, 自动化攻击